4
choices = [a,a,a,a,b,b,c]
random.choice(choices)

As you can see a is most likely to be chosen but is there a better/shorter way of doing this?

Glitchd
  • 361
  • 2
  • 12

2 Answers2

5

If you use choices, not choice, you can specify a weight for each element.

random.choices([a,b,c], [4,2,1])

The second argument are the relative weights for each element in the first argument. For example, in the following you can see that a was chosen roughly twice as often as b and roughly four times as often as c.

>>> import collections, random
>>> collections.Counter(random.choices('abc', [4,2,1], k=100))
Counter({'a': 58, 'b': 25, 'c': 17})
chepner
  • 497,756
  • 71
  • 530
  • 681
  • How is this more random if you're defining the weights? – def_init_ Feb 24 '19 at 02:07
  • 1
    `random.choice('abc')` would have a 1/3 chance of returning `a`, `b`, or `c`. `random.choices('abc', [4,2,1])` will choose `a` 4 out of 7 times, `b` 2 out of 7 times, and `c` 1 out of 7 times. It's still random, but it's not a uniform distribution. – chepner Feb 24 '19 at 02:09
  • @darshvader because one is more likely to be chosen – Glitchd Feb 24 '19 at 02:09
  • @chepner ahhhh ok, very nice explanation. For some reason I was forgetting that even randomly, they'll still align uniformly. – def_init_ Feb 24 '19 at 02:15
  • @chepner which has the same function as the way I originally did it? – Glitchd Feb 24 '19 at 02:16
0

If you are not using Python 3.6+ (support for weights in random.choices), you can construct a population:

import random
a, b, c = 'abc'

weighted = [(a, 4), (b, 2), (c, 1)]

population = [x for x, weight in weighted for _ in range(weight)]
random.choice(population)

If you are worried about performance for large weight values, you can use itertools.accumulate and bisect:

import bisect
import itertools
import random

choices = ['a', 'b', 'c']
weights = [4, 2, 1]
cumulative_weights = list(itertools.accumulate(weights))

print(choices[bisect.bisect(cumulative_weights, random.random() * cumulative_weights[-1])])
iz_
  • 15,923
  • 3
  • 25
  • 40