使用Java 8 Streams API随机播放整数列表

dea*_*mon 26 java scala java-stream

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

// Scala
util.Random.shuffle((1 to 24).toList)
Run Code Online (Sandbox Code Playgroud)

要在Java中编写等效项,我创建了一系列整数:

IntStream.range(1, 25)
Run Code Online (Sandbox Code Playgroud)

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

collect(
  Supplier<R> supplier, ObjIntConsumer<R> accumulator, BiConsumer<R,R> combiner)
Run Code Online (Sandbox Code Playgroud)

如何使用Java 8 Streams API对列表进行混洗?

And*_*hev 32

干得好:

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);
Run Code Online (Sandbox Code Playgroud)

打印:

[8, 1, 5, 3, 4, 2, 6, 9, 7]
Run Code Online (Sandbox Code Playgroud)

  • 啊.它实际上不是一个流解决方案. (15认同)
  • 这不能保证按[API文档](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toList--)编写,因为` toList()`不保证返回的`List`的可变性.这在实践中有效,因为`toList()`的当前实现恰好返回一个可变的`ArrayList`.为了确保实现发生变化时的持续正确性,我们可以显式声明集合类型(`Collectors.toCollection(ArrayList :: new)`),或者将`toList`的结果复制到一个可变列表中(`new ArrayList <> (整数)`). (6认同)
  • 我编辑了答案以使用`IntStream.boxed()`,这非常规范。 (2认同)

Pau*_*ton 24

您可能会发现以下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;
}
Run Code Online (Sandbox Code Playgroud)

这样可以实现以下类型的单线程:

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

示例输出:

AVBFYXIMUDENOTHCRJKWGQZSPL
Run Code Online (Sandbox Code Playgroud)

  • @ M.Justin谢谢,我编辑了答案.这是关于Stream API最令人愤怒的事情之一.它不保证返回List的可变性或线程安全性,甚至不保证它是否具有O(1)`get`方法.除了如此多的Stream API之外,这也很糟糕.将参数作为其逻辑结论,您应该实际使用的返回列表上的唯一方法是`size`和`iterator`. (5认同)
  • 这应该是答案 (3认同)
  • 这不能保证按照 [API 文档](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html#toList--) 所写的那样工作,因为` toList()` 不保证返回的 `List` 的可变性。这在实践中是有效的,因为 `toList()` 的当前实现恰好返回一个 `ArrayList`,它是可变的。如果实现发生变化,为了确保持续的正确性,我们可以显式声明集合类型(`Collectors.toCollection(ArrayList::new)`),或者将 `toList` 的结果复制到可变列表中(`new ArrayList&lt;&gt; (整数)`)。 (2认同)
  • @PaulBoddington 更进一步,值得注意的是一些专业集合实现甚至没有 O(1) `size` 操作(尽管这些绝对是异常值)- https://docs.oracle.com/javase/8 /docs/api/java/util/concurrent/ConcurrentLinkedQueue.html#size-- (2认同)

Xav*_*ier 7

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

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

}
Run Code Online (Sandbox Code Playgroud)

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

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

IntStream.rangeClosed(0, 24).boxed()
    .sorted(new RandomComparator<>())
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

该解决方案的优点是它集成在流管道中.

  • 由于排序算法的变化,这个答案不再适用:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6804124,当比较器不遵守基本合同时可能会抱怨. (3认同)
  • 如果列表中有重复的元素,这将不会完全洗牌,因为它们最终总是彼此相邻。例如,列表“[1,5,1,1,5]”将变为“[5,5,1,1,1]”或“[1,1,1,5,5]”。 (2认同)

Grz*_*rek 6

如果您想处理整个流而没有太多麻烦,您可以简单地使用以下方法创建自己的收集器Collectors.collectingAndThen()

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

limit()但如果您想要生成的流,则这不会很好地执行。为了克服这个问题,可以创建一个自定义 Spliterator:

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

Run Code Online (Sandbox Code Playgroud)

进而:

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();
          });
    }
}
Run Code Online (Sandbox Code Playgroud)

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