从集合中随机选择一个元素

216

如何从一个集合中随机选取元素? 我特别希望能在Java中从HashSet或LinkedHashSet中随机选择一个元素。 其他语言的方案也可以考虑。


5
你应该指定一些条件来确定这是否是你真正想要的。
  • 你将选择多少次随机元素?
  • 数据需要存储在 HashSet 还是 LinkedHashSet 中?两者都不支持随机访问。
  • HashSet 很大吗?键很小吗?
- David Nehme
35个回答

3

使用Guava,我们可以比Khoth的答案更好地完成这个任务:

public static E random(Set<E> set) {
  int index = random.nextInt(set.size();
  if (set instanceof ImmutableSet) {
    // ImmutableSet.asList() is O(1), as is .get() on the returned list
    return set.asList().get(index);
  }
  return Iterables.get(set, index);
}

3

Clojure解决方案:

(defn pick-random [set] (let [sq (seq set)] (nth sq (rand-int (count sq)))))

1
这个解决方案也是线性的,因为要获取第 n 个元素,你必须遍历整个 seq - Bruno Kim
1
它是线性的,因为它可以很好地适应一行:D - Krzysztof Wolny

2
上述解决方案侧重于延迟,但并不保证每个索引被选择的概率相等。如果需要考虑这一点,可以尝试蓄水池抽样(reservoir sampling)算法。http://en.wikipedia.org/wiki/Reservoir_sampling。少数人建议使用Collections.shuffle()函数,它使用了这种算法。

2
C++。这应该相当快,因为它不需要迭代整个集合或对其进行排序。假设现代编译器支持tr1,这应该可以直接使用。如果不支持,您可能需要使用Boost。 Boost文档在这里非常有帮助,即使您不使用Boost也是如此。
关键是利用数据已被分成存储桶的事实,并快速识别随机选择的存储桶(具有适当的概率)。
//#include <boost/unordered_set.hpp>  
//using namespace boost;
#include <tr1/unordered_set>
using namespace std::tr1;
#include <iostream>
#include <stdlib.h>
#include <assert.h>
using namespace std;

int main() {
  unordered_set<int> u;
  u.max_load_factor(40);
  for (int i=0; i<40; i++) {
    u.insert(i);
    cout << ' ' << i;
  }
  cout << endl;
  cout << "Number of buckets: " << u.bucket_count() << endl;

  for(size_t b=0; b<u.bucket_count(); b++)
    cout << "Bucket " << b << " has " << u.bucket_size(b) << " elements. " << endl;

  for(size_t i=0; i<20; i++) {
    size_t x = rand() % u.size();
    cout << "we'll quickly get the " << x << "th item in the unordered set. ";
    size_t b;
    for(b=0; b<u.bucket_count(); b++) {
      if(x < u.bucket_size(b)) {
        break;
      } else
        x -= u.bucket_size(b);
    }
    cout << "it'll be in the " << b << "th bucket at offset " << x << ". ";
    unordered_set<int>::const_local_iterator l = u.begin(b);
    while(x>0) {
      l++;
      assert(l!=u.end(b));
      x--;
    }
    cout << "random item is " << *l << ". ";
    cout << endl;
  }
}

2

Perl 5

@hash_keys = (keys %hash);
$rand = int(rand(@hash_keys));
print $hash{$hash_keys[$rand]};

这是一种实现方式。

1

如果你只是想从Set中随机获取“任何”一个对象,而不需要任何关于随机性的保证,最简单的方法是使用迭代器返回的第一个对象。

    Set<Integer> s = ...
    Iterator<Integer> it = s.iterator();
    if(it.hasNext()){
        Integer i = it.next();
        // i is a "random" object from set
    }

1
这不会是随机选择。想象一下对同一组数据进行多次相同的操作,我认为顺序将是相同的。 - Menezes Sousa

1

在Mathematica中:

a = {1, 2, 3, 4, 5}

a[[ ⌈ Length[a] Random[] ⌉ ]]

或者在最近的版本中,只需:

RandomChoice[a]

Random[] 生成一个介于0和1之间的伪随机浮点数。然后将其乘以列表的长度,再使用天花板函数向上舍入到下一个整数。然后从a中提取此索引。

由于哈希表功能通常在Mathematica中使用规则完成,并且规则存储在列表中,因此可以使用以下方法:

a = {"Badger" -> 5, "Bird" -> 1, "Fox" -> 3, "Frog" -> 2, "Wolf" -> 4};

1

既然你说“其他语言的解决方案也可以”,这是Python版本:

>>> import random
>>> random.choice([1,2,3,4,5,6])
3
>>> random.choice([1,2,3,4,5,6])
4

3
由于它不支持像快速查找之类的功能,因此仅仅 [1,2,3,4,5,6] 不是一个集合,而是一个列表。 - Thomas Ahle
你仍然可以这样做:>>> random.choice(list(set(range(5)))) >>> 4 如果你非常需要的话,这并不是理想的选择,但它能够胜任。 - SapphireSun

1

你不能只是获取集合/数组的大小/长度,生成一个介于0和大小/长度之间的随机数,然后调用索引与该数字匹配的元素吗? HashSet有一个 .size() 方法,我非常确定。

伪代码如下 -

function randFromSet(target){
 var targetLength:uint = target.length()
 var randomIndex:uint = random(0,targetLength);
 return target[randomIndex];
}

只有在容器支持随机索引查找时,此方法才有效。许多容器实现不支持随机索引查找(例如哈希表、二叉树、链表)。 - David Haley

1

PHP,假设“set”是一个数组:

$foo = array("alpha", "bravo", "charlie");
$index = array_rand($foo);
$val = $foo[$index];

Mersenne Twister 函数更好,但 PHP 中没有 MT 等效的 array_rand。

大多数集合实现都没有get(i)或索引运算符,所以我认为这就是OP指定它是一个集合的原因。 - DownloadPizza

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