web-dev-qa-db-fra.com

Mélangez une liste d'entiers avec l'API Java 8 Streams

J'ai essayé de traduire la ligne suivante de Scala en Java 8 à l'aide de l'API Streams:

// Scala
util.Random.shuffle((1 to 24).toList)

Pour écrire l'équivalent en Java, j'ai créé une plage d'entiers:

IntStream.range(1, 25)

J'ai suspecté de trouver une méthode toList dans l'API de flux, mais IntStream ne connaît que la méthode étrange:

collect(
  Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiConsumer<R,R> combiner)

Comment puis-je mélanger une liste avec l'API Java 8 Streams?

22
deamon

Voici:

List<Integer> integers =
    IntStream.range(1, 10)                      // <-- creates a stream of ints
        .boxed()                                // <-- converts them to Integers
        .collect(Collectors.toList());          // <-- collects the values to a list

Collections.shuffle(integers);

System.out.println(integers);

Impressions:

[8, 1, 5, 3, 4, 2, 6, 9, 7]
30
Andrey Chaschev

Vous pouvez trouver la méthode toShuffledList() suivante utile.

private static final Collector<?, ?, ?> SHUFFLER = Collectors.collectingAndThen(
        Collectors.toCollection(ArrayList::new),
        list -> {
            Collections.shuffle(list);
            return list;
        }
);

@SuppressWarnings("unchecked")
public static <T> Collector<T, ?, List<T>> toShuffledList() {
    return (Collector<T, ?, List<T>>) SHUFFLER;
}

Cela permet le type de ligne suivante:

IntStream.rangeClosed('A', 'Z')
         .mapToObj(a -> (char) a)
         .collect(toShuffledList())
         .forEach(System.out::print);

Exemple de sortie:

AVBFYXIMUDENOTHCRJKWGQZSPL
22
Paul Boddington

Vous pouvez utiliser un comparateur personnalisé qui "trie" les valeurs selon une valeur aléatoire:

public final class RandomComparator<T> implements Comparator<T> {

    private final Map<T, Integer> map = new IdentityHashMap<>();
    private final Random random;

    public RandomComparator() {
        this(new Random());
    }

    public RandomComparator(Random random) {
        this.random = random;
    }

    @Override
    public int compare(T t1, T t2) {
        return Integer.compare(valueFor(t1), valueFor(t2));
    }

    private int valueFor(T t) {
        synchronized (map) {
            return map.computeIfAbsent(t, ignore -> random.nextInt());
        }
    }

}

Chaque objet du flux est associé (paresseusement) à une valeur entière aléatoire sur laquelle nous trions. La synchronisation sur la carte consiste à gérer les flux parallèles.

Vous pouvez ensuite l'utiliser comme ça:

IntStream.rangeClosed(0, 24).boxed()
    .sorted(new RandomComparator<>())
    .collect(Collectors.toList());

L'avantage de cette solution est qu'elle s'intègre dans le pipeline de flux.

6
Xavier

Pour effectuer un mélange efficacement, vous avez besoin de toutes les valeurs à l'avance. Vous pouvez utiliser Collections.shuffle () après avoir converti le flux en liste comme vous le faites dans Scala. 

1
Peter Lawrey

Si vous souhaitez traiter le flux entier sans trop de tracas, vous pouvez simplement créer votre propre collecteur à l'aide de Collectors.collectingAndThen():

public static <T> Collector<T, ?, Stream<T>> toEagerShuffledStream() {
    return Collectors.collectingAndThen(
      toList(),
      list -> {
          Collections.shuffle(list);
          return list.stream();
      });
}

Mais cela ne fonctionnera pas bien si vous voulez limit() le Stream résultant. Afin de surmonter ceci, on pourrait créer un Spliterator personnalisé:

package com.pivovarit.stream;

import Java.util.List;
import Java.util.Random;
import Java.util.Spliterator;
import Java.util.function.Consumer;
import Java.util.function.Supplier;

public class ImprovedRandomSpliterator<T> implements Spliterator<T> {

    private final Random random;
    private final T[] source;
    private int size;

    ImprovedRandomSpliterator(List<T> source, Supplier<? extends Random> random) {
        if (source.isEmpty()) {
            throw new IllegalArgumentException("RandomSpliterator can't be initialized with an empty collection");
        }
        this.source = (T[]) source.toArray();
        this.random = random.get();
        this.size = this.source.length;
    }

    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        int nextIdx = random.nextInt(size);
        int lastIdx = size - 1;

        action.accept(source[nextIdx]);
        source[nextIdx] = source[lastIdx];
        source[lastIdx] = null; // let object be GCed
        return --size > 0;
    }

    @Override
    public Spliterator<T> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return source.length;
    }

    @Override
    public int characteristics() {
        return SIZED;
    }
}

et alors:

public final class RandomCollectors {

    private RandomCollectors() {
    }

    public static <T> Collector<T, ?, Stream<T>> toImprovedLazyShuffledStream() {
        return Collectors.collectingAndThen(
          toCollection(ArrayList::new),
          list -> !list.isEmpty()
            ? StreamSupport.stream(new ImprovedRandomSpliterator<>(list, Random::new), false)
            : Stream.empty());
    }

    public static <T> Collector<T, ?, Stream<T>> toEagerShuffledStream() {
        return Collectors.collectingAndThen(
          toCollection(ArrayList::new),
          list -> {
              Collections.shuffle(list);
              return list.stream();
          });
    }
}

J'ai expliqué les considérations de performance ici: https://4comprehension.com/implementing-a-randomized-stream-spliterator-in-Java/

0
Grzegorz Piwowarek

Si vous recherchez une solution "en continu uniquement" et un déterministe, vous pouvez toujours trier votre ints par une valeur de hachage:

List<Integer> xs=IntStream.range(0, 10)
    .boxed()
    .sorted( (a, b) -> a.hashCode() - b.hashCode() )
    .collect(Collectors.toList());

Si vous préférez avoir un int[] qu'un List<Integer>, vous pouvez simplement les décompresser par la suite. Malheureusement, vous avez franchi l'étape de la boxe pour appliquer une variable Comparator personnalisée. Il n'est donc pas possible d'éliminer cette partie du processus.

List<Integer> ys=IntStream.range(0, 10)
    .boxed()
    .sorted( (a, b) -> a.hashCode() - b.hashCode() )
    .mapToInt( a -> a.intValue())
    .toArray();
0
sigpwned