如何从一个集合中随机选取元素? 我特别希望能在Java中从HashSet或LinkedHashSet中随机选择一个元素。 其他语言的方案也可以考虑。
如何从一个集合中随机选取元素? 我特别希望能在Java中从HashSet或LinkedHashSet中随机选择一个元素。 其他语言的方案也可以考虑。
int size = myHashSet.size();
int item = new Random().nextInt(size); // In real life, the Random object should be rather more shared than this
int i = 0;
for(Object obj : myhashSet)
{
if (i == item)
return obj;
i++;
}
有一个相关的“你知道吗”:
在java.util.Collections
中,有一些方法可以对整个集合进行洗牌操作:Collections.shuffle(List<?>)
和Collections.shuffle(List<?> list, Random rnd)
。
List
接口的集合,而不是 OP 讨论的 Set
接口。 - ThomasList.get(new Random().nextInt(size))
。 - Erk在Java 8中:
static <E> E getRandomSetElement(Set<E> set) {
return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
set.size()
,而是使用 set.size() -1
。 - Olivier Faucheux使用ArrayList
和HashMap
实现Java的快速解决方案:[元素->索引]。
动机:我需要一个具有RandomAccess
属性的项目集,特别是从集合中随机选择一个项目(请参阅pollRandom
方法)。在二叉树中进行随机导航不够准确:树不是完美平衡的,这将不会导致均匀分布。
public class RandomSet<E> extends AbstractSet<E> {
List<E> dta = new ArrayList<E>();
Map<E, Integer> idx = new HashMap<E, Integer>();
public RandomSet() {
}
public RandomSet(Collection<E> items) {
for (E item : items) {
idx.put(item, dta.size());
dta.add(item);
}
}
@Override
public boolean add(E item) {
if (idx.containsKey(item)) {
return false;
}
idx.put(item, dta.size());
dta.add(item);
return true;
}
/**
* Override element at position <code>id</code> with last element.
* @param id
*/
public E removeAt(int id) {
if (id >= dta.size()) {
return null;
}
E res = dta.get(id);
idx.remove(res);
E last = dta.remove(dta.size() - 1);
// skip filling the hole if last is removed
if (id < dta.size()) {
idx.put(last, id);
dta.set(id, last);
}
return res;
}
@Override
public boolean remove(Object item) {
@SuppressWarnings(value = "element-type-mismatch")
Integer id = idx.get(item);
if (id == null) {
return false;
}
removeAt(id);
return true;
}
public E get(int i) {
return dta.get(i);
}
public E pollRandom(Random rnd) {
if (dta.isEmpty()) {
return null;
}
int id = rnd.nextInt(dta.size());
return removeAt(id);
}
@Override
public int size() {
return dta.size();
}
@Override
public Iterator<E> iterator() {
return dta.iterator();
}
}
Concurrent
的集合才是真正安全的,而使用 Collections.synchronized()
包装的集合则是半安全的。此外,OP 没有提到并发性,因此这是一个有效且好的答案。 - TWiStErRobdta
中删除元素(例如,可以通过使用 guava 的 Iterators.unmodifiableIterator
来实现)。否则,在 RandomSet
中使用该迭代器的 AbstractSet 及其父类的默认实现,如 removeAll 和 retainAll,将会破坏您的集合! - muued这比已接受答案中的 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%的速度提升,但这可能因人而异。(此外,这个方法可以编译,而不需要添加额外的返回语句。)
请注意,此代码(以及大多数其他答案)可以应用于任何集合,而不仅仅是Set。在通用方法中:
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();
}
}
在Java中:
Set<Integer> set = new LinkedHashSet<Integer>(3);
set.add(1);
set.add(2);
set.add(3);
Random rand = new Random(System.currentTimeMillis());
int[] setArray = (int[]) set.toArray();
for (int i = 0; i < 10; ++i) {
System.out.println(setArray[rand.nextInt(set.size())]);
}
List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);
这与被接受的答案(Khoth)完全相同,但删除了不必要的size
和i
变量。
int random = new Random().nextInt(myhashSet.size());
for(Object obj : myhashSet) {
if (random-- == 0) {
return obj;
}
}
0
递减。if (--random < 0) {
,其中 random
达到 -1
。 - SalvadorJava 8+ 流:
static <E> Optional<E> getRandomElement(Collection<E> collection) {
return collection
.stream()
.skip(ThreadLocalRandom.current()
.nextInt(collection.size()))
.findAny();
}
基于Joshua Bone的答案,但稍作改动: