条件付きでストリームから最初の(インデックスが0の)要素を削除する


10

私は次のコードを持っています:

Stream<String> lines = reader.lines();

"email"最初の文字列が等しい場合、ストリームから最初の文字列を削除します。ストリームの他の文字列については、このチェックは必要ありません。

どうすればそれを達成できますか?

PS

確かに、それをリストに変換してから、オールドスクールforループを使用することもできますが、さらにストリームをもう一度必要とします。


3
そして、2番目の要素が「メール」である場合、それをドロップしたくないですか?
ミシャルク

@michalk正解
gstackoverflow

うーん... skip(long n)最初のn要素をスキップするものがありますが、どうにかしてそれを調整できるかどうかわかりません...
deHaar

4
@gstackoverflowストリームの最初の2つの文字列が「電子メール」に等しい可能性が低い場合、csvファイルのヘッダーについて話している場合は、次のように使用できますstream.dropWhile(s -> s.equals("email"));
Eritrean

1
@Eritrean投稿します;)
YCF_L '25年

回答:


6

リーダーは、行のストリームを作成した後は不特定の状態になりますが、実行する前は明確に定義された状態です。

だからできる

String firstLine = reader.readLine();
Stream<String> lines = reader.lines();
if(firstLine != null && !"email".equals(firstLine))
    lines = Stream.concat(Stream.of(firstLine), lines);

これは私の意見では最もクリーンなソリューションです。これは、Java 9と同じではないことに注意してください。Java9はdropWhile、一致する場合に複数の行をドロップします。


同意します-このソリューションの方が優れていますが、dropWhileが2位です。残念ながら、著者はこのアイデアを個別の回答として投稿しないことを決定しました
gstackoverflow '25年

3

あなたがリストを持つことができず、それだけでそれをしなければならない場合 Streamで実行する必要がある場合は、変数を使用して実行できます。

重要なのは、変数が「最終」または「事実上最終」である場合にのみ変数を使用できるため、リテラルブール値を使用できないことです。あなたはまだそれでそれを行うことができますAtomicBoolean

Stream<String> stream  = Arrays.asList("test", "email", "foo").stream();

AtomicBoolean first = new AtomicBoolean(true);
stream.filter(s -> {
    if (first.compareAndSet(true, false)) {
        return !s.equals("email");
    }
    return true;
})
// Then here, do whatever you need
.forEach(System.out::println);

注:Stream関数型プログラミングのパラダイムでは副作用は悪い習慣であるため、「外部変数」の使用は好きではありません。より良いオプションを歓迎します。


を使用するcompareAndSetと、フラグの設定と取得を同時に行う非常にエレガントな方法です。
f1sh

stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))議論の余地はありますが。
Joop Eggen

1
predicate無国籍でな​​ければならない
ZhekaKozlov

3
AtomicBooleanhere の使用が並列ストリームに対応することである場合(compareAndSet提案の使用として)、ストリームがシーケンシャルである場合にのみ機能するため、これは完全に間違っています。stream.sequential().filter(...)ただし、この手法を使用する場合は、それが機能することに同意します。
ダニエル

3
@daniel正解です。このアプローチは、並列処理をサポートせずに、(すべての要素に対して)スレッドセーフ構成の費用を払っています。ステートフルフィルタを備えた非常に多くの例が、これらのクラスを使用する理由は...そこには相当スレッドの安全性なしではなく、人々がその答えに、専用のクラスを作成する煩雑さを避けることである
ホルガー

1

ファイルの各行の状態を確認しないようにするには、最初の行を個別に読み取って確認し、条件を確認せずに残りの行でパイプラインを実行します。

String first = reader.readLine();
Stream<String> firstLines = Optional.of(first)
        .filter(s -> !"email".equals(s))
        .map(s -> Stream.of(s))
        .orElseGet(() -> Stream.empty());

Stream<String> lines = Stream.concat(firstLines, reader.lines());

Java 9以上でよりシンプル:

Stream<String> firstLines = Optional.of(first)
        .filter(s -> !"email".equals(s))
        .stream();

Stream<String> lines = Stream.concat(firstLines, reader.lines());

4
Java 9はさらにシンプルですStream.ofNullable(first).filter(not("email"::equals))
Holger

1

@Arnoudsの答えは正しいです。最初の行に1つのストリームを作成し、以下のように比較できます。

Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);

フィルターを使用する場合でも、フィルターは行ごとに計算されます。巨大なファイルの場合、パフォーマンスを引き起こす可能性があります。制限がある場合は、1回だけ比較されます。
Code_Mode

読者のために働いていないだけでなく、limit(0)きっとlimit(1)
Holger

testimgの後に答えを制限して0に制限しました。
Code_Mode

1
変更したようですが.limit(0).filter(line -> !line.startsWith("email"))、意味がありません。述語は決して評価されません。ストリームを再生できるストリームソースの場合、limit(1)1 skip(1)番目と2番目の組み合わせは適切です。リーダーのようなストリームソースの場合、どちらlimit(1)limit(0)機能しません。無条件に飲み込まれる行を変更しました。あなたがたまたま望ましいことをする組み合わせを見つけたとしても、それは不特定の実装依存の振る舞いの根拠にあるでしょう。
Holger、

0

インデックスに基づいて要素をフィルタリングするには、を使用AtomicIntegerしてインデックスを格納および増分するために使用できますStream

private static void filter(Stream<String> stream) {
  AtomicInteger index = new AtomicInteger();
  List<String> result = stream
      .filter(el -> {
        int i = index.getAndIncrement();
        return i > 0 || (i == 0 && !"email".equals(el));
      })
      .collect(toList());
  System.out.println(result);
}

public static void main(String[] args) {
  filter(Stream.of("email", "test1", "test2", "test3")); 
  //[test1, test2, test3]

  filter(Stream.of("test1", "email", "test2", "test3")); 
  //[test1, email, test2, test3]

  filter(Stream.of("test1", "test2", "test3")); 
  //[test1, test2, test3]
}

このアプローチでは、最初の要素だけでなく、任意のインデックスで要素をフィルタリングできます。


0

少し複雑で、いくつかのインスピレーションを得ます このスニペットいます。

あなたが作成することができます Stream<Integer>インデックスを表しますことを、あなたとそれを「ジップ」Stream<String>を作成するにはStream<Pair<String, Integer>>

次に、インデックスを使用してフィルタリングし、それを Stream<String>

public static void main(String[] args) {
    Stream<String> s = reader.lines();
    Stream<Integer> indexes = Stream.iterate(0, i -> i + 1);

    zip(s, indexes)
        .filter(pair -> !(pair.getKey().equals("email") && pair.getValue() == 0))
        .map(Pair::getKey)
        .forEach(System.out::println);
}

private static Stream<Pair<String,Integer>> zip(Stream<String> stringStream, Stream<Integer> indexesStream){
    Iterable<Pair<String,Integer>> iterable = () -> new ZippedWithIndexIterator(stringStream.iterator(), indexesStream.iterator());
    return StreamSupport.stream(iterable.spliterator(), false);
}

static class ZippedWithIndexIterator implements Iterator<Pair<String, Integer>> {
    private final Iterator<String> stringIterator;
    private final Iterator<Integer> integerIterator;

    ZippedWithIndexIterator(Iterator<String> stringIterator, Iterator<Integer> integerIterator) {
        this.stringIterator = stringIterator;
        this.integerIterator = integerIterator;
    }
    @Override
    public Pair<String, Integer> next() {
        return new Pair<>(stringIterator.next(), integerIterator.next());
    }
    @Override
    public boolean hasNext() {
        return stringIterator.hasNext() && integerIterator.hasNext();
    }
}

0

これがの例Collectors.reducingです。しかし結局のところ、とにかくリストを作成します。

Stream<String> lines = Arrays.asList("email", "aaa", "bbb", "ccc")
        .stream();

List reduceList = (List) lines
        .collect(Collectors.reducing( new ArrayList<String>(), (a, v) -> {
                    List list = (List) a;
                    if (!(list.isEmpty() && v.equals("email"))) {
                        list.add(v);
                    }
                    return a;
                }));

reduceList.forEach(System.out::println);

0

これを試して:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.