Rimuovere il primo elemento (con indice zero) dallo stream in modo condizionale


10

Ho il seguente codice:

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

Se la stringa del pugno è uguale, "email"voglio rimuovere la prima stringa dallo Stream. Per altre stringhe dello stream non ho bisogno di questo controllo.

Come potrei raggiungerlo?

PS

Certo che posso trasformarlo in elenco, quindi utilizzare la vecchia scuola per il ciclo, ma ulteriormente ho bisogno di nuovo lo streaming.


3
E se il secondo elemento è "e-mail" non vuoi lasciarlo cadere?
Michalk,

@michalk hai ragione
gstackoverflow

Hmm ... c'è skip(long n)che salta i primi nelementi, ma non so se puoi condizionarlo in qualche modo ...
deHaar

4
@gstackoverflow se è improbabile che le prime due stringhe nello stream equivalgano a "email" ciò che penso sia il caso se stai parlando dell'intestazione del file CSV, puoi usarestream.dropWhile(s -> s.equals("email"));
Eritrean,

1
@Eritrea lo pubblicherà semplicemente;)
YCF_L

Risposte:


6

Mentre il lettore sarà in uno stato non specificato dopo aver costruito un flusso di linee da esso, è in uno stato ben definito prima di farlo.

Quindi puoi farlo

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

Qual è la soluzione più pulita secondo me. Si noti che questo non è lo stesso di Java 9 dropWhile, che lascerebbe cadere più di una riga se corrispondessero.


D'accordo - questa soluzione è migliore ma dropWhile - è il secondo posto. Purtroppo l'autore ha deciso di non pubblicare questa idea come una risposta separata
gstackoverflow il

3

Se non puoi avere la lista e devi farlo solo con a Stream, puoi farlo con una variabile.

Il fatto è che puoi usare una variabile solo se è "finale" o "effettivamente finale", quindi non puoi usare un valore booleano letterale. Puoi ancora farlo con un 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);

Nota: non mi piace usare "variabili esterne" in un Streamperché gli effetti collaterali sono una cattiva pratica nel paradigma di programmazione funzionale. Le opzioni migliori sono benvenute.


usare il compareAndSetè un modo molto elegante di ambientare e ottenere la bandiera allo stesso tempo, bello.
f

Potrebbe fare stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))anche se discutibile.
Joop Eggen,

1
predicatedeve essere apolide
ZhekaKozlov,

3
Se l'uso di AtomicBooleanqui è per soddisfare flussi paralleli (come compareAndSetsuggerisce l'uso di ), allora questo è completamente sbagliato in quanto funzionerà solo se il flusso è sequenziale. Se usi la tecnica con, stream.sequential().filter(...)però, sono d'accordo che funzionerà.
daniel

3
@daniel hai ragione, questo approccio paga le spese di un costrutto thread-safe (per ogni elemento) pur non supportando l'elaborazione parallela. Il motivo per cui così tanti esempi con filtri con stato usano queste classi è che non esiste un equivalente senza sicurezza dei thread e le persone evitano la complessità di creare una classe dedicata nelle loro risposte ...
Holger,

1

Per evitare di controllare la condizione su ciascuna riga del file, leggo e controllo la prima riga separatamente, quindi eseguo la pipeline su tutte le righe senza verificare la condizione:

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

Più semplice su 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 è ancora più semplice:Stream.ofNullable(first).filter(not("email"::equals))
Holger,

1

La risposta di @Arnouds è corretta. Puoi creare un flusso per la prima riga e confrontarlo come di seguito,

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

anche se usi un filtro, verrebbe calcolato per ogni riga. In caso di file di grandi dimensioni, potrebbe causare prestazioni. In caso di limite, verrà confrontato una sola volta.
Code_Mode

Oltre a non lavorare per un lettore, limit(0)sicuramente dovrebbe essere limit(1)...
Holger,

Ho modificato la risposta per limitare 0 dopo la testimonianza.
Code_Mode

1
Vedo che l'hai cambiato, ma .limit(0).filter(line -> !line.startsWith("email"))non ha senso. Il predicato non verrà mai valutato. Per una sorgente di flusso in grado di riprodurre flussi, la combinazione di limit(1)sul primo e skip(1)sul secondo sarebbe corretta. Per una fonte di streaming come un lettore, né funzionerà limit(1)limit(0). Hai appena cambiato la linea che viene ingoiata incondizionatamente. Anche se hai trovato una combinazione che capita di fare la cosa desiderata, sarebbe sulla base di comportamenti non specificati e dipendenti dall'implementazione.
Holger,

0

Per filtrare gli elementi in base al loro indice, è possibile utilizzare AtomicIntegerper archiviare e incrementare l'indice durante l'elaborazione di un 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]
}

Questo approccio consente di filtrare elementi in qualsiasi indice, non solo nel primo.


0

Un po 'più contorto, traendo ispirazione da questo frammento .

È possibile creare un Stream<Integer>che rappresenterà gli indici e "comprimerlo" con il proprio Stream<String>per creare unStream<Pair<String, Integer>>

Quindi filtrare utilizzando l'indice e mapparlo nuovamente 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();
    }
}

0

Ecco un esempio con Collectors.reducing. Ma alla fine crea comunque un elenco.

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

Prova questo:

MutableBoolean isFirst = MutableBoolean.of(true);
lines..dropWhile(e -> isFirst.getAndSet(false) && "email".equals(e))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.