java 如何使用流api从列表中获取随机元素?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45981964/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to get a random element from a list with stream api?
提问by aekber
What is the most effective way to get a random element from a list with Java8 stream api?
使用 Java8 流 api 从列表中获取随机元素的最有效方法是什么?
Arrays.asList(new Obj1(), new Obj2(), new Obj3());
Thanks.
谢谢。
回答by Jean-Baptiste Yunès
Why with streams? You just have to get a random number from 0 to the size of the list and then call get
on this index:
为什么使用流?你只需要得到一个从 0 到列表大小的随机数,然后调用get
这个索引:
Random r = new Random();
ElementType e = list.get(r.nextInt(list.size()));
Stream will give you nothing interesting here, but you can try with:
Stream 在这里不会给你任何有趣的东西,但你可以尝试:
Random r = new Random();
ElementType e = list.stream().skip(r.nextInt(list.size()-1)).findFirst().get();
Idea is to skip an arbitrary number of elements (but not the last one!), then get the first element if it exists. As a result you will have an Optional<ElementType>
which will be non empty and then extract its value with get
. You have a lot of options here after having skip.
想法是跳过任意数量的元素(但不是最后一个!),然后获取第一个元素(如果存在)。因此,您将有一个Optional<ElementType>
非空的,然后使用get
. 跳过后,您在这里有很多选择。
Using streams here is highly inefficient...
在这里使用流是非常低效的......
Note: that none of these solutions take in account empty lists, but the problem is defined on non-empty lists.
注意:这些解决方案都没有考虑空列表,但问题是在非空列表上定义的。
回答by RichardK
There are much more efficient ways to do it, but if this has to be Stream the easiest way is to create your own Comparator, which returns random result (-1, 0, 1) and sort your stream:
有更有效的方法来做到这一点,但如果这必须是 Stream,最简单的方法是创建您自己的 Comparator,它返回随机结果 (-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();
ThreadLocalRandom has ready "out of the box" method to get random number in your required range for comparator.
ThreadLocalRandom 已经准备好“开箱即用”的方法来获取比较器所需范围内的随机数。
回答by KidCrippler
If you HAVEto use streams, I wrote an elegant, albeit very inefficient collector that does the job:
如果您HAVE到使用的流,我写了一个优雅的,虽然非常低效的收集,没有工作:
/**
* 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())));
}
Usage:
用法:
@Test
public void standardRandomTest() {
assertThat(Stream.of(1, 2, 3, 4).collect(randomItem())).isBetween(1, 4);
}
回答by Mikusch
While all the given answers work, there is a simple one-liner that does the trick without having to check if the list is empty first:
虽然所有给定的答案都有效,但有一个简单的单行代码可以做到这一点,而无需先检查列表是否为空:
List<String> list = List.of("a", "b", "c");
list.stream().skip((int) (list.size() * Math.random())).findAny();
For an empty list this will return an Optional.empty
.
对于空列表,这将返回一个Optional.empty
.
回答by Grzegorz Piwowarek
Another idea would be to implement your own Spliterator
and then use it as a source for Stream
:
另一个想法是实现您自己的Spliterator
,然后将其用作以下内容的来源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) {
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());
}
and then simply:
然后简单地:
list.stream()
.collect(toShuffledStream())
.findAny();
...but it's definitely an overkill, so if you're looking for a pragmatic approach. Definitely go for Jean's solution.
...但这绝对是一种矫枉过正,所以如果你正在寻找一种务实的方法。绝对去寻求让的解决方案。
回答by Chris A
If you don't know in advance the size of the your list, you could do something like that :
如果您事先不知道列表的大小,您可以执行以下操作:
yourStream.collect(new RandomListCollector<>(randomSetSize));
I guess that you will have to write your own Collector implementation like this one to have an homogeneous randomization :
我想您必须像这样编写自己的收集器实现才能获得均匀的随机化:
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();
}
}
}
If you want something easier and still don't want to load all your list in memory:
如果您想要更简单的东西,但仍然不想将所有列表加载到内存中:
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 );
}
but you are then away from the stream api and could do the same with a basic iterator
但是你离开了流 api,可以用基本的迭代器做同样的事情
回答by Tarandrus
The selected answer has errors in its stream solution... You cannot use Random#nextInt with a non-positive long, "0" in this case. The stream solution will also never choose the last in the list Example:
所选答案在其流解决方案中存在错误......在这种情况下,您不能使用 Random#nextInt 和非正长“0”。流解决方案也永远不会选择列表中的最后一个示例:
List<Integer> intList = Arrays.asList(0, 1, 2, 3, 4);
// #nextInt is exclusive, so here it means a returned value of 0-3
// if you have a list of size = 1, #next Int will throw an IllegalArgumentException (bound must be positive)
int skipIndex = new Random().nextInt(intList.size()-1);
// randomInt will only ever be 0, 1, 2, or 3. Never 4
int randomInt = intList.stream()
.skip(skipIndex) // max skip of list#size - 2
.findFirst()
.get();
My recommendation would be to go with the non-stream approach that Jean-Baptiste Yunès put forth, but if you must do a stream approach, you could do something like this (but it's a little ugly):
我的建议是采用 Jean-Baptiste Yunès 提出的非流方法,但如果你必须采用流方法,你可以这样做(但它有点难看):
list.stream()
.skip(list.isEmpty ? 0 : new Random().nextInt(list.size()))
.findFirst();
回答by Bassem Reda Zohdy
you may add the below portion to your Stream
be for findAny
您可以将以下部分添加到您Stream
的findAny
.sorted((f1, f2) -> (new Random().nextInt(1)) == 0 ? -1 : 1)