【问题标题】:How to get 100 random elements from HashSet in Java?如何从 Java 中的 HashSet 中获取 100 个随机元素?
【发布时间】:2015-04-16 19:34:28
【问题描述】:

我有一个 HashSet,其中有 10000 个元素。我想从那个 HashSet 中随机抽取 100 个元素。所以我想我可以在片场使用 shuffle 但它不起作用。

Set<String> users = new HashSet<String>();

// for randomness, but this doesn't work
Collections.shuffle(users, new Random(System.nanoTime()));  

// and use for loop to get 100 elements

我现在不能使用 shuffle,有没有其他最好的方法从 Java 的 HashSet 中获取 100 个随机元素?

【问题讨论】:

  • 您的代码不会编译,因为Collections.shuffle 需要一个列表。所以试着从你的集合中创建一个List,然后随机播放这个列表。
  • 随机播放users.toArray()的结果。

标签: java random set hashset


【解决方案1】:

无需构建新列表,您可以实现以下算法:

n = 100
d = 10000  # length(users)
for user in users:
    generate a random number p between 0 and 1
    if p <= n / d:
       select user
       n -= 1
    d -= 1

当您遍历列表时,您会降低 通过减少 n 来选择未来的元素,但在 同时增加通过减少d的概率。最初, 您将有 100/10000 的机会选择第一个元素。 如果您决定采用该元素,您将有 99/9999 的机会 选择第二个元素;如果你拿第一个,你会 选择第二个元素的几率为 100/9999。数学计算出来,最终,每个元素都有 100/10000 的机会被选择用于输出。

【讨论】:

  • 这似乎是一个有效的解决方案,虽然你知道我在哪里可以找到数学证明吗?我看到了类似的答案stackoverflow.com/a/48089/926907,但从 cmets 对我来说,这是否是正确的方法并不完全清楚。
  • 另外,我想应该是p &lt;= n / d。对于n=1, d=1, p=1,它不会选择单个元素。
  • 我似乎找不到证明,但它是一个相当简单的期望值应用。对于第一个元素,显然是 100/1000。对于第二种算法,它是 (100/10000)(99/9999)(您选择第一个元素的概率乘以选择第二个元素的概率)加上 (9900/10000)(100/9999)(您 的概率不要选择第一次的概率是选择第二次的概率),这应该简化为 100/10000。类似(但越来越复杂)的数学适用于其余元素。
  • 该方法在 Knuth Vol 2 2nd Ed, Algorithm 3.4.2 S 中,但不幸的是证明在练习中(呻吟!)
【解决方案2】:

对集合进行打乱意味着其中有一些已定义的元素顺序,因此可以对元素进行重新排序。 HashSet 不是有序集合,因为内部没有元素的顺序(或者更确切地说,排序的细节不会向用户公开)。因此,在实现方面,将HashSet 洗牌没有多大意义。

您可以做的是将set 中的所有元素添加到ArrayList,随机播放并获得结果。

List<String> usersList = new ArrayList<String>(users);
Collections.shuffle(usersList);
// get 100 elements out of the list

【讨论】:

  • 所以你是说我应该在集合之外创建一个列表,然后这样做?
  • @user1950349 chepner 给出的答案似乎产生了正确的结果。如果用户数量很少,我建议将设置转换为List。否则,请考虑 chepner 的解决方案。
【解决方案3】:

java.lang.HashSet 有一个顺序,所以你不能打乱集合。如果您必须使用 Sets,您可能会遍历 Set 并在随机位置停止。

伪代码:

Set randomUsers = new HashSet<String>();
Random r = new Random();
Iterator it = users.iterator(); 
numUsersNeeded = 100;
numUsersLeft = users.size();
while (it.hasNext() && randomUsers.size() < 100) {
  String user = it.next();
  double prop = (double)numUsersNeeded / numUsersLeft;
  --numUsersLeft;
  if (prop > r.nextDouble() && randomUsers.add(user)) { 
    --numUsersNeeded;
  }
}

您可能会重复此操作,因为无法保证您获取 100 个元素。

如果内存没有问题,您可以创建一个数组并选择 100 个随机元素:

伪代码二:

Object userArray[] = user.toArray();
Set<String> randoms = new HashSet<String>();
while(randoms.size() != 100) {
  int randomUser = userArray[new Random().nexInt(10000)];
  randoms.add(randomUser);
}

【讨论】:

  • 那不会是均匀分布 - 最后一个元素被选中的可能性较小
  • 你是对的。感谢您指出了这一点。我调整了第一个代码以使第一个元素具有较低的选择属性。据我所知,我们现在应该有一个均匀的分布。
  • 您的第二个伪代码也不正确 - 它可能会连续多次从数组中选择相同的元素。这将导致输出集中的元素少于 100 个。
猜你喜欢
  • 2015-08-09
  • 1970-01-01
  • 1970-01-01
  • 2014-09-23
  • 2012-02-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
相关资源
最近更新 更多