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?
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]
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
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.
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.
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/
Si vous recherchez une solution "en continu uniquement" et un déterministe, vous pouvez toujours trier votre int
s 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();