Entfernen Sie das erste Element (mit dem Index Null) bedingt aus dem Stream

10

Ich habe folgenden Code:

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

Wenn die erste Zeichenfolge gleich ist, "email"möchte ich die erste Zeichenfolge aus dem Stream entfernen. Für andere Strings aus dem Stream brauche ich diese Prüfung nicht.

Wie könnte ich es erreichen?

PS

Sicher kann ich es in die Liste umwandeln und dann die alte Schule für die Schleife verwenden, aber weiter brauche ich wieder Stream.

gstackoverflow
quelle
3
Und wenn das zweite Element "E-Mail" ist, möchten Sie es nicht löschen?
Michalk
@ Michael Sie sind richtig
gstackoverflow
Hmm ... da ist, skip(long n)dass die ersten nElemente
übersprungen werden
4
@gstackoverflow Wenn es unwahrscheinlich ist, dass die ersten beiden Zeichenfolgen im Stream gleich "E-Mail" sind, was meiner Meinung nach der Fall ist, wenn Sie über den Header der CSV-Datei sprechen, können Siestream.dropWhile(s -> s.equals("email"));
Eritrean
1
@Eritrean es wird nur posten;)
YCF_L

Antworten:

6

Während sich der Leser in einem nicht spezifizierten Zustand befindet, nachdem Sie einen Zeilenstrom daraus erstellt haben, befindet er sich in einem genau definierten Zustand, bevor Sie dies tun.

So können Sie tun

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

Welches ist die sauberste Lösung meiner Meinung nach. Beachten Sie, dass dies nicht mit Java 9 identisch ist dropWhile, bei dem mehr als eine Zeile gelöscht wird, wenn sie übereinstimmen.

Holger
quelle
Zustimmen - diese Lösung besser, aber dropWhile - ist der zweite Platz. Leider hat der Autor beschlossen, diese Idee nicht als separate Antwort zu veröffentlichen
gstackoverflow
3

Wenn Sie die Liste nicht haben können und es nur mit einem tun müssen Stream mit a ausführen , können Sie sie mit einer Variablen ausführen.

Die Sache ist, dass Sie eine Variable nur verwenden können, wenn sie "final" oder "effektiv final" ist, sodass Sie keinen wörtlichen Booleschen Wert verwenden können. Sie können es immer noch mit einem 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);

Hinweis: Ich mag es nicht, "externe Variablen" in einem zu verwenden, Streamda Nebenwirkungen eine schlechte Praxis im Paradigma der funktionalen Programmierung sind. Bessere Optionen sind willkommen.

Arnaud Denoyelle
quelle
Die Verwendung von compareAndSetist eine sehr elegante Art, die Flagge gleichzeitig zu setzen und zu erhalten, schön.
25.
Könnte aber stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))umstritten sein.
Joop Eggen
1
predicatemuss staatenlos sein
ZhekaKozlov
3
Wenn die Verwendung von AtomicBooleanhier für parallele Streams vorgesehen ist (wie die Verwendung von compareAndSetnahe legt), ist dies völlig falsch, da es nur funktioniert, wenn der Stream sequentiell ist. Wenn Sie die Technik stream.sequential().filter(...)jedoch verwenden, stimme ich zu, dass sie funktionieren wird.
Daniel
3
@ Daniel Sie haben Recht, dieser Ansatz zahlt die Kosten für ein thread-sicheres Konstrukt (für jedes Element), ohne die parallele Verarbeitung zu unterstützen. Der Grund, warum so viele Beispiele mit Stateful-Filtern diese Klassen verwenden, ist, dass es ohne Thread-Sicherheit kein Äquivalent gibt und die Leute die Komplexität vermeiden, in ihren Antworten eine dedizierte Klasse zu erstellen…
Holger,
1

Um zu vermeiden, dass die Bedingung in jeder Zeile der Datei überprüft wird, habe ich einfach die erste Zeile separat gelesen und überprüft und dann die Pipeline in den restlichen Zeilen ausgeführt, ohne die Bedingung zu überprüfen:

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

Einfacher auf Java 9+:

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

Stream<String> lines = Stream.concat(firstLines, reader.lines());
ernest_k
quelle
4
Java 9 ist noch einfacher:Stream.ofNullable(first).filter(not("email"::equals))
Holger
1

@ Arnouds Antwort ist richtig. Sie können einen Stream für die erste Zeile erstellen und dann wie folgt vergleichen:

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
quelle
Selbst wenn Sie einen Filter verwenden, wird dieser für jede Zeile berechnet. Bei großen Dateien kann dies zu Leistungseinbußen führen. Im Falle einer Begrenzung würde es nur einmal verglichen.
Code_Mode
Abgesehen davon, dass er nicht für einen Leser arbeitet, sollte er limit(0)sicherlich limit(1)
Holger,
Ich habe die Antwort nach dem Test auf 0 begrenzt.
Code_Mode
1
Ich sehe, dass du es geändert hast, aber es .limit(0).filter(line -> !line.startsWith("email"))macht keinen Sinn. Das Prädikat wird niemals ausgewertet. Für eine Stream-Quelle, die Streams reproduzieren kann, wäre die Kombination von limit(1)der ersten und skip(1)der zweiten korrekt. Für eine Stream-Quelle wie einen Reader funktioniert weder limit(1)noch limit(0). Sie haben gerade geändert, welche Leitung bedingungslos verschluckt wird. Selbst wenn Sie eine Kombination gefunden hätten, die zufällig das Gewünschte tut, würde dies auf einem nicht spezifizierten, implementierungsabhängigen Verhalten beruhen.
Holger
0

Um Elemente basierend auf ihrem Index zu filtern, können Sie den Index AtomicIntegerspeichern und erhöhen, während Sie Folgendes verarbeiten 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]
}

Dieser Ansatz ermöglicht das Filtern von Elementen an jedem Index, nicht nur am ersten.

Evgeniy Khyst
quelle
0

Etwas komplizierter, um sich von diesem Ausschnitt inspirieren zu lassen .

Sie können eine erstellen Stream<Integer>, die Indizes darstellt, und diese mit Ihrer "komprimieren"Stream<String> , um ein zu erstellenStream<Pair<String, Integer>>

Filtern Sie dann anhand des Index und ordnen Sie ihn wieder a zu 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
quelle
0

Hier ist ein Beispiel mit Collectors.reducing. Aber am Ende wird trotzdem eine Liste erstellt.

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
quelle
0

Versuche dies:

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