如何从集合中随机选取一个元素? 我特别感兴趣的是从a中随机选取一个元素 Java中的HashSet或LinkedHashSet。 也欢迎其他语言的解决方案。
当前回答
为了好玩,我写了一个基于拒绝抽样的RandomHashSet。这有点粗糙,因为HashMap不让我们直接访问它的表,但它应该工作得很好。
它不使用任何额外的内存,查找时间是O(1)平摊。(因为java哈希表是密集的)。
class RandomHashSet<V> extends AbstractSet<V> {
private Map<Object,V> map = new HashMap<>();
public boolean add(V v) {
return map.put(new WrapKey<V>(v),v) == null;
}
@Override
public Iterator<V> iterator() {
return new Iterator<V>() {
RandKey key = new RandKey();
@Override public boolean hasNext() {
return true;
}
@Override public V next() {
while (true) {
key.next();
V v = map.get(key);
if (v != null)
return v;
}
}
@Override public void remove() {
throw new NotImplementedException();
}
};
}
@Override
public int size() {
return map.size();
}
static class WrapKey<V> {
private V v;
WrapKey(V v) {
this.v = v;
}
@Override public int hashCode() {
return v.hashCode();
}
@Override public boolean equals(Object o) {
if (o instanceof RandKey)
return true;
return v.equals(o);
}
}
static class RandKey {
private Random rand = new Random();
int key = rand.nextInt();
public void next() {
key = rand.nextInt();
}
@Override public int hashCode() {
return key;
}
@Override public boolean equals(Object o) {
return true;
}
}
}
其他回答
PHP,使用MT:
$items_array = array("alpha", "bravo", "charlie");
$last_pos = count($items_array) - 1;
$random_pos = mt_rand(0, $last_pos);
$random_item = $items_array[$random_pos];
c++。这应该是相当快的,因为它不需要遍历整个集合,也不需要对它排序。这应该适用于大多数现代编译器,假设它们支持tr1。如果没有,您可能需要使用Boost。
Boost文档有助于解释这一点,即使你不使用Boost。
诀窍在于利用数据已被划分为多个bucket的事实,并快速识别随机选择的bucket(具有适当的概率)。
//#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;
}
}
这比接受答案中的for-each循环要快:
int index = rand.nextInt(set.size());
Iterator<Object> iter = set.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
return iter.next();
for-each构造在每次循环时调用Iterator.hasNext(),但由于index < set.size(),该检查是不必要的开销。我看到速度提高了10-20%,但是YMMV。(而且,编译时不需要添加额外的return语句。)
请注意,这段代码(以及大多数其他答案)可以应用于任何集合,而不仅仅是集合。通用方法形式:
public static <E> E choice(Collection<? extends E> coll, Random rand) {
if (coll.size() == 0) {
return null; // or throw IAE, if you prefer
}
int index = rand.nextInt(coll.size());
if (coll instanceof List) { // optimization
return ((List<? extends E>) coll).get(index);
} else {
Iterator<? extends E> iter = coll.iterator();
for (int i = 0; i < index; i++) {
iter.next();
}
return iter.next();
}
}
不如就
public static <A> A getRandomElement(Collection<A> c, Random r) {
return new ArrayList<A>(c).get(r.nextInt(c.size()));
}
不幸的是,这在任何标准库集合容器中都不能有效地完成(比O(n)更好)。
这很奇怪,因为向哈希集和二进制集添加随机选择函数是非常容易的。在一个非稀疏散列集中,你可以尝试随机的条目,直到你得到一个命中。对于二叉树,您可以在左子树或右子树之间随机选择,最多有O(log2)步。我已经实现了后面的演示如下:
import random
class Node:
def __init__(self, object):
self.object = object
self.value = hash(object)
self.size = 1
self.a = self.b = None
class RandomSet:
def __init__(self):
self.top = None
def add(self, object):
""" Add any hashable object to the set.
Notice: In this simple implementation you shouldn't add two
identical items. """
new = Node(object)
if not self.top: self.top = new
else: self._recursiveAdd(self.top, new)
def _recursiveAdd(self, top, new):
top.size += 1
if new.value < top.value:
if not top.a: top.a = new
else: self._recursiveAdd(top.a, new)
else:
if not top.b: top.b = new
else: self._recursiveAdd(top.b, new)
def pickRandom(self):
""" Pick a random item in O(log2) time.
Does a maximum of O(log2) calls to random as well. """
return self._recursivePickRandom(self.top)
def _recursivePickRandom(self, top):
r = random.randrange(top.size)
if r == 0: return top.object
elif top.a and r <= top.a.size: return self._recursivePickRandom(top.a)
return self._recursivePickRandom(top.b)
if __name__ == '__main__':
s = RandomSet()
for i in [5,3,7,1,4,6,9,2,8,0]:
s.add(i)
dists = [0]*10
for i in xrange(10000):
dists[s.pickRandom()] += 1
print dists
我得到[995,975,971,995,1057,1004,966,1052,984,1001]作为输出,所以分布很好。
我自己也遇到过同样的问题,但我还不确定这种更有效的选择所带来的性能收益是否值得使用基于python的集合。当然,我可以将其提炼并翻译成C语言,但这对我来说工作量太大了:)
推荐文章
- 如何格式化Joda-Time DateTime仅为mm/dd/yyyy?
- 使嵌套JavaScript对象平放/不平放的最快方法
- 使用该集合的原因。种子功能
- 如何在POM.xml中引用环境变量?
- 大的Ө符号到底代表什么?
- 如何在android中复制一个文件?
- 将整数转换为字符串,以逗号表示千
- 堆与二叉搜索树(BST)
- 接口方法的最终参数-有什么意义?
- Java中的@UniqueConstraint注释
- 给定一个数字数组,返回所有其他数字的乘积的数组(不除法)
- 如何在清洁模式下运行eclipse ?如果我们这样做会发生什么?
- 关于数据库,每个开发人员应该知道些什么?
- 获取java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory异常
- 广度优先Vs深度优先