Combining Select k random elements from a list whose elements have weights with How can I make a random selection from an inversely-weighted list? and applied to your dictionary:
import random
from operator import mul
class Node:
__slots__ = ['w', 'v', 'tw']
def __init__(self, w, v, tw):
self.w, self.v, self.tw = w, v, tw
def rws_heap(items):
h = [None]
for w, v in items:
h.append(Node(w, v, w))
for i in range(len(h) - 1, 1, -1):
h[i>>1].tw += h[i].tw
return h
def rws_heap_pop(h):
gas, i = h[1].tw * random.random(), 1
while gas > h[i].w:
gas -= h[i].w
i <<= 1
if gas > h[i].tw:
gas -= h[i].tw
i += 1
w, v = h[i].w, h[i].v
h[i].w = 0
while i:
h[i].tw -= w
i >>= 1
return v
def random_weighted_sample_no_replacement(items, n):
heap = rws_heap(items)
for i in range(n):
yield rws_heap_pop(heap)
def random_weighted_sample_no_replacements_inverse_weights(mapping, n):
keys, values = zip(*mapping.items())
total = reduce(mul, (v + 1 for v in values))
weights = (total / (v + 1) for v in values)
heap = rws_heap(zip(weights, keys))
for i in xrange(n):
yield rws_heap_pop(heap)
I condensed Jason's Python implementation a little, and inverted your weights by using multiplication (shifting all weights up by 1 to allow for the division trick).
Applying this to your dictionary:
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[9, 11, 8]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[8, 6, 9]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[4, 8, 5]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[4, 10, 11]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[4, 9, 10]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[5, 10, 8]
>>> list(random_weighted_sample_no_replacements_inverse_weights(d, 3))
[6, 4, 5]
where 8
and 5
show up more often than 7
and 11
.