Supprimer le premier élément (avec un index nul) du flux de manière conditionnelle

10

J'ai le code suivant:

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

Si la première chaîne est égale à, "email"je veux supprimer la première chaîne du flux. Pour les autres chaînes du flux, je n'ai pas besoin de cette vérification.

Comment pourrais-je y parvenir?

PS

Bien sûr, je peux le transformer en liste, puis utiliser la vieille école pour la boucle, mais j'ai encore besoin de diffuser à nouveau.

gstackoverflow
la source
3
Et si le deuxième élément est "e-mail", vous ne voulez pas le supprimer?
michalk
@michalk vous avez raison
gstackoverflow
Hmm ... il y a skip(long n)ça qui saute les premiers néléments, mais je ne sais pas si vous pouvez le conditionner d'une manière ou d'une autre ...
deHaar
4
@gstackoverflow s'il est peu probable que les deux premières chaînes du flux soient égales à "e-mail", ce que je pense être le cas si vous parlez d'en-tête de fichier csv, vous pouvez utiliserstream.dropWhile(s -> s.equals("email"));
Erythréen
1
@Eritrean il va juste le poster;)
YCF_L

Réponses:

6

Bien que le lecteur soit dans un état non spécifié après que vous en ayez construit un flux de lignes, il est dans un état bien défini avant de le faire.

Vous pouvez donc faire

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

Quelle est la solution la plus propre à mon avis. Notez que ce n'est pas la même chose que Java 9 dropWhile, qui laisserait tomber plus d'une ligne si elles correspondent.

Holger
la source
D'accord - cette solution meilleure mais dropWhile - est la deuxième place. Malheureusement, l'auteur a décidé de ne pas publier cette idée comme réponse séparée
gstackoverflow
3

Si vous ne pouvez pas avoir la liste et devez le faire avec seulement un Stream, vous pouvez le faire avec une variable.

Le fait est que vous ne pouvez utiliser une variable que si elle est "finale" ou "effectivement finale", vous ne pouvez donc pas utiliser un booléen littéral. Vous pouvez toujours le faire avec 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);

Remarque: Je n'aime pas utiliser des "variables externes" dans un Streamcar les effets secondaires sont une mauvaise pratique dans le paradigme de programmation fonctionnelle. De meilleures options sont les bienvenues.

Arnaud Denoyelle
la source
utiliser le compareAndSetest une façon très élégante de régler et d'obtenir le drapeau en même temps, c'est bien.
f1sh
Cela pourrait stream.filter(!first.compareAndSet(true, false) || !s.equals("email"))être discutable.
Joop Eggen
1
predicatedoit être apatride
ZhekaKozlov
3
Si l'utilisation de AtomicBooleanici est de prendre en charge des flux parallèles (comme le compareAndSetsuggère l'utilisation de ), cela est complètement faux car cela ne fonctionnera que si le flux est séquentiel. stream.sequential().filter(...)Cependant, si vous utilisez la technique, je suis d'accord qu'elle fonctionnera.
daniel
3
@daniel vous avez raison, cette approche paie les frais d'une construction thread-safe (pour chaque élément) sans prendre en charge le traitement parallèle. La raison pour laquelle tant d'exemples avec des filtres avec état utilisent ces classes est qu'il n'y a pas d'équivalent sans sécurité des threads et les gens évitent la complexité de créer une classe dédiée dans leurs réponses ...
Holger
1

Pour éviter de vérifier la condition sur chaque ligne du fichier, je voudrais simplement lire et vérifier la première ligne séparément, puis exécuter le pipeline sur le reste des lignes sans vérifier la condition:

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

Plus simple sur Java 9+:

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

Stream<String> lines = Stream.concat(firstLines, reader.lines());
ernest_k
la source
4
Java 9 est encore plus simple:Stream.ofNullable(first).filter(not("email"::equals))
Holger
1

La réponse @Arnouds est correcte. Vous pouvez créer un flux pour la première ligne, puis comparer comme ci-dessous,

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
la source
même si vous utilisez un filtre, il serait calculé pour chaque ligne. En cas de fichier volumineux, cela peut entraîner des performances. En cas de limite, il ne serait comparé qu'une seule fois.
Code_Mode
En plus de ne pas travailler pour un lecteur, cela limit(0)devrait sûrement être limit(1)
Holger
J'ai modifié la réponse pour limiter 0 après le témoignage.
Code_Mode
1
Je vois que vous l'avez changé, mais .limit(0).filter(line -> !line.startsWith("email"))cela n'a aucun sens. Le prédicat ne sera jamais évalué. Pour une source de flux capable de reproduire des flux, la combinaison du limit(1)premier et skip(1)du second serait correcte. Pour une source de flux comme un lecteur, limit(1)ni limit(0)ne fonctionnera. Vous venez de changer la ligne qui est avalée sans condition. Même si vous trouviez une combinaison qui fait la chose souhaitée, ce serait sur la base d'un comportement non spécifié, dépendant de l'implémentation.
Holger
0

Pour filtrer les éléments en fonction de leur index, vous pouvez utiliser AtomicIntegerpour stocker et incrémenter l'index lors du traitement d'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]
}

Cette approche permet de filtrer les éléments à n'importe quel index, pas seulement le premier.

Evgeniy Khyst
la source
0

Un peu plus compliqué, s'inspirant de cet extrait .

Vous pouvez créer un Stream<Integer>qui représentera les index et le "zip" avec votre Stream<String>pour créer unStream<Pair<String, Integer>>

Filtrez ensuite à l'aide de l'index et mappez-le sur un 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
la source
0

Voici un exemple avec Collectors.reducing. Mais à la fin crée une liste de toute façon.

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
la source
0

Essaye ça:

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