次の問題シナリオがあります。
これをJavaまたはC++で実装する必要があります。
私の最初のアイデアは、次のような関数/メソッドを定義することでした:
問題は、読み取るデータが大きすぎてメインメモリに収まらない可能性があることです。そのため、これらすべてのリストを作成して関数を順番に適用することはできません。
一方、レコードがすべての基になるデータ(基本的には前のレコードと現在のレコードの間のテキスト行、およびレコード)が消費されると、メインメモリにすべてのデータを一度に収める必要はないと思います。それ自体)は処分することができます。
Haskellについての知識がほとんどないので、ある種の遅延評価についてすぐに考えました。完全に計算されたリストに関数を適用する代わりに、互いに積み重ねられたさまざまなデータストリームがあります。毎回、各ストリームの必要な部分だけがメインメモリに実体化されます。
しかし、これをJavaまたはC++で実装する必要があります。したがって、私の質問は、これらの言語の1つでストリームのこの遅延処理を実装できるデザインパターンまたはその他の手法はどれかということです。
Javaを決定する場合は、イテレーターを調べる必要があります。
ファイルから1行を読み取るIterator<String>
を記述します。コンストラクターで上記のイテレーターを受け入れるフィルタリングイテレーターを記述し、必要な行のみを生成します。コンストラクターで文字列イテレーターを受け入れ、各行をレコードに分割する分割Iterator<Record>
を記述します。等々。
「もっとありますか?」で処理を行うことに気付くでしょう。ロジックを正しくするためのセクション。
この質問にはすでに受け入れられた答えがありますが、私は最近、別の興味深い解決策を見つけました。それをここで共有したいと思います。 Javaでストリームを実装するための他の手法について知りたいと思います。
私のソリューションの最初のアイデアは、Scalaのコースから生まれました。このコースでは、Scalaのライブラリストリーム(scala.collection.immutable.Stream
)を試すことができました。このコースでは、Scalaのストリームは、tail()関数/メソッドが遅延計算されるリスト(consセルのシーケンス)と非常に似ていることを説明しました。tailシーケンスは、tail()がオンデマンドで計算されます。が初めて呼び出され、以降のアクセスのためにキャッシュされます。
もちろん、テールも遅延計算されます。つまり、tail()が呼び出されると、テールの最初のconsセルのみが生成されます。このストリームでtail()が呼び出されると、テールストリームの後続の要素が生成されます。 。
解決策の2番目の部分は この質問 とその答えの一部から来ました。私が必要とした唯一の拡張は、レイジーテールのコンスセルを実装することでした。
このソリューションに基づいて、通常の関数(メソッド)take
とdrop
、および高階関数map
、filter
、を定義するのは簡単でした。 dropWhile
、takeWhile
これもストリームで遅延動作します。これらのツールを使用すると、文字列のストリームを操作するという元の問題をはるかに簡単に解決できます。
私の解決策
最初にprogrammers
の質問に示されているリストを実装し、次に同様のデータ型Stream<T>
を実装しました。コンスセルは、クラスCons<T>
のオブジェクトで構成されます。
T
(ヘッド)の値、およびapply
メソッドを持つクロージャ(関数オブジェクト)。主な定義は次のとおりです。
// An interface to store functions (closures) that are
// used to produce a stream's tail on demand.
public interface IStreamFunction<A>
{
public Stream<A> apply();
}
// Wrapper object that invokes a stream function
// and then stores the result for future invocations of tail().
public class Memoizer<Stream<A>>
{
private final IStreamFunction<A> _f;
private Stream<A> _v;
public Memoizer(IStreamFunction<A> f)
{
_f = f;
_v = null;
}
public Stream<A> value()
{
if (_v == null)
{
_v = _f.apply();
}
return _v;
}
}
// Stream class with two subclasses implementing the empty stream
// and a cons cell, respectively.
public abstract class Stream<A>
{
// Private constructor cannot be called by any subclasses except inner classes.
private Stream()
{
}
public abstract A head();
public abstract Stream<A> tail();
public abstract boolean isEmpty();
// Empty stream.
public static final class Nil<A> extends Stream<A>
{
public Nil()
{
}
public A head()
{
throw new NoSuchElementException("Nil.head");
}
public Stream<A> tail()
{
throw new NoSuchElementException("Nil.tail");
}
public boolean isEmpty()
{
return true;
}
}
// Cons cell.
public static final class Cons<A> extends Stream<A>
{
private final A _h;
private final Memoizer<Stream<A>> _m;
public Cons(A h, IStreamFunction<A> f)
{
_h = h;
_m = new Memoizer<Stream<A>>(f);
}
public A head()
{
return _h;
}
public Stream<A> tail()
{
return _m.value();
}
public boolean isEmpty()
{
return false;
}
}
}