【问题标题】:Set.pop() isn't random?Set.pop() 不是随机的?
【发布时间】:2014-01-09 10:11:36
【问题描述】:

python docs 中,“set.pop() 从 s 中删除并返回任意元素”。在生成一些随机数据来测试程序时,我注意到这个 pop() 函数的奇怪行为。这是我的代码(python 2.7.3):

testCases = 10
numberRange = 500

poppedValues = []
greaterPercentages = []

for i in range (testCases):
    s = Set()

    """ inserting 100 random values in the set, in the range [0, numberRange) """
    for j in range (100):
        s.add(random.randrange(numberRange)) 

    poppedValue = s.pop()
    greaterCount = 0

    """ counting how many numbers in the set are smaller then the popped value """
    for number in s:
        if poppedValue > number:
            greaterCount += 1

    poppedValues.append(poppedValue)
    greaterPercentages.append(float(greaterCount) / len(s) * 100)

for poppedValue in poppedValues:
    print poppedValue, '\t',

print

for percentage in greaterPercentages:
    print "{:2.2f}".format(percentage), '\t',

我在这里做的是,

  1. 在集合 s 中插入一些随机值,其中每个元素都在 [0, numberRange) 范围内
  2. 从集合中弹出一个元素(根据文档,它应该是随机的)
  3. 计算集合中有多少元素小于弹出的值

我预计弹出的值应该是随机的,并且集合中大约 50% 的数字将大于弹出的值。但似乎pop() 几乎总是返回集合中的最小数字。这是numberRange = 500 的结果。第一行表示弹出元素的值。第二行是小于弹出值的元素的百分比。

9   0   3   1   409     0   1   2   4   0   
0 % 0 % 0 % 0 % 87 %    0 % 0 % 0 % 0 % 0 %

我使用不同的 numberRange 值进行了此测试。似乎对于集合元素的较低值,pop() 几乎总是返回最低元素。但是对于更高的值,它会返回一个随机元素。对于numberRange = 1000,结果为:

518     3586    3594    4103    2560    3087    4095    3079    3076    1622    
7 %     72 %    73 %    84 %    54 %    51 %    79 %    63 %    67 %    32 %

我认为这是非常随机的。为什么会有这种奇怪的行为?我做错了吗?

编辑:感谢大家的回答和评论,看来“随意”,不能保证一定是“随机”。

【问题讨论】:

  • 不是随机的,是无序的。
  • 文档中的“任意”并不意味着“随机”,它们的意思是“不依赖任何特定值,实现细节可能会在没有警告的情况下更改”
  • 随机并不意味着它分布得很好,甚至是不可预测的。这意味着您不能依赖任何观察来判断未来是否正确。
  • 是的,恕我直言,random.choice(s) 不适用于集合。此外,random.choice 对于 dicts 来说完全是 BROKEN,see here for more details about that
  • @wim:同意,如果有人刚刚阅读了 random 文档并且不理解 Python/C++ 术语“序列”,那么 set 或 @987654337 并不明显@不是一个。我对“序列”、“可迭代”、“迭代器”等术语在 Python 文档的各个部分中被假定为词汇表这一事实并不强烈,但毫无疑问,它至少会吸引大多数人一次。另外,如果random.choice 在任意可迭代(在O(n) 时间和O(1) 内存中)以及在任何时间复杂度可行的集合和字典上工作,那就太好了。

标签: python random


【解决方案1】:

这是一个实现细节 - set 实现为 HashMap(类似于 dict,但没有值槽),set.pop 删除 HashMap 中的第一个条目,以及 ints 哈希值是相同的int。

结合起来,这意味着您的 set 按哈希值排序,实际上也按条目 modulo hashtable size 排序;在您的情况下,这应该接近自然排序,因为您只插入小范围内的数字 - 如果您从 randrange(10**10) 而不是 randrange(500) 获取随机数,您应该会看到不同的行为。此外,根据您的插入顺序,由于哈希冲突,您可能会从原始哈希顺序中获取一些值。

【讨论】:

  • 您的第二段不正确。仅当插入的值相对于设置大小较小时才成立。所有赌注都以较大的价值关闭。
  • @interjay 是的,你是对的,正在编辑 atm - 它当然是按哈希表大小的模数排序的;但由于 OP 是从一个小范围内插入数字,因此对于他的情况来说应该是正确的。
【解决方案2】:

当医生说:

从 s 中移除并返回任意元素;如果为空则引发 KeyError

这意味着行为没有定义,实现可以做任何可能的事情。在这种情况下,似乎实现的行为是删除最小值。就是这样。
事实上,set.pop() 是基于 HashMap 并删除它的第一个元素(这是较小的哈希码)。对于整数的set,它是最小的int

在 Python 的其他实现上可能会返回一个随机值或第一个推送的值……你不知道。

【讨论】:

  • set作为HashMap的实现和int的hash值导致的一个实现细节
猜你喜欢
  • 2012-05-13
  • 2012-10-04
  • 2011-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-09
  • 2014-10-07
相关资源
最近更新 更多