web-dev-qa-db-fra.com

Pouvez-vous diviser un flux en deux flux?

J'ai un ensemble de données représenté par un flux Java 8:

Stream<T> stream = ...;

Je peux voir comment le filtrer pour obtenir un sous-ensemble aléatoire - par exemple

Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();   
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));

Je peux également voir comment je pourrais réduire ce flux pour obtenir, par exemple, deux listes représentant deux moitiés aléatoires de l'ensemble de données, puis les transformer en flux. Mais, existe-t-il un moyen direct de générer deux flux à partir du premier? Quelque chose comme

(heads, tails) = stream.[some kind of split based on filter]

Merci pour toute idée.

118
user1148758

Pas exactement. Vous ne pouvez pas obtenir deux Streamsur un; cela n'a pas de sens - comment itéreriez-vous sur l'un sans avoir besoin de générer l'autre en même temps? Un flux ne peut être exploité qu'une seule fois.

Cependant, si vous voulez les transférer dans une liste ou quelque chose, vous pouvez faire

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));
8
Louis Wasserman

Un collecteur peut être utilisé à cet effet.

  • Pour deux catégories, utilisez Collectors.partitioningBy() factory.

Cela créera une Map de Boolean à List et placera les éléments dans l'une ou l'autre liste en fonction de Predicate.

Remarque: comme le flux doit être consommé entier, cela ne peut pas fonctionner sur des flux infinis. Comme le flux est quand même consommé, cette méthode les place simplement dans des listes au lieu de créer un nouveau flux avec mémoire.

En outre, inutile de recourir à l'itérateur, pas même dans l'exemple Head-Only que vous avez fourni.

Random r = new Random();

Map<Boolean, List<String>> groups = stream
    .collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
  • Pour plus de catégories, utilisez une fabrique Collectors.groupingBy().
Map<Object, List<String>> groups = stream
    .collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());

Si les flux ne sont pas Stream, mais qu’un des flux primitifs tel que IntStream, cette méthode .collect(Collectors) n’est pas disponible. Vous devrez le faire de manière manuelle sans usine de collection. Sa mise en œuvre ressemble à ceci:

IntStream intStream = IntStream.iterate(0, i -> i + 1).limit(1000000);

Predicate<Integer> p = x -> r.nextBoolean();
Map<Boolean, List<Integer>> groups = intStream.collect(() -> {
    Map<Boolean, List<Integer>> map = new HashMap<>();
    map.put(false, new ArrayList<>());
    map.put(true, new ArrayList<>());
    return map;
}, (map, x) -> {
    boolean partition = p.test(x);
    List<Integer> list = map.get(partition);
    list.add(x);
}, (map1, map2) -> {
    map1.get(false).addAll(map2.get(false));
    map1.get(true).addAll(map2.get(true));
});

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());

Éditer

Comme indiqué, la solution de contournement ci-dessus n'est pas thread-safe. La conversion en Stream normale avant la collecte est la voie à suivre:

Stream<Integer> stream = intStream.boxed();
246
Mark Jeronimus

Malheureusement, ce que vous demandez est directement mal vu dans le JavaDoc de Stream :

Un flux ne doit être exploité (en invoquant une opération de flux intermédiaire ou terminal) qu'une seule fois. Cela exclut, par exemple, les flux "fourchus", où la même source alimente deux pipelines ou plus, ou plusieurs traversées du même flux.

Vous pouvez contourner ce problème en utilisant peek ou d'autres méthodes si vous désirez vraiment ce type de comportement. Dans ce cas, vous devez au lieu d'essayer de sauvegarder deux flux de la même source de flux d'origine avec un filtre de forking, vous dupliquez votre flux et filtrez chacun des doublons de manière appropriée.

Cependant, vous voudrez peut-être reconsidérer si une Stream est la structure appropriée pour votre cas d'utilisation.

19
Trevor Freeman

Je suis tombé par hasard sur cette question et j’ai l’impression que certains cas d’utilisation pourraient se révéler valables. J'ai écrit le code ci-dessous en tant que consommateur afin qu'il ne fasse rien, mais que vous puissiez l'appliquer à des fonctions et à tout ce que vous pourriez rencontrer.

class PredicateSplitterConsumer<T> implements Consumer<T>
{
  private Predicate<T> predicate;
  private Consumer<T>  positiveConsumer;
  private Consumer<T>  negativeConsumer;

  public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
  {
    this.predicate = predicate;
    this.positiveConsumer = positive;
    this.negativeConsumer = negative;
  }

  @Override
  public void accept(T t)
  {
    if (predicate.test(t))
    {
      positiveConsumer.accept(t);
    }
    else
    {
      negativeConsumer.accept(t);
    }
  }
}

Maintenant, votre implémentation de code pourrait ressembler à ceci:

personsArray.forEach(
        new PredicateSplitterConsumer<>(
            person -> person.getDateOfBirth().isPresent(),
            person -> System.out.println(person.getName()),
            person -> System.out.println(person.getName() + " does not have Date of birth")));
15
LudgerP

Ceci est contraire au mécanisme général de Stream. Supposons que vous puissiez diviser les flux S0 en Sa et Sb comme vous le souhaitiez. Toute opération de terminal, par exemple count(), sur Sa "consommera" nécessairement tous les éléments de S0. Sb a donc perdu sa source de données.

Auparavant, Stream utilisait une méthode tee() qui duplique un flux en deux. C'est enlevé maintenant.

Stream possède une méthode peek (), mais vous pourrez peut-être l'utiliser pour répondre à vos besoins.

7
ZhongYu

pas exactement, mais vous pourrez peut-être accomplir ce dont vous avez besoin en appelant Collectors.groupingBy(). vous créez une nouvelle collection et pouvez ensuite instancier des flux sur cette nouvelle collection.

5
aepurniet

C'était la moins mauvaise réponse que je pouvais trouver.

import org.Apache.commons.lang3.Tuple.ImmutablePair;
import org.Apache.commons.lang3.Tuple.Pair;

public class Test {

    public static <T, L, R> Pair<L, R> splitStream(Stream<T> inputStream, Predicate<T> predicate,
            Function<Stream<T>, L> trueStreamProcessor, Function<Stream<T>, R> falseStreamProcessor) {

        Map<Boolean, List<T>> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));
        L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());
        R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());

        return new ImmutablePair<L, R>(trueResult, falseResult);
    }

    public static void main(String[] args) {

        Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(10);

        Pair<List<Integer>, String> results = splitStream(stream,
                n -> n > 5,
                s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),
                s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));

        System.out.println(results);
    }

}

Cela prend un flux d’entiers et les divise en 5. Pour ceux de plus de 5, il filtre uniquement les nombres pairs et les met dans une liste. Pour le reste, il les joint avec |.

les sorties:

 ([6, 8],0|1|2|3|4|5)

Ce n’est pas idéal car il rassemble tout dans des collections intermédiaires, ce qui brise le flux (et a trop d’arguments!)

1
Ian Jones

Je suis tombé sur cette question en cherchant un moyen de filtrer certains éléments d'un flux et de les consigner comme des erreurs. Je n'avais donc pas vraiment besoin de scinder le flux, mais d'associer une action de terminaison prématurée à un prédicat avec une syntaxe discrète. Voici ce que je suis venu avec:

public class MyProcess {
    /* Return a Predicate that performs a bail-out action on non-matching items. */
    private static <T> Predicate<T> withAltAction(Predicate<T> pred, Consumer<T> altAction) {
    return x -> {
        if (pred.test(x)) {
            return true;
        }
        altAction.accept(x);
        return false;
    };

    /* Example usage in non-trivial pipeline */
    public void processItems(Stream<Item> stream) {
        stream.filter(Objects::nonNull)
              .peek(this::logItem)
              .map(Item::getSubItems)
              .filter(withAltAction(SubItem::isValid,
                                    i -> logError(i, "Invalid")))
              .peek(this::logSubItem)
              .filter(withAltAction(i -> i.size() > 10,
                                    i -> logError(i, "Too large")))
              .map(SubItem::toDisplayItem)
              .forEach(this::display);
    }
}
1
Sebastian Hans