私はそこにたくさんの文字列があるテキストファイルを持っています。私は、ストリームを使用してJava 8に相当するものを実装するにはどうすればよいgrepのようなjava 8ストリームからマッチングする前後の行を取得する方法は?
grep -A 10 -B 10 "ABC" myfile.txt
:私はgrepの中に一致する前と後の行を検索したい場合は、私はこのように行うのでしょうか?
私はそこにたくさんの文字列があるテキストファイルを持っています。私は、ストリームを使用してJava 8に相当するものを実装するにはどうすればよいgrepのようなjava 8ストリームからマッチングする前後の行を取得する方法は?
grep -A 10 -B 10 "ABC" myfile.txt
:私はgrepの中に一致する前と後の行を検索したい場合は、私はこのように行うのでしょうか?
このようなシナリオは、既存のメソッドがストリーム内の要素ネイバーへのアクセスを提供しないため、Stream APIではうまくサポートされていません。私はカスタムイテレータ/ spliteratorsとサードパーティのライブラリ呼び出しを作成せずに、最大考えることができる最も近いソリューションはList
に入力ファイルを読み込んで、ストリームインデックスを使用することです:
List<String> input = Files.readAllLines(Paths.get(fileName));
Predicate<String> pred = str -> str.contains("ABC");
int contextLength = 10;
IntStream.range(0, input.size()) // line numbers
// filter them leaving only numbers of lines satisfying the predicate
.filter(idx -> pred.test(input.get(idx)))
// add nearby numbers
.flatMap(idx -> IntStream.rangeClosed(idx-contextLength, idx+contextLength))
// remove numbers which are out of the input range
.filter(idx -> idx >= 0 && idx < input.size())
// sort numbers and remove duplicates
.distinct().sorted()
// map to the lines themselves
.mapToObj(input::get)
// output
.forEachOrdered(System.out::println);
grepの出力も"--"
のような特殊な区切り文字を含んでいます省略された行を指定します。 Tagir Valeevが指摘したように、この種の
// Same as IntStream.range(...).filter(...) steps above
IntStreamEx.ofIndices(input, pred)
// same as above
.flatMap(idx -> IntStream.rangeClosed(idx-contextLength, idx+contextLength))
// remove numbers which are out of the input range
.atLeast(0).less(input.size())
// sort numbers and remove duplicates
.distinct().sorted()
.boxed()
// merge adjacent numbers into single interval and map them to subList
.intervalMap((i, j) -> (j - i) == 1, (i, j) -> input.subList(i, j + 1))
// flatten all subLists prepending them with "--"
.flatMap(list -> StreamEx.of(list).prepend("--"))
// skipping first "--"
.skip(1)
.forEachOrdered(System.out::println);
:あなたはさらに行くと、同様な動作を模倣したい場合は、この場合に便利ですintervalMap
方法を持っているとして、私は私の自由StreamExライブラリをしようとするあなたを提案することができます問題はストリームAPIでうまくサポートされていません。インクリメンタルに入力から行を読み込み、一致する行をコンテキストで表示したい場合は、ステートフルなパイプラインステージ(またはカスタムコレクタまたはスプライテータ)を導入する必要があります。
すべての行をメモリに読み込む場合は、BitSet
がマッチのグループを操作するのに便利です。これはTagirの解と類似していますが、整数範囲を使用して印刷する線を表現する代わりに、BitSet
の1ビットを使用します。 BitSet
のいくつかの利点は、多数の組み込みの一括操作があり、内部表現がコンパクトであることです。また、1ビットのインデックスのストリームを生成することもできます。これは、この問題に非常に役立ちます。
まずは、述語に一致する行ごとに1ビットの持つBitSet
作成することから始めてみましょう:
void contextMatch(Predicate<String> pred, int before, int after, List<String> input) {
int len = input.size();
BitSet matches = IntStream.range(0, len)
.filter(i -> pred.test(input.get(i)))
.collect(BitSet::new, BitSet::set, BitSet::or);
を今、私たちはマッチした行のビットセットを持っていることを、私たちはインデックスをストリーム各1ビットの次に、前後のコンテキストを表すビットセット内のビットを設定します。これにより、1ビットがコンテキスト行を含めて印刷されるすべての行を表す単一のBitSet
が得られます。
BitSet context = matches.stream()
.collect(BitSet::new,
(bs,i) -> bs.set(Math.max(0, i - before), Math.min(i + after + 1, len)),
BitSet::or);
我々だけでコンテキストを含むすべての行を、印刷したい場合は、我々はこれを行うことができます:
context.stream()
.forEachOrdered(i -> System.out.println(input.get(i)));
実際grep -A a -B b
コマンドは、コンテキスト行の各グループ間の区切りを印刷します。セパレータをいつ印刷するかを理解するために、コンテキストビットセット内の各1ビットを見る。その前に0ビットがある場合、または最初にある場合は、結果にビットを設定します。これは、コンテキスト行の各グループの先頭に私たちに1ビットを与える:
私たちは、コンテキスト行の各グループの前に区切りを印刷する必要はありません。それぞれのグループにの間にを印刷します。つまり、最初の1ビット(ある場合)をクリアする必要があります。
// clear the first bit
int first = separators.nextSetBit(0);
if (first >= 0) {
separators.clear(first);
}
ここで結果の行を出力できます。しかし、それぞれの行を印刷する前に、我々は最初の区切り文字を印刷する必要があるかどうかを確認します。あなたは、サードパーティのライブラリを使用するために喜んでいると並列処理を必要としない場合
context.stream()
.forEachOrdered(i -> {
if (separators.get(i)) {
System.out.println("--");
}
System.out.println(input.get(i));
});
}
興味深いアプローチ、upvoted。もう一つの方法は、最初の2つのステップを 'IntStream.range(..)。filter(..)。flatMap(..)。filter(..)'のステップをマージして、 '.collect(BitSet ::新しい、BitSet :: set、BitSet :: or) '.distinct()。sorted()'の代わりに。これはメモリ効率を維持する一方で、よりストリーに見えるかもしれません。 Btw 'i> 0 &&!context.get(i-1)|| i == 0'は 'i == 0 ||に短縮することができる。 !context.get(i-1) '。 –
私はあなたの中間ステップを簡素化しました。私はあなたが私がそれを直接編集したことに気にしないことを願っています。その文脈では分かりやすいが、私にはコメントが難しいように見えた。 – Holger
@TagirValeevあなたの "btw"に良い提案。私は 'i == 0'の場合を追加してそのエッジケースを手に入れました。編集されました。 –
、そしてjOOλは、SQLスタイルのウィンドウを提供しています機能、
-1: .window(-1, 1)
0: .filter(w -> w.value().contains("ABC"))
+1: .forEach(w -> {
-1: System.out.println("+1:" + w.lead().orElse(""));
0: // ABC: Just checking
+1: });
lead()
関数はウィンドウからトラバーサル順で次の値をアクセス0降伏
Seq.seq(Files.readAllLines(Paths.get(new File("/path/to/Example.java").toURI())))
.window(-1, 1)
.filter(w -> w.value().contains("ABC"))
.forEach(w -> {
System.out.println("-1:" + w.lag().orElse(""));
System.out.println(" 0:" + w.value());
System.out.println("+1:" + w.lead().orElse(""));
// ABC: Just checking
});
を次のように関数は前の行にアクセスします。
免責事項:私は残念ながら、箱から出しストリームAPIでサポートされていないですが、何がしたいことは、「スライディングウィンドウ」と呼ばれているjOOλ
の背後にある会社のために働きます。 –