随机Python字典键,按值加权

时间:2009-06-29 00:46:06

标签: python random dictionary

我有一个字典,其中每个键都有一个可变长度列表,例如:

d = {
 'a': [1, 3, 2],
 'b': [6],
 'c': [0, 0]
}

是否有一种干净的方法来获取随机字典密钥,按其值的长度加权? random.choice(d.keys())会对密钥进行相同的加权,但在上面的情况下,我希望'a'大约有一半的时间返回。

10 个答案:

答案 0 :(得分:32)

这样可行:

random.choice([k for k in d for x in d[k]])

答案 1 :(得分:17)

您是否始终知道字典中的值总数?如果是这样,使用以下算法可能很容易,只要您想从有序列表中对某些项进行概率选择,就可以使用该算法:

  1. 迭代您的密钥列表。
  2. 生成介于0和1之间的均匀分布的随机值(又名“掷骰子”)。
  3. 假设此密钥具有与之关联的N_VALS值,并且整个字典中存在TOTAL_VALS总值,请以概率N_VALS / N_REMAINING接受此密钥,其中N_REMAINING是列表中剩余的项目数。
  4. 此算法的优点是无需生成任何新列表,这在您的字典较大时非常重要。你的程序只需支付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}
    

    编辑:迈尔斯在我的原始实现中指出了一个严重错误,该错误已经得到纠正。对不起!

答案 2 :(得分:8)

不构建具有重复值的新的可能大的列表:

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

答案 3 :(得分:6)

鉴于你的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'

答案 4 :(得分:3)

制作一个列表,其中每个键重复的次数等于其值的长度。在您的示例中:['a', 'a', 'a', 'b', 'c', 'c']。然后使用random.choice()

编辑:或者,不那么优雅但效率更高,请尝试这样:获取字典中所有值的长度之和S(您可以缓存并使此值无效,或将其保持为最新状态您编辑字典,具体取决于您预期的确切使用模式)。生成一个从0到S的随机数,并通过字典键进行线性搜索,找到随机数落入的范围。

我认为,如果不更改或添加数据表示,这是最好的。

答案 5 :(得分:1)

以下是一些基于我之前为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}

答案 6 :(得分:1)

我会这样说:

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)

答案 7 :(得分:0)

我修改了其他一些答案来提出这个问题。它更具可配置性。它需要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

感谢sth的基本代码。

答案 8 :(得分: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

答案 9 :(得分:0)

对于 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]

另见replthis question