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

Clu*_*ess 170 java language-agnostic random algorithm set

如何从集合中选择随机元素?我特别感兴趣的是从Java中的HashSet或LinkedHashSet中选择一个随机元素.也欢迎其他语言的解决方案.

Kho*_*oth 82

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++;
}
Run Code Online (Sandbox Code Playgroud)

  • 如果myHashSet很大,那么这将是一个相当慢的解决方案,因为平均而言,需要(n/2)次迭代才能找到随机对象. (87认同)
  • 如果集合未在多次访问中发生变异,则可以将其复制到数组中,然后访问O(1).只需使用myHashSet.toArray() (10认同)
  • @David Nehme:这是Java中HashSet规范的一个缺点.在C++中,通常能够直接访问构成hashset的存储桶,这使我们能够更有效地选择随机元素.如果Java中需要随机元素,那么定义允许用户查看内容的自定义哈希集可能是值得的.请参阅[boost的文档] [1]以获取更多内容.[1] http://www.boost.org/doc/libs/1_43_0/doc/html/unordered/buckets.html (7认同)
  • 如果您的数据在哈希集中,则需要O(n)时间.如果您只是选择单个元素并且数据存储在HashSet中,则无法绕过它. (6认同)
  • @ykaganovich这不会让事情变得更糟,因为这个集合必须被复制到一个新阵列?https://docs.oracle.com/javase/7/docs/api/java/util/AbstractCollection.html#toArray%28%29"即使此集合由数组支持,此方法也必须分配新数组" (2认同)

chi*_*uit 73

有点相关你知道吗:

有一些有用的方法java.util.Collections来改组整个集合:Collections.shuffle(List<?>)Collections.shuffle(List<?> list, Random rnd).

  • 但这仅适用于列表,即具有.get()函数的结构. (20认同)
  • @ bourbaki4481472绝对正确.这仅适用于扩展`List`接口的集合,而不适用于OP讨论的`Set`接口. (4认同)

小智 32

使用a ArrayList和a HashMap:[element - > index] 快速解决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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Sea*_*der 28

这比接受的答案中的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();
Run Code Online (Sandbox Code Playgroud)

for-each构造调用Iterator.hasNext()每个循环,但是因为index < set.size(),该检查是不必要的开销.我看到速度提高10-20%,但是YMMV.(此外,这可以编译而无需添加额外的return语句.)

请注意,此代码(以及大多数其他答案)可以应用于任何集合,而不仅仅是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();
    }
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*yer 15

如果要在Java中执行此操作,则应考虑将元素复制到某种随机访问集合(例如ArrayList)中.因为,除非您的设置很小,否则访问所选元素将是昂贵的(O(n)而不是O(1)).[编辑:列表副本也是O(n)]

或者,您可以查找更符合您要求的另一个Set实现.Commons Collections 的ListOrderedSet看起来很有前途.

  • 这取决于你想从集合中挑选多少次.该副本是一次性操作,然后您可以根据需要从该组中选择多次.如果你只选择一个元素,那么副本不会让事情变得更快. (12认同)
  • 复制到列表将花费O(n)及时使用O(n)内存,那么为什么这比直接从地图中获取更好? (8认同)

Jor*_*ira 8

在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())]);
}
Run Code Online (Sandbox Code Playgroud)

  • 你应该将toArray移动到循环外部. (12认同)
  • 你的答案有效,但由于set.toArray()部分,效率不高. (11认同)

Ben*_*and 8

List asList = new ArrayList(mySet);
Collections.shuffle(asList);
return asList.get(0);
Run Code Online (Sandbox Code Playgroud)

  • 这非常低效.您的ArrayList构造函数在提供的集合上调用.toArray().ToArray(在大多数情况下,如果不是所有标准集合实现中)遍历整个集合,随时填充数组.然后你随机播放列表,用随机元素交换每个元素.只需将集合迭代到随机元素,你会好得多. (21认同)

Jas*_*ley 5

这与接受的答案(Khoth)相同,但删除了不必要的变量sizei变量。

    int random = new Random().nextInt(myhashSet.size());
    for(Object obj : myhashSet) {
        if (random-- == 0) {
            return obj;
        }
    }
Run Code Online (Sandbox Code Playgroud)

尽管消除了上述两个变量,但上述解决方案仍然是随机的,因为我们依赖于 random(从随机选择的索引开始)0在每次迭代中递减。


小智 5

在Java 8中:

static <E> E getRandomSetElement(Set<E> set) {
    return set.stream().skip(new Random().nextInt(set.size())).findFirst().orElse(null);
}
Run Code Online (Sandbox Code Playgroud)

  • @Olivier Faucheux,Random().nextInt() 中的最大元素不包括在内。所以 set.size() 应该没问题。 (2认同)

小智 5

Java 8+ 流:

    static <E> Optional<E> getRandomElement(Collection<E> collection) {
        return collection
                .stream()
                .skip(ThreadLocalRandom.current()
                .nextInt(collection.size()))
                .findAny();
    }
Run Code Online (Sandbox Code Playgroud)

基于Joshua Bone回答,但略有改动:

  • 忽略 Streams 元素顺序以略微提高并行操作的性能
  • 使用当前线程的 ThreadLocalRandom
  • 接受任何集合类型作为输入
  • 返回提供的Optional而不是null