从集合中获取随机元素

21

我有一个Collection<Obj>,如何从中获取一个随机的Obj

我查看了文档,似乎没有一种方法可以做到这一点,因为迭代器是访问集合的唯一方式。 我必须遍历它才能获得随机对象吗?


12
从集合的大小中获取随机整数,即 random.nextInt(collection.size()),然后迭代该数字次数。 - Hovercraft Full Of Eels
3
如果您需要随机访问,那么您需要使用列表(List)。 - Peter Lawrey
9个回答

58

使用Lambda表达式可以很快地完成这项任务,并处理集合为空的情况。

public static <E> Optional<E> getRandom (Collection<E> e) {

    return e.stream()
            .skip((int) (e.size() * Math.random()))
            .findFirst();
}

4
喜欢这个答案,但对其进行了小的修改: 将 skip((int) (e.size() * Math.random())) 改为 skip(new Random().nextInt(collection.size())) - BitfulByte
1
@BitfulByte 这个代码在空集合上无法工作。nextInt的参数应该大于0。 - Eikthyrnir

30
最高效的方法是只迭代到你需要的程度。
public static <T> T random(Collection<T> coll) {
    int num = (int) (Math.random() * coll.size());
    for(T t: coll) if (--num < 0) return t;
    throw new AssertionError();
}

8
private Object getRandomObject(Collection from) {
   Random rnd = new Random();
   int i = rnd.nextInt(from.size());
   return from.toArray()[i];
}

19
为了获取单个元素,将整个集合存储到数组中似乎有些浪费。 - arshajii
@Keppil - 那不会意味着我们永远无法获得集合中的第一个项目吗? - Sridhar Sarnobat
4
不,nextInt(max) 返回的值是从 0max-1 的范围内。 - Keppil

5

我知道这是一个老话题了,但我很惊讶居然没有人提到RandomAccess接口,它将List实现标记为具有非常快速的索引访问。

以下是RandomAccess的文档:

由List实现使用的标记接口,表示它们支持快速(通常是恒定时间)随机访问。此接口的主要目的是允许通用算法在应用于随机或顺序访问列表时改变其行为以提供良好的性能。

例如: 它是ArrayList实现的一部分,与LinkedList不同。

这是我的解决方案,利用了它:

public static <E> E getRandomElement(Collection<E> collection) 
{
    if(collection.isEmpty()) 
        throw new IllegalArgumentException("Cannot return a random value from an empty collection!");

    int randomIndex = ThreadLocalRandom.current().nextInt(collection.size());

    if(collection instanceof RandomAccess) 
        return ((List<E>) collection).get(randomIndex);

    for(E element : collection)
    {
        if(randomIndex == 0)
            return element;

        randomIndex--;
    }

    throw new IllegalStateException("How did we get here?"); //unreachable
}

1

几个选项(按效率顺序):

  • 使用List而不是Collection,
  • 使用random.nextInt(collection.size())生成一个随机索引,然后获取迭代器并进行迭代,
  • 使用random.nextInt(collection.size())生成一个随机索引,将集合转换为数组并索引该数组。

1
如果你不介意使用第三方库,Utils库有一个IterableUtils方法,其中包含一个randomFrom(Iterable iterable)方法,它将获取一个Collection并返回其中的一个随机元素。
Collection<Object> collection = ....;
Object random = IterableUtils.randomFrom(collection);

它在Maven中央仓库中:

<dependency>
  <groupId>com.github.rkumsher</groupId>
  <artifactId>utils</artifactId>
  <version>1.3</version>
</dependency>

0

可以使用 Stream#skip 以及 ThreadLocalRandom

public static <T> T getRandomElement(final Collection<T> collection) {
    return Objects.requireNonNull(collection, "collection is null").stream()
      .skip(ThreadLocalRandom.current().nextInt(Math.max(collection.size(), 1)))
      .findFirst().orElseThrow(() -> new IllegalArgumentException("collection is empty"));
}

0
使用Google Guava Iterables.get() 方法的解决方案:
private <T> T getRandomObject(Collection<T> from) {
   Random rnd = new Random();
   int i = rnd.nextInt(from.size());
   return Iterables.get(from, i);
}

如果您想处理空集合,可以使用带有defaultValue的方法Iterables.get(from, i, null)


-4

使用Collections.shuffle(list);,然后您只需获取第一个元素即可。它将是随机的。

或者您也可以这样做

int size = list.size();
int item = new Random().nextInt(size); 
list.get(item )

3
由于需要使用List才能洗牌Collection,因此无法对Collection进行洗牌,这就是OP所询问的内容。 - Janne Valkealahti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接