Вагова версія random.choice


245

Мені потрібно було написати зважену версію 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 має бути одновимірним"), тому в цьому випадку можна попросити numpy вибрати індекс у списку, тобто len(list_of_candidates), а потім зробитиlist_of_candidates[draw]
xjcl

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 сек, де як random.choices займає 0,0416 сек, приблизно в 8 разів швидше.
Коди Антона

@AntonCodes Цей приклад - вишневий. numpy матиме деякий постійний наклад, який random.choicesцього не зробить, тому, звичайно, це повільніше в мінливому списку з 8 предметів, і якщо ви вибираєте 10k разів з такого списку, ви маєте рацію. Але для випадків, коли список більший (залежно від того, як ви тестуєте, я бачу точки перерви між 100-300 елементами), np.random.choiceпочинає перевершувати random.choicesдосить широкий проміжок. Наприклад, включаючи крок нормалізації разом з нумером дзвінка, я отримую майже 4-кратне прискорення random.choicesдля списку елементів 10 к.
ggorlen

Це має бути нова відповідь на основі покращення продуктивності, про яке повідомили @AntonCodes.
Wayne Workman

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
збережіть змінну, видаляючи upto та просто зменшуючи r на вагу кожного разу. Тоді порівнянняif r < 0
JnBrymn

@JnBrymn Вам потрібно перевірити r <= 0. Розглянемо вхідний набір з 1 предмета та ролик 1,0. Твердження тоді не вдасться. Я виправив цю помилку у відповіді.
moooeeeep

1
@Sardathrion ви можете використовувати прагму, щоб позначити цикл for як частковий:# pragma: no branch
Ned Batchelder

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

індекс кортежу поза діапазоном, якщо випадковий () трапиться до 1,0
Джон Вун

10
Це все ще працює O(n)через обчислення кумулятивного розподілу.
Лев Левицький

6
Це рішення краще в тому випадку, коли для одного і того ж набору варіантів потрібно кілька дзвінків на weight__ice. У такому випадку ви можете створити сукупну суму один раз і здійснити двійковий пошук під час кожного виклику.
Амос

1
@JonVaughan random() не може повернути 1.0. За документи він повертає результат у напіввідкритому інтервалі [0.0, 1.0), тобто, він може повернути рівно 0,0, але не може повернути рівно 1,0. Найбільше значення, яке воно може повернути, - 0,99999999999999988897769753748434595763683319091796875 (яке 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 мільйонами предметів).
Райан

@Ryan Дійсно. Він також не працює для не цілих ваг, що є іншим реалістичним сценарієм (наприклад, якщо у вас ваги виражені як ймовірності вибору).
Марк Амері

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; наприклад, якщо тоді буде weightsстановити лише 0,9999999999999998, менше, ніж найбільше можливе повернене значення (яке становить 0,9999999999999999). Тоді ніколи не буде задоволений. [1,1,1,1,1,1,1]probabilitiesrandom.randomchoice <= 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

Ось ще одна версія Weightte_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

Про це читає лекція Себастьяна Thurn у безкоштовному курсі AI для робототехніки Udacity. В основному він робить круговий масив індексованих ваг за допомогою оператора мод %, встановлює змінну бета до 0, випадковим чином вибирає індекс, для циклів через N, де N - кількість індексів, а в бета - спочатку з кроком бета за формулою:

бета = бета + рівномірний зразок від {0 ... 2 * Вага_макс}

а потім вкладено в цикл for, певний цикл внизу:

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

Використання нуме

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.