Взвешенная версия random.choice


246

Мне нужно было написать взвешенную версию random.choice (каждый элемент в списке имеет различную вероятность выбора). Вот что я придумал:

def weightedChoice(choices):
    """Like random.choice, but each element can have a different chance of
    being selected.

    choices can be any iterable containing iterables with two items each.
    Technically, they can have more than two items, the rest will just be
    ignored.  The first item is the thing being chosen, the second item is
    its weight.  The weights can be any numeric values, what matters is the
    relative differences between them.
    """
    space = {}
    current = 0
    for choice, weight in choices:
        if weight > 0:
            space[current] = choice
            current += weight
    rand = random.uniform(0, current)
    for key in sorted(space.keys() + [current]):
        if rand < key:
            return choice
        choice = space[key]
    return None

Эта функция кажется мне слишком сложной и безобразной. Я надеюсь, что все здесь могут предложить некоторые предложения по улучшению или альтернативные способы сделать это. Эффективность не так важна для меня, как чистота кода и удобочитаемость.

Ответы:


297

Начиная с версии 1.7.0, в NumPy есть choiceфункция, которая поддерживает распределение вероятностей.

from numpy.random import choice
draw = choice(list_of_candidates, number_of_items_to_pick,
              p=probability_distribution)

Обратите внимание, что probability_distributionэто последовательность в том же порядке list_of_candidates. Вы также можете использовать ключевое слово, replace=Falseчтобы изменить поведение, чтобы нарисованные элементы не заменялись.


11
По моим тестам, это на порядок медленнее, чем random.choicesдля отдельных вызовов. Если вам нужно много случайных результатов, очень важно выбрать их все сразу, настроив number_of_items_to_pick. Если вы это сделаете, это будет на порядок быстрее.
jpmc26

2
Это не работает с кортежами и т. Д. («ValueError: a должно быть 1-мерным»), поэтому в этом случае можно попросить numpy выбрать индекс в список, т. len(list_of_candidates)list_of_candidates[draw]
Е.

218

Начиная с Python 3.6 есть метод choicesиз randomмодуля.

Python 3.6.1 (v3.6.1:69c0db5050, Mar 21 2017, 01:21:04)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import random

In [2]: random.choices(
...:     population=[['a','b'], ['b','a'], ['c','b']],
...:     weights=[0.2, 0.2, 0.6],
...:     k=10
...: )

Out[2]:
[['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['b', 'a'],
 ['c', 'b'],
 ['c', 'b']]

Обратите внимание, что random.choicesбудет образец с заменой , в соответствии с документами :

Возвращает kразмерный список элементов, выбранных из популяции с заменой.

Если вам нужно выполнить выборку без замены, то, как гласит блестящий ответ @ ronan-paixão , вы можете использовать numpy.choice, чей replaceаргумент контролирует такое поведение.


4
Это намного быстрее, чем numpy.random.choice. При выборке из списка из 8 взвешенных элементов 10000 раз numpy.random.choice занял 0,3286 с, тогда как случайный выбор занял 0,0416 с, примерно в 8 раз быстрее.
Антон Коды

@AntonCodes Этот пример выбран вишней. У numpy будут постоянные накладные расходы, которых random.choicesнет, поэтому, конечно, он медленнее в минимальном списке из 8 элементов, и если вы выбираете 10k раз из такого списка, вы правы. Но для случаев, когда список больше (в зависимости от того, как вы тестируете, я вижу точки разрыва между 100-300 элементами), np.random.choiceначинает выигрывать random.choicesот довольно большого разрыва. Например, включая шаг нормализации вместе с вызовом numpy, я получаю ускорение почти в 4 раза random.choicesдля списка из 10 тыс. Элементов.
Ггорлен

Это должен быть новый ответ, основанный на улучшении производительности, о котором сообщил @AntonCodes.
Уэйн Уоркман

132
def weighted_choice(choices):
   total = sum(w for c, w in choices)
   r = random.uniform(0, total)
   upto = 0
   for c, w in choices:
      if upto + w >= r:
         return c
      upto += w
   assert False, "Shouldn't get here"

10
Вы можете отказаться от операции и сэкономить время, изменив операторы внутри цикла for:upto +=w; if upto > r
knite

5
сохранить переменную, удалив до и просто уменьшая r на вес каждый раз. Сравнение тогдаif r < 0
JnBrymn

@JnBrymn Вы должны проверить r <= 0. Рассмотрим входной набор из 1 предметов и бросок 1,0. Утверждение потерпит неудачу тогда. Я исправил эту ошибку в ответе.
moooeeeep

1
@Sardathrion, вы можете использовать прагму, чтобы пометить цикл for как частичный:# pragma: no branch
Нед

1
@ mLstudent33 Я не использую Udacity.
Антон Коды

70
  1. Расставьте веса в совокупное распределение.
  2. Используйте random.random (), чтобы выбрать случайное число с плавающей точкой 0.0 <= x < total.
  3. Найдите дистрибутив, используя bisect.bisect, как показано в примере по адресу http://docs.python.org/dev/library/bisect.html#other-examples .
from random import random
from bisect import bisect

def weighted_choice(choices):
    values, weights = zip(*choices)
    total = 0
    cum_weights = []
    for w in weights:
        total += w
        cum_weights.append(total)
    x = random() * total
    i = bisect(cum_weights, x)
    return values[i]

>>> weighted_choice([("WHITE",90), ("RED",8), ("GREEN",2)])
'WHITE'

Если вам нужно сделать более одного выбора, разделите его на две функции: одну для построения совокупных весов, а другую для деления пополам на случайную точку.


5
Это более эффективно, чем ответ Неда. По сути, вместо линейного (O (n)) поиска по выбору, он выполняет бинарный поиск (O (log n)). +1!
NHDaly

Индекс кортежа выходит за пределы диапазона, если random () возвращает 1.0
Джон Воган,

10
Это все еще работает O(n)из-за совокупного расчета распределения.
Лев Левицкий

6
Это решение лучше в случае, когда для одного и того же выбора необходимо несколько вызовов weighted_choice. В этом случае вы можете создать накопленную сумму один раз и выполнить бинарный поиск по каждому вызову.
Амос

1
@JonVaughan random() не может вернуть 1.0. Согласно документам, он возвращает результат в полуоткрытом интервале [0.0, 1.0), то есть он может вернуть ровно 0,0, но не может вернуть ровно 1,0. Наибольшее значение, которое он может вернуть, составляет 0,999999999999999988897769753748434595763683319091796875 (которое Python печатает как 0,99999999999999999 и является самым большим 64-разрядным числом с плавающей запятой меньше 1).
Марк Амери

21

Если вы не возражаете против использования numpy, вы можете использовать numpy.random.choice .

Например:

import numpy

items  = [["item1", 0.2], ["item2", 0.3], ["item3", 0.45], ["item4", 0.05]
elems = [i[0] for i in items]
probs = [i[1] for i in items]

trials = 1000
results = [0] * len(items)
for i in range(trials):
    res = numpy.random.choice(items, p=probs)  #This is where the item is selected!
    results[items.index(res)] += 1
results = [r / float(trials) for r in results]
print "item\texpected\tactual"
for i in range(len(probs)):
    print "%s\t%0.4f\t%0.4f" % (items[i], probs[i], results[i])

Если вы знаете, сколько выборов нужно сделать заранее, вы можете сделать это без цикла, подобного следующему:

numpy.random.choice(items, trials, p=probs)

15

Грубо, но может быть достаточно

import random
weighted_choice = lambda s : random.choice(sum(([v]*wt for v,wt in s),[]))

Это работает?

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

# initialize tally dict
tally = dict.fromkeys(choices, 0)

# tally up 1000 weighted choices
for i in xrange(1000):
    tally[weighted_choice(choices)] += 1

print tally.items()

Печать:

[('WHITE', 904), ('GREEN', 22), ('RED', 74)]

Предполагается, что все веса являются целыми числами. Они не должны добавлять до 100, я просто сделал это, чтобы результаты теста было легче интерпретировать. (Если веса являются числами с плавающей запятой, умножьте их все на 10 несколько раз, пока все веса>> 1.)

weights = [.6, .2, .001, .199]
while any(w < 1.0 for w in weights):
    weights = [w*10 for w in weights]
weights = map(int, weights)

1
Хорошо, я не уверен, что могу предположить, что все веса являются целыми числами.
Колин

1
Похоже, что ваши объекты будут дублированы в этом примере. Это было бы неэффективно (как и функция преобразования весов в целые числа). Тем не менее, это решение является хорошим однострочником, если целые веса малы.
wei2912

Примитивы будут дублироваться, но объекты будут дублироваться только ссылками, а не сами объекты. (вот почему вы не можете создать список списков, используя [[]]*10- все элементы внешнего списка указывают на один и тот же список.
PaulMcG

@PaulMcG Нет; ничего, кроме ссылок, никогда не будет продублировано. Система типов Python не имеет понятия примитивов. Вы можете подтвердить, что даже если, например, intвы по-прежнему получаете много ссылок на один и тот же объект, выполняя что-то подобное, вы [id(x) for x in ([99**99] * 100)]наблюдаете, что idпри каждом вызове возвращается один и тот же адрес памяти.
Марк Амери

14

Если у вас есть взвешенный словарь вместо списка, вы можете написать это

items = { "a": 10, "b": 5, "c": 1 } 
random.choice([k for k in items for dummy in range(items[k])])

Обратите внимание, что [k for k in items for dummy in range(items[k])]производит этот список['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'c', 'b', 'b', 'b', 'b', 'b']


10
Это работает для небольших значений общей численности населения, но не для больших наборов данных (например, население США по штатам в конечном итоге создаст рабочий список с 300 миллионами элементов в нем).
Райан

@ Райан Действительно. Это также не работает для нецелых весов, которые являются другим реалистичным сценарием (например, если у вас есть весовые коэффициенты, выраженные как вероятности выбора).
Марк Амери

12

Начиная с Python v3.6, random.choicesможет использоваться для возврата listэлементов заданного размера из заданной совокупности с необязательными весами.

random.choices(population, weights=None, *, cum_weights=None, k=1)

  • население : listсодержит уникальные наблюдения. (Если пусто, поднимает IndexError)

  • веса : точнее относительные веса, необходимые для выбора.

  • cum_weights : совокупные веса, необходимые для выбора.

  • k : размер ( len) объекта listдля вывода. (По умолчанию len()=1)


Несколько предостережений:

1) Используется взвешенная выборка с заменой, чтобы вытянутые элементы впоследствии были заменены. Значения в последовательности весов сами по себе не имеют значения, но их относительное соотношение имеет значение.

В отличие от того, np.random.choiceкоторый может принимать только вероятности в качестве весов, а также который должен обеспечивать суммирование индивидуальных вероятностей до 1 критерия, здесь нет таких правил. Пока они принадлежат числовым типам ( int/float/fractionкроме Decimalтипа), они все равно будут работать.

>>> import random
# weights being integers
>>> random.choices(["white", "green", "red"], [12, 12, 4], k=10)
['green', 'red', 'green', 'white', 'white', 'white', 'green', 'white', 'red', 'white']
# weights being floats
>>> random.choices(["white", "green", "red"], [.12, .12, .04], k=10)
['white', 'white', 'green', 'green', 'red', 'red', 'white', 'green', 'white', 'green']
# weights being fractions
>>> random.choices(["white", "green", "red"], [12/100, 12/100, 4/100], k=10)
['green', 'green', 'white', 'red', 'green', 'red', 'white', 'green', 'green', 'green']

2) Если ни веса, ни cum_weights не указаны, выборы делаются с равной вероятностью. Если указана последовательность весов , она должна быть той же длины, что и последовательность совокупности .

Задание весов и cum_weights повышает a TypeError.

>>> random.choices(["white", "green", "red"], k=10)
['white', 'white', 'green', 'red', 'red', 'red', 'white', 'white', 'white', 'green']

3) cum_weights обычно являются результатом itertools.accumulateфункции, которая действительно удобна в таких ситуациях.

Из документации связано:

Внутренне, относительные веса преобразуются в кумулятивные веса, прежде чем делать выбор, поэтому предоставление кумулятивных весов экономит работу.

Таким образом, либо поставка, weights=[12, 12, 4]либо cum_weights=[12, 24, 28]для нашего надуманного дела дает тот же результат, и последний кажется более быстрым / эффективным.


11

Вот версия, которая включена в стандартную библиотеку для Python 3.6:

import itertools as _itertools
import bisect as _bisect

class Random36(random.Random):
    "Show the code included in the Python 3.6 version of the Random class"

    def choices(self, population, weights=None, *, cum_weights=None, k=1):
        """Return a k sized list of population elements chosen with replacement.

        If the relative weights or cumulative weights are not specified,
        the selections are made with equal probability.

        """
        random = self.random
        if cum_weights is None:
            if weights is None:
                _int = int
                total = len(population)
                return [population[_int(random() * total)] for i in range(k)]
            cum_weights = list(_itertools.accumulate(weights))
        elif weights is not None:
            raise TypeError('Cannot specify both weights and cumulative weights')
        if len(cum_weights) != len(population):
            raise ValueError('The number of weights does not match the population')
        bisect = _bisect.bisect
        total = cum_weights[-1]
        return [population[bisect(cum_weights, random() * total)] for i in range(k)]

Источник: https://hg.python.org/cpython/file/tip/Lib/random.py#l340


2
import numpy as np
w=np.array([ 0.4,  0.8,  1.6,  0.8,  0.4])
np.random.choice(w, p=w/sum(w))

2

Я, вероятно, слишком поздно, чтобы внести что-то полезное, но вот простой, короткий и очень эффективный фрагмент:

def choose_index(probabilies):
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

Нет необходимости сортировать ваши вероятности или создавать вектор с помощью cmf, и он завершается, когда находит свой выбор. Память: O (1), время: O (N), со средним временем работы ~ N / 2.

Если у вас есть вес, просто добавьте одну строку:

def choose_index(weights):
    probabilities = weights / sum(weights)
    cmf = probabilies[0]
    choice = random.random()
    for k in xrange(len(probabilies)):
        if choice <= cmf:
            return k
        else:
            cmf += probabilies[k+1]

1
Несколько вещей не так с этим. Поверхностно, есть некоторые имена переменных typoed и нет никаких оснований с учетом использования над этим, скажем, np.random.choice. Но что еще более интересно, есть режим отказа, где это вызывает исключение. Выполнение probabilities = weights / sum(weights)не гарантирует, что probabilitiesсоставит 1; например, если weightsis, [1,1,1,1,1,1,1]то probabilitiesсумма будет только 0,99999999999999998, что меньше максимально возможного возвращаемого значения random.random(которое составляет 0,99999999999999999). Тогда choice <= cmfникогда не будешь доволен.
Марк Амери

2

Если ваш список взвешенных вариантов относительно статичен и вам требуется частая выборка, вы можете выполнить один O (N) -процесс предварительной обработки, а затем выполнить выбор в O (1), используя функции из этого связанного ответа .

# run only when `choices` changes.
preprocessed_data = prep(weight for _,weight in choices)

# O(1) selection
value = choices[sample(preprocessed_data)][0]

1

Я посмотрел указанную другую нить и нашел этот вариант в моем стиле кодирования, он возвращает индекс выбора для подсчета, но просто вернуть строку (закомментированная альтернатива возврата):

import random
import bisect

try:
    range = xrange
except:
    pass

def weighted_choice(choices):
    total, cumulative = 0, []
    for c,w in choices:
        total += w
        cumulative.append((total, c))
    r = random.uniform(0, total)
    # return index
    return bisect.bisect(cumulative, (r,))
    # return item string
    #return choices[bisect.bisect(cumulative, (r,))][0]

# define choices and relative weights
choices = [("WHITE",90), ("RED",8), ("GREEN",2)]

tally = [0 for item in choices]

n = 100000
# tally up n weighted choices
for i in range(n):
    tally[weighted_choice(choices)] += 1

print([t/sum(tally)*100 for t in tally])

1

Это зависит от того, сколько раз вы хотите попробовать дистрибутив.

Предположим, вы хотите попробовать распределение K раз. Тогда сложность времени, используемая np.random.choice()каждый раз, - это O(K(n + log(n)))когда nколичество элементов в распределении.

В моем случае мне нужно было выбрать одно и то же распределение несколько раз порядка 10 ^ 3, где n порядка 10 ^ 6. Я использовал приведенный ниже код, который предварительно вычисляет накопительное распределение и пробует его в O(log(n)). Общая сложность времени есть O(n+K*log(n)).

import numpy as np

n,k = 10**6,10**3

# Create dummy distribution
a = np.array([i+1 for i in range(n)])
p = np.array([1.0/n]*n)

cfd = p.cumsum()
for _ in range(k):
    x = np.random.uniform()
    idx = cfd.searchsorted(x, side='right')
    sampled_element = a[idx]

1

Если у вас есть Python 3, и вы боитесь устанавливать numpyили писать свои собственные циклы, вы можете сделать:

import itertools, bisect, random

def weighted_choice(choices):
   weights = list(zip(*choices))[1]
   return choices[bisect.bisect(list(itertools.accumulate(weights)),
                                random.uniform(0, sum(weights)))][0]

Потому что вы можете собрать все что угодно из пакета адаптеров! Хотя ... Я должен признать, что ответ Неда, хотя и немного длиннее, легче понять.


0

Общее решение:

import random
def weighted_choice(choices, weights):
    total = sum(weights)
    treshold = random.uniform(0, total)
    for k, weight in enumerate(weights):
        total -= weight
        if total < treshold:
            return choices[k]

0

Вот еще одна версия weighted_choice, которая использует numpy. Передайте вектор весов, и он вернет массив из 0, содержащий 1, указывающий, какой лот был выбран. По умолчанию в коде используется только одна раздача, но вы можете указать количество разыгранных розыгрышей, и будет возвращено количество разыгранных бинов.

Если вектор весовых коэффициентов не равен 1, он будет нормализован.

import numpy as np

def weighted_choice(weights, n=1):
    if np.sum(weights)!=1:
        weights = weights/np.sum(weights)

    draws = np.random.random_sample(size=n)

    weights = np.cumsum(weights)
    weights = np.insert(weights,0,0.0)

    counts = np.histogram(draws, bins=weights)
    return(counts[0])

0

Другой способ сделать это, предполагая, что у нас есть веса с тем же индексом, что и у элементов в массиве элементов.

import numpy as np
weights = [0.1, 0.3, 0.5] #weights for the item at index 0,1,2
# sum of weights should be <=1, you can also divide each weight by sum of all weights to standardise it to <=1 constraint.
trials = 1 #number of trials
num_item = 1 #number of items that can be picked in each trial
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# gives number of times an item was selected at a particular index
# this assumes selection with replacement
# one possible output
# selected_item_arr
# array([[0, 0, 1]])
# say if trials = 5, the the possible output could be 
# selected_item_arr
# array([[1, 0, 0],
#   [0, 0, 1],
#   [0, 0, 1],
#   [0, 1, 0],
#   [0, 0, 1]])

Теперь давайте предположим, что мы должны отобрать 3 элемента в 1 пробной версии. Вы можете предположить, что есть три шара R, G, B, присутствующие в большом количестве в соотношении их весов, заданных массивом весов, следующие результаты могут быть возможными:

num_item = 3
trials = 1
selected_item_arr = np.random.multinomial(num_item, weights, trials)
# selected_item_arr can give output like :
# array([[1, 0, 2]])

Вы также можете думать о количестве элементов, которые будут выбраны в качестве количества биномиальных / полиномиальных испытаний в наборе. Итак, вышеприведенный пример можно еще поработать как

num_binomial_trial = 5
weights = [0.1,0.9] #say an unfair coin weights for H/T
num_experiment_set = 1
selected_item_arr = np.random.multinomial(num_binomial_trial, weights, num_experiment_set)
# possible output
# selected_item_arr
# array([[1, 4]])
# i.e H came 1 time and T came 4 times in 5 binomial trials. And one set contains 5 binomial trails.

0

Об этом есть лекция Себастьяна Турна в бесплатном курсе AI для робототехники Udacity. По сути, он создает циклический массив индексированных весов с помощью оператора mod %, устанавливает переменную beta в 0, случайным образом выбирает индекс для циклов по N, где N - число индексов, а в цикле for сначала увеличивается бета по формуле:

бета = бета + единообразная выборка из {0 ... 2 * Weight_max}

и затем вложенный в цикл for, цикл while согласно ниже:

while w[index] < beta:
    beta = beta - w[index]
    index = index + 1

select p[index]

Затем перейдите к следующему индексу для повторной выборки на основе вероятностей (или нормированной вероятности в случае, представленном в курсе).

Ссылка на лекцию: https://classroom.udacity.com/courses/cs373/lessons/48704330/concepts/487480820923

Я вошел в Udacity со своей школьной учетной записью, поэтому, если ссылка не работает, это Урок 8, видео № 21 «Искусственного интеллекта для робототехники», где он читает лекции по фильтрам частиц.


-1

Одним из способов является рандомизация по сумме всех весов, а затем использование значений в качестве предельных точек для каждой переменной. Вот грубая реализация в качестве генератора.

def rand_weighted(weights):
    """
    Generator which uses the weights to generate a
    weighted random values
    """
    sum_weights = sum(weights.values())
    cum_weights = {}
    current_weight = 0
    for key, value in sorted(weights.iteritems()):
        current_weight += value
        cum_weights[key] = current_weight
    while True:
        sel = int(random.uniform(0, 1) * sum_weights)
        for key, value in sorted(cum_weights.iteritems()):
            if sel < value:
                break
        yield key

-1

Используя NumPy

def choice(items, weights):
    return items[np.argmin((np.cumsum(weights) / sum(weights)) < np.random.rand())]

NumPy np.random.choice, как уже упоминалось в принятом ответе, который был здесь с 2014 года, уже есть. Какой смысл кататься самостоятельно?
Марк Амери

-1

Мне нужно было сделать что-то вроде этого очень быстро, очень просто, от поиска идей я наконец-то создал этот шаблон. Идея состоит в том, чтобы получить взвешенные значения в форме JSON от API, который здесь моделируется диктом.

Затем переведите его в список, в котором каждое значение повторяется пропорционально его весу, и просто используйте random.choice, чтобы выбрать значение из списка.

Я попробовал запустить его с 10, 100 и 1000 итерациями. Распределение кажется довольно солидным.

def weighted_choice(weighted_dict):
    """Input example: dict(apples=60, oranges=30, pineapples=10)"""
    weight_list = []
    for key in weighted_dict.keys():
        weight_list += [key] * weighted_dict[key]
    return random.choice(weight_list)

-1

Мне не понравился синтаксис любого из них. Я действительно хотел просто указать, что это были за вещи и какой вес у каждого из них. Я понимаю, что мог бы использовать, random.choicesно вместо этого я быстро написал класс ниже.

import random, string
from numpy import cumsum

class randomChoiceWithProportions:
    '''
    Accepts a dictionary of choices as keys and weights as values. Example if you want a unfair dice:


    choiceWeightDic = {"1":0.16666666666666666, "2": 0.16666666666666666, "3": 0.16666666666666666
    , "4": 0.16666666666666666, "5": .06666666666666666, "6": 0.26666666666666666}
    dice = randomChoiceWithProportions(choiceWeightDic)

    samples = []
    for i in range(100000):
        samples.append(dice.sample())

    # Should be close to .26666
    samples.count("6")/len(samples)

    # Should be close to .16666
    samples.count("1")/len(samples)
    '''
    def __init__(self, choiceWeightDic):
        self.choiceWeightDic = choiceWeightDic
        weightSum = sum(self.choiceWeightDic.values())
        assert weightSum == 1, 'Weights sum to ' + str(weightSum) + ', not 1.'
        self.valWeightDict = self._compute_valWeights()

    def _compute_valWeights(self):
        valWeights = list(cumsum(list(self.choiceWeightDic.values())))
        valWeightDict = dict(zip(list(self.choiceWeightDic.keys()), valWeights))
        return valWeightDict

    def sample(self):
        num = random.uniform(0,1)
        for key, val in self.valWeightDict.items():
            if val >= num:
                return key

-1

Укажите random.choice () с предварительно взвешенным списком:

Решение и тест:

import random

options = ['a', 'b', 'c', 'd']
weights = [1, 2, 5, 2]

weighted_options = [[opt]*wgt for opt, wgt in zip(options, weights)]
weighted_options = [opt for sublist in weighted_options for opt in sublist]
print(weighted_options)

# test

counts = {c: 0 for c in options}
for x in range(10000):
    counts[random.choice(weighted_options)] += 1

for opt, wgt in zip(options, weights):
    wgt_r = counts[opt] / 10000 * sum(weights)
    print(opt, counts[opt], wgt, wgt_r)

Вывод:

['a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'd', 'd']
a 1025 1 1.025
b 1948 2 1.948
c 5019 5 5.019
d 2008 2 2.008
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.