37

我尝试使用 Streams API 将以下 Scala 行转换为 Java 8:

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

为了用 Java 编写等价的代码,我创建了一个整数范围:

IntStream.range(1, 25)

toList我怀疑是在stream API中找到了一个方法,但IntStream只知道奇怪的方法:

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

如何使用 Java 8 Streams API 打乱列表?

4

9 回答 9

41

干得好:

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

印刷:

[8, 1, 5, 3, 4, 2, 6, 9, 7]
于 2013-11-18T21:39:12.567 回答
41

您可能会发现以下toShuffledList()方法很有用。

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;
}

这启用了以下类型的单线:

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

示例输出:

AVBFYXIMUDENOTHCRJKWGQZSPL
于 2016-04-03T22:21:43.613 回答
11

您可以使用自定义比较器按随机值“排序”值:

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

}

流中的每个对象(懒惰地)关联一个随机整数值,我们对其进行排序。地图上的同步是为了处理并行流。

然后你可以像这样使用它:

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

此解决方案的优势在于它集成在流管道中。

于 2016-03-07T14:30:29.630 回答
5

如果您想轻松处理整个 Stream,您可以使用以下方法简单地创建自己的 Collector Collectors.collectingAndThen()

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

limit()但是,如果您想要生成的 Stream,这将不会很好。为了克服这一点,可以创建一个自定义拆分器:

package com.pivovarit.stream;

import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.RandomAccess;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.function.Supplier;

class ImprovedRandomSpliterator<T, LIST extends RandomAccess & List<T>> implements Spliterator<T> {

    private final Random random;
    private final List<T> source;
    private int size;

    ImprovedRandomSpliterator(LIST source, Supplier<? extends Random> random) {
        Objects.requireNonNull(source, "source can't be null");
        Objects.requireNonNull(random, "random can't be null");

        this.source = source;
        this.random = random.get();
        this.size = this.source.size();
    }

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

            T last = source.get(lastIdx);
            T elem = source.set(nextIdx, last);
            action.accept(elem);
            return true;
        } else {
            return false;
        }
    }

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

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

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

接着:

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

我在这里解释了性能注意事项:https ://4comprehension.com/implementing-a-randomized-stream-spliterator-in-java/

于 2019-01-04T07:33:32.870 回答
2

要有效地执行随机播放,您需要提前获取所有值。在将流转换为列表后,您可以使用 Collections.shuffle(),就像在 Scala 中所做的那样。

于 2013-11-18T21:36:39.560 回答
2
public static List<Integer> getSortedInRandomOrder(List<Integer> list) {
    return list
            .stream()
            .sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2))
            .collect(Collectors.toList());
}
于 2019-03-26T07:49:14.550 回答
1
List<Integer> randomShuffledRange(int startInclusive, int endExclusive) {
    return new Random().ints(startInclusive, endExclusive)
            .distinct()
            .limit(endExclusive-startInclusive)
            .boxed()
            .collect(Collectors.toList());
}

var shuffled = randomShuffledRange(1, 10);
System.out.println(shuffled);

示例输出:

[4, 6, 8, 9, 1, 7, 3, 5, 2]
于 2021-06-05T19:45:09.960 回答
1

如果您正在寻找“仅流式传输”的解决方案和确定性的、仅“随意”排序而不是“随机”排序就足够了,您始终可以int按哈希值对 s 进行排序:

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

如果您宁愿拥有a 而int[]不是 a List<Integer>,则可以在之后将它们拆箱。不幸的是,您已经通过装箱步骤来应用 custom Comparator,因此没有消除该过程的一部分。

List<Integer> ys=IntStream.range(0, 10)
    .boxed()
    .sorted( (a, b) -> a.hashCode() - b.hashCode() )
    .mapToInt( a -> a.intValue())
    .toArray();
于 2018-05-31T04:08:24.407 回答
-2

这是我的单行解决方案:我正在选择一种随机颜色:

colourRepository.findAll().stream().sorted((o1,o2)-> RandomUtils.nextInt(-1,1)).findFirst().get()
于 2018-01-15T15:22:54.553 回答