【发布时间】:2010-11-06 13:39:38
【问题描述】:
我有一个字典,其中每个键都有一个可变长度的列表,例如:
d = {
'a': [1, 3, 2],
'b': [6],
'c': [0, 0]
}
有没有一种干净的方法来获取随机字典键,按其值的长度加权?
random.choice(d.keys()) 将平等地加权键,但在上述情况下,我希望 'a' 大约有一半的时间返回。
【问题讨论】:
标签: python random dictionary
我有一个字典,其中每个键都有一个可变长度的列表,例如:
d = {
'a': [1, 3, 2],
'b': [6],
'c': [0, 0]
}
有没有一种干净的方法来获取随机字典键,按其值的长度加权?
random.choice(d.keys()) 将平等地加权键,但在上述情况下,我希望 'a' 大约有一半的时间返回。
【问题讨论】:
标签: python random dictionary
对于 Python 3.6+ 需要提及 random.choices:
import random
raffle_dict = {"Person 1": [1,2], "Person 2": [1]}
random.choices(list(raffle_dict.keys()), [len(w[1]) for w in raffle_dict.items()], k=1)[0]
random.choices 返回一个样本列表,所以 k=1 如果您只需要一个,我们将获取列表中的第一项。如果您的字典已经有权重,只需去掉 len 或更好:
raffle_dict = {"Person 1": 1, "Person 2": 10}
random.choices(list(raffle_dict.keys()), raffle_dict.values(), k=1)[0]
【讨论】:
import numpy as np
my_dict = {
"one": 5,
"two": 1,
"three": 25,
"four": 14
}
probs = []
elements = [my_dict[x] for x in my_dict.keys()]
total = sum(elements)
probs[:] = [x / total for x in elements]
r = np.random.choice(len(my_dict), p=probs)
print(list(my_dict.values())[r])
# 25
【讨论】:
我修改了其他一些答案来提出这个问题。它的可配置性更高一些。它需要 2 个参数、一个列表和一个 lambda 函数来告诉它如何生成密钥。
def select_weighted(lst, weight):
""" Usage: select_weighted([0,1,10], weight=lambda x: x) """
thesum = sum([weight(x) for x in lst])
if thesum == 0:
return random.choice(lst)
offset = random.randint(0, thesum - 1)
for k in lst:
v = weight(k)
if offset < v:
return k
offset -= v
多亏了这个的基本代码。
【讨论】:
我会这样说:
random.choice("".join([k * len(d[k]) for k in d]))
这清楚地表明,d 中的每个 k 获得的机会与其值的长度一样多。当然,它依赖于长度为 1 的字典键是字符....
很久以后:
table = "".join([key * len(value) for key, value in d.iteritems()])
random.choice(table)
【讨论】:
无需构造一个新的、可能带有重复值的大列表:
def select_weighted(d):
offset = random.randint(0, sum(d.itervalues())-1)
for k, v in d.iteritems():
if offset < v:
return k
offset -= v
【讨论】:
itervalues() 更改为 values(),并将 iteritems() 更改为 items()。
这里有一些代码基于我之前为probability distribution in python 给出的答案,但使用长度来设置权重。它使用迭代马尔可夫链,因此它不需要知道所有权重的总和是多少。目前它计算最大长度,但如果太慢,只需更改
self._maxw = 1
到
self._maxw = max lenght
并删除
for k in self._odata:
if len(self._odata[k])> self._maxw:
self._maxw=len(self._odata[k])
这里是代码。
import random
class RandomDict:
"""
The weight is the length of each object in the dict.
"""
def __init__(self,odict,n=0):
self._odata = odict
self._keys = list(odict.keys())
self._maxw = 1 # to increase speed set me to max length
self._len=len(odict)
if n==0:
self._n=self._len
else:
self._n=n
# to increase speed set above max value and comment out next 3 lines
for k in self._odata:
if len(self._odata[k])> self._maxw:
self._maxw=len(self._odata[k])
def __iter__(self):
return self.next()
def next(self):
while (self._len > 0) and (self._n>0):
self._n -= 1
for i in range(100):
k=random.choice(self._keys)
rx=random.uniform(0,self._maxw)
if rx <= len(self._odata[k]): # test to see if that is the value we want
break
# if you do not find one after 100 tries then just get a random one
yield k
def GetRdnKey(self):
for i in range(100):
k=random.choice(self._keys)
rx=random.uniform(0,self._maxw)
if rx <= len(self._odata[k]): # test to see if that is the value we want
break
# if you do not find one after 100 tries then just get a random one
return k
#test code
d = {
'a': [1, 3, 2],
'b': [6],
'c': [0, 0]
}
rd=RandomDict(d)
dc = {
'a': 0,
'b': 0,
'c': 0
}
for i in range(100000):
k=rd.GetRdnKey()
dc[k]+=1
print("Key count=",dc)
#iterate over the objects
dc = {
'a': 0,
'b': 0,
'c': 0
}
for k in RandomDict(d,100000):
dc[k]+=1
print("Key count=",dc)
测试结果
Key count= {'a': 50181, 'c': 33363, 'b': 16456}
Key count= {'a': 50080, 'c': 33411, 'b': 16509}
【讨论】:
你总是知道字典中值的总数吗?如果是这样,这可能很容易通过以下算法实现,只要您想从有序列表中概率性地选择某些项目,就可以使用该算法:
此算法的优点是不必生成任何新列表,如果您的字典很大,这一点很重要。您的程序只需为 K 键上的循环支付费用以计算总数,在键上的另一个循环平均会在中途结束,以及生成介于 0 和 1 之间的随机数的成本。生成这样的随机数是编程中非常常见的应用程序,因此大多数语言都可以快速实现此类功能。在 Python 中,random number generator 是 Mersenne Twister algorithm 的 C 实现,应该非常快。此外,文档声称此实现是线程安全的。
这是代码。如果您想使用更多 Pythonic 功能,我相信您可以清理它:
#!/usr/bin/python
import random
def select_weighted( d ):
# calculate total
total = 0
for key in d:
total = total + len(d[key])
accept_prob = float( 1.0 / total )
# pick a weighted value from d
n_seen = 0
for key in d:
current_key = key
for val in d[key]:
dice_roll = random.random()
accept_prob = float( 1.0 / ( total - n_seen ) )
n_seen = n_seen + 1
if dice_roll <= accept_prob:
return current_key
dict = {
'a': [1, 3, 2],
'b': [6],
'c': [0, 0]
}
counts = {}
for key in dict:
counts[key] = 0
for s in range(1,100000):
k = select_weighted(dict)
counts[k] = counts[k] + 1
print counts
运行 100 次后,我得到了这个次数的选择键:
{'a': 49801, 'c': 33548, 'b': 16650}
这些与您的预期值相当接近:
{'a': 0.5, 'c': 0.33333333333333331, 'b': 0.16666666666666666}
编辑:Miles 指出了我最初的实现中的一个严重错误,该错误已得到纠正。很抱歉!
【讨论】:
鉴于您的 dict 适合内存, random.choice 方法应该是合理的。但假设不是这样,下一个技术是使用增加权重的列表,并使用 bisect 找到随机选择的权重。
>>> import random, bisect
>>> items, total = [], 0
>>> for key, value in d.items():
total += len(value)
items.append((total, key))
>>> items[bisect.bisect_left(items, (random.randint(1, total),))][1]
'a'
>>> items[bisect.bisect_left(items, (random.randint(1, total),))][1]
'c'
【讨论】:
制作一个列表,其中每个键的重复次数等于其值的长度。在您的示例中:['a', 'a', 'a', 'b', 'c', 'c']。然后使用random.choice()。
编辑:或者,不那么优雅但更有效,试试这个:取字典中所有值的长度之和,S(你可以缓存和无效这个值,或者在你编辑时保持它是最新的字典,具体取决于您预期的确切使用模式)。生成一个从 0 到 S 的随机数,并通过字典键进行线性搜索以找到您的随机数所在的范围。
我认为这是您在不更改或添加数据表示的情况下可以做的最好的事情。
【讨论】:
这可行:
random.choice([k for k in d for x in d[k]])
【讨论】:
random.choice([key for key, values in d.iteritems() for x in values])。