Java 如何使用流api从列表中获取随机元素?
使用Java8流api从列表中获取随机元素的最有效方法是什么Java 如何使用流api从列表中获取随机元素?,java,java-8,Java,Java 8,使用Java8流api从列表中获取随机元素的最有效方法是什么 Arrays.asList(new Obj1(), new Obj2(), new Obj3()); 谢谢。为什么要使用流?您只需获取一个从0到列表大小的随机数,然后在此索引上调用get: Random r = new Random(); ElementType e = list.get(r.nextInt(list.size())); Stream在这里不会给您带来任何有趣的东西,但您可以尝试: Random r = new R
Arrays.asList(new Obj1(), new Obj2(), new Obj3());
谢谢。为什么要使用流?您只需获取一个从0到列表大小的随机数,然后在此索引上调用
get
:
Random r = new Random();
ElementType e = list.get(r.nextInt(list.size()));
Stream在这里不会给您带来任何有趣的东西,但您可以尝试:
Random r = new Random();
ElementType e = list.stream().skip(r.nextInt(list.size())).findFirst().get();
其思想是跳过任意数量的元素(但不是最后一个!),然后获取第一个元素(如果它存在的话)。因此,您将有一个非空的可选
,然后使用get
提取其值。在跳过之后,您在这里有很多选择
在这里使用流是非常低效的
注意:这些解决方案都不考虑空列表,但问题是在非空列表上定义的。如果您有使用流,我编写了一个优雅但效率非常低的收集器来完成此任务:
/**
* Returns a random item from the stream (or null in case of an empty stream).
* This operation can't be lazy and is inefficient, and therefore shouldn't
* be used on streams with a large number or items or in performance critical sections.
* @return a random item from the stream or null if the stream is empty.
*/
public static <T> Collector<T, List<T>, T> randomItem() {
final Random RANDOM = new Random();
return Collector.of(() -> (List<T>) new ArrayList<T>(),
(acc, elem) -> acc.add(elem),
(list1, list2) -> ListUtils.union(list1, list2), // Using a 3rd party for list union, could be done "purely"
list -> list.isEmpty() ? null : list.get(RANDOM.nextInt(list.size())));
}
有很多更有效的方法可以做到这一点,但如果这必须是流,最简单的方法是创建自己的比较器,它返回随机结果(-1,0,1)并对流进行排序:
List<String> strings = Arrays.asList("a", "b", "c", "d", "e", "f");
String randomString = strings
.stream()
.sorted((o1, o2) -> ThreadLocalRandom.current().nextInt(-1, 2))
.findAny()
.get();
List strings=Arrays.asList(“a”、“b”、“c”、“d”、“e”、“f”);
字符串随机字符串=字符串
.stream()
.sorted((o1,o2)->ThreadLocalRandom.current().nextInt(-1,2))
.findAny()
.get();
ThreadLocalRandom已经准备好了“开箱即用”方法来获取比较器所需范围内的随机数。若您事先不知道列表的大小,可以这样做:
yourStream.collect(new RandomListCollector<>(randomSetSize));
yourStream.collect(新的RandomListCollector(randomSetSize));
我想您必须编写自己的收集器实现,就像这样,以实现同质随机化:
public class RandomListCollector<T> implements Collector<T, RandomListCollector.ListAccumulator<T>, List<T>> {
private final Random rand;
private final int size;
public RandomListCollector(Random random , int size) {
super();
this.rand = random;
this.size = size;
}
public RandomListCollector(int size) {
this(new Random(System.nanoTime()), size);
}
@Override
public Supplier<ListAccumulator<T>> supplier() {
return () -> new ListAccumulator<T>();
}
@Override
public BiConsumer<ListAccumulator<T>, T> accumulator() {
return (l, t) -> {
if (l.size() < size) {
l.add(t);
} else if (rand.nextDouble() <= ((double) size) / (l.gSize() + 1)) {
l.add(t);
l.remove(rand.nextInt(size));
} else {
// in any case gSize needs to be incremented
l.gSizeInc();
}
};
}
@Override
public BinaryOperator<ListAccumulator<T>> combiner() {
return (l1, l2) -> {
int lgSize = l1.gSize() + l2.gSize();
ListAccumulator<T> l = new ListAccumulator<>();
if (l1.size() + l2.size()<size) {
l.addAll(l1);
l.addAll(l2);
} else {
while (l.size() < size) {
if (l1.size()==0 || l2.size()>0 && rand.nextDouble() < (double) l2.gSize() / (l1.gSize() + l2.gSize())) {
l.add(l2.remove(rand.nextInt(l2.size()), true));
} else {
l.add(l1.remove(rand.nextInt(l1.size()), true));
}
}
}
// set the gSize of l :
l.gSize(lgSize);
return l;
};
}
@Override
public Function<ListAccumulator<T>, List<T>> finisher() {
return (la) -> la.list;
}
@Override
public Set<Characteristics> characteristics() {
return Collections.singleton(Characteristics.CONCURRENT);
}
static class ListAccumulator<T> implements Iterable<T> {
List<T> list;
volatile int gSize;
public ListAccumulator() {
list = new ArrayList<>();
gSize = 0;
}
public void addAll(ListAccumulator<T> l) {
list.addAll(l.list);
gSize += l.gSize;
}
public T remove(int index) {
return remove(index, false);
}
public T remove(int index, boolean global) {
T t = list.remove(index);
if (t != null && global)
gSize--;
return t;
}
public void add(T t) {
list.add(t);
gSize++;
}
public int gSize() {
return gSize;
}
public void gSize(int gSize) {
this.gSize = gSize;
}
public void gSizeInc() {
gSize++;
}
public int size() {
return list.size();
}
@Override
public Iterator<T> iterator() {
return list.iterator();
}
}
}
公共类RandomListCollector实现收集器{
私有最终随机兰德;
私人最终整数大小;
公共随机列表收集器(随机,整数大小){
超级();
this.rand=随机;
这个。大小=大小;
}
公共随机列表收集器(整数大小){
这个(新的随机(System.nanoTime()),大小);
}
@凌驾
公共供应商(){
return()->newlistcummerator();
}
@凌驾
公共双消费者累加器(){
返回(l,t)->{
如果(l.size()la.list;
}
@凌驾
公共集特征(){
返回集合。单例(特征。并发);
}
静态类ListAccumulator实现Iterable{
名单;
易失性内质网;
公共列表(){
列表=新的ArrayList();
gSize=0;
}
公共void addAll(列表l){
list.addAll(l.list);
gSize+=l.gSize;
}
公共T删除(整型索引){
返回删除(索引,false);
}
公共T删除(整型索引,布尔全局){
T=列表。删除(索引);
if(t!=null&&global)
gSize--;
返回t;
}
公共无效添加(T){
列表。添加(t);
gSize++;
}
公共int gSize(){
返回gSize;
}
公共无效gSize(int gSize){
this.gSize=gSize;
}
公共无效gSizeInc(){
gSize++;
}
公共整数大小(){
返回list.size();
}
@凌驾
公共迭代器迭代器(){
return list.iterator();
}
}
}
如果您想要更简单的内容,但仍然不想在内存中加载所有列表:
public <T> Stream<T> getRandomStreamSubset(Stream<T> stream, int subsetSize) {
int cnt = 0;
Random r = new Random(System.nanoTime());
Object[] tArr = new Object[subsetSize];
Iterator<T> iter = stream.iterator();
while (iter.hasNext() && cnt <subsetSize) {
tArr[cnt++] = iter.next();
}
while (iter.hasNext()) {
cnt++;
T t = iter.next();
if (r.nextDouble() <= (double) subsetSize / cnt) {
tArr[r.nextInt(subsetSize)] = t;
}
}
return Arrays.stream(tArr).map(o -> (T)o );
}
publicstream getRandomStreamSubset(Stream-Stream,int-subsetSize){
int-cnt=0;
Random r=新的Random(System.nanoTime());
Object[]tArr=新对象[subsetSize];
迭代器iter=stream.Iterator();
而(iter.hasNext()&&cnt所选答案的流解决方案中有错误。。。
在这种情况下,不能将Random#nextInt与非正长字符“0”一起使用。
流解决方案也不会选择列表中的最后一个
例如:
另一个想法是实现您自己的拆分器
,然后将其用作流
的源:
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) {
if (size > 0) {
int nextIdx = random.nextInt(size);
int lastIdx = size - 1;
action.accept(source[nextIdx]);
source[nextIdx] = source[lastIdx];
source[lastIdx] = null; // let object be GCed
size--;
return true;
} else {
return false;
}
}
@Override
public Spliterator<T> trySplit() {
return null;
}
@Override
public long estimateSize() {
return source.length;
}
@Override
public int characteristics() {
return SIZED;
}
}
public static <T> Collector<T, ?, Stream<T>> toShuffledStream() {
return Collectors.collectingAndThen(
toCollection(ArrayList::new),
list -> !list.isEmpty()
? StreamSupport.stream(new ImprovedRandomSpliterator<>(list, Random::new), false)
: Stream.empty());
}
…但这绝对是一种矫枉过正的做法,因此,如果你正在寻找一种务实的方法。一定要这样做。虽然所有给出的答案都有效,但有一个简单的一行代码可以做到这一点,而不必先检查列表是否为空:
List<String> list = List.of("a", "b", "c");
list.stream().skip((int) (list.size() * Math.random())).findAny();
List List=List.of(“a”、“b”、“c”);
list.stream().skip((int)(list.size()*Math.random()).findAny();
对于空列表,这将返回一个可选。空的您可以将以下部分添加到流中用于findAny
.sorted((f1, f2) -> (new Random().nextInt(1)) == 0 ? -1 : 1)
在上一次我需要做这样的事情时,我做到了:
List<String> list = Arrays.asList("a", "b", "c");
Collections.shuffle(list);
String letter = list.stream().findAny().orElse(null);
System.out.println(letter);
List List=Arrays.asList(“a”、“b”、“c”);
集合。洗牌(列表);
字符串字母=list.stream().findAny().orElse(null);
系统输出打印号(字母);
如果列表为空,则会产生NoTouchElementExceptionempty@AndrewTobilko可以,但从空列表中提取随机元素始终是未定义的。解决方案也是如此…list.stream().skip(r.nextInt(list.size()-1)).findFirst().get();
从不选择流中的最后一个元素。它应该是list.stream().skip(r.nextInt(list.size()).findFirst().get();
因为Random.nextInt(5)
永远不会返回5。鉴于在撰写本文时有25个向上投票,我不敢想象有多少程序在
list.stream()
.collect(toShuffledStream())
.findAny();
List<String> list = List.of("a", "b", "c");
list.stream().skip((int) (list.size() * Math.random())).findAny();
.sorted((f1, f2) -> (new Random().nextInt(1)) == 0 ? -1 : 1)
List<String> list = Arrays.asList("a", "b", "c");
Collections.shuffle(list);
String letter = list.stream().findAny().orElse(null);
System.out.println(letter);