Hapus elemen pertama (dengan indeks nol) dari aliran bersyarat

10

Saya memiliki kode berikut:

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

Jika fist string sama dengan "email"saya ingin menghapus string pertama dari Stream. Untuk string lain dari aliran saya tidak perlu cek ini.

Bagaimana saya bisa mencapainya?

PS

Tentu saya bisa mengubahnya ke daftar, lalu gunakan sekolah tua untuk loop tetapi lebih lanjut saya perlu streaming lagi.

gstackoverflow
sumber
3
Dan jika elemen kedua adalah "email" Anda tidak ingin membuangnya?
michalk
@ Michael, Anda benar
gstackoverflow
Hmm ... ada skip(long n)yang melewatkan nelemen pertama , tapi saya tidak tahu apakah Anda bisa mengkondisikannya ...
deHaar
4
@ gstackoverflow jika tidak mungkin bahwa dua string pertama dalam aliran sama dengan "email" apa yang saya pikirkan adalah kasus jika Anda berbicara tentang header file csv, Anda dapat menggunakanstream.dropWhile(s -> s.equals("email"));
Eritrea
1
@ Eropa hanya akan mempostingnya;)
YCF_L

Jawaban:

6

Sementara pembaca akan berada dalam kondisi yang tidak ditentukan setelah Anda membangun aliran garis dari itu, itu dalam keadaan yang terdefinisi dengan baik sebelum Anda melakukannya.

Jadi kamu bisa melakukannya

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

Yang merupakan solusi terbersih menurut saya. Perhatikan bahwa ini tidak sama dengan Java 9 dropWhile, yang akan turun lebih dari satu baris jika cocok.

Holger
sumber
Setuju - solusi ini lebih baik tetapi dropWhile - adalah tempat kedua. Sayangnya penulis memutuskan untuk tidak memposting ide ini sebagai jawaban yang terpisah
gstackoverflow
3

Jika Anda tidak dapat memiliki daftar dan harus melakukannya hanya dengan Stream, Anda dapat melakukannya dengan variabel.

Masalahnya adalah Anda hanya bisa menggunakan variabel jika "final" atau "efektif final" sehingga Anda tidak dapat menggunakan boolean literal. Anda masih dapat melakukannya dengan 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);

Catatan: Saya tidak suka menggunakan "variabel eksternal" Streamkarena efek samping adalah praktik buruk dalam paradigma pemrograman fungsional. Pilihan yang lebih baik dipersilakan.

Arnaud Denoyelle
sumber
menggunakan compareAndSetadalah cara yang sangat elegan untuk mengatur dan mendapatkan bendera pada saat yang sama, bagus.
f1sh
Bisa dilakukan stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))meski bisa diperdebatkan.
Joop Eggen
1
predicateharus stateless
ZhekaKozlov
3
Jika penggunaan di AtomicBooleansini adalah untuk memenuhi aliran paralel (seperti penggunaan yang compareAndSetdisarankan) maka ini sepenuhnya salah karena hanya akan bekerja jika aliran berurutan. Jika Anda menggunakan teknik dengan stream.sequential().filter(...)namun saya setuju itu akan berhasil.
daniel
3
@aniel Anda benar, pendekatan ini membayar biaya konstruk aman thread (untuk setiap elemen) sementara tidak mendukung pemrosesan paralel. Alasan mengapa begitu banyak contoh dengan filter stateful menggunakan kelas-kelas ini adalah bahwa tidak ada yang setara tanpa keamanan thread dan orang-orang menghindari kompleksitas menciptakan kelas khusus dalam jawaban mereka ...
Holger
1

Untuk menghindari memeriksa kondisi pada setiap baris file, saya cukup membaca dan memeriksa baris pertama secara terpisah, kemudian jalankan pipeline pada sisa baris tanpa memeriksa kondisi:

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());

Lebih sederhana di Java 9+:

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

Stream<String> lines = Stream.concat(firstLines, reader.lines());
ernest_k
sumber
4
Java 9 bahkan lebih sederhana:Stream.ofNullable(first).filter(not("email"::equals))
Holger
1

Jawaban @rnouds benar. Anda dapat membuat satu aliran untuk baris pertama dan kemudian membandingkannya seperti di bawah ini,

Stream<String> firstLineStream = reader.lines().limit(1).filter(line -> !line.startsWith("email"));;
Stream<String> remainingLinesStream = reader.lines().skip(1);
Stream.concat(firstLineStream, remainingLinesStream);
Code_Mode
sumber
bahkan jika Anda menggunakan filter, itu akan dihitung untuk setiap baris. Dalam kasus file besar, ini dapat menyebabkan kinerja. Dalam hal batas, itu akan dibandingkan hanya sekali.
Code_Mode
Selain tidak bekerja untuk seorang pembaca, limit(0)pastilah seharusnya limit(1)...
Holger
Saya telah mengedit jawaban untuk membatasi 0 setelah testimg.
Code_Mode
1
Saya melihat bahwa Anda mengubahnya, tetapi .limit(0).filter(line -> !line.startsWith("email"))tidak masuk akal. Predikat tidak akan pernah dievaluasi. Untuk sumber aliran yang mampu mereproduksi aliran, kombinasi dari limit(1)yang pertama dan skip(1)yang kedua akan benar. Untuk sumber streaming seperti pembaca, tidak limit(1)juga tidak limit(0)akan berfungsi. Anda baru saja mengubah baris mana yang ditelan tanpa syarat. Bahkan jika Anda menemukan kombinasi yang melakukan hal yang diinginkan, itu akan didasarkan pada perilaku tergantung implementasi yang tidak ditentukan.
Holger
0

Untuk memfilter elemen berdasarkan indeksnya, Anda dapat menggunakan AtomicIntegeruntuk menyimpan dan menambah indeks saat memproses 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]
}

Pendekatan ini memungkinkan untuk menyaring elemen pada indeks apa pun, tidak hanya yang pertama.

Evgeniy Khyst
sumber
0

Sedikit lebih berbelit-belit, mendapat inspirasi dari cuplikan ini .

Anda dapat membuat Stream<Integer>yang akan mewakili indeks dan "zip" dengan Anda Stream<String>untuk membuatStream<Pair<String, Integer>>

Kemudian filter menggunakan indeks dan petakan kembali ke a 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();
    }
}
Bentaye
sumber
0

Berikut ini contoh dengan Collectors.reducing. Namun pada akhirnya tetap membuat daftar.

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);
lczapski
sumber
0

Coba ini:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
user_3380739
sumber