генерировать случайный взвешенный строковый файл в python

я пытаюсь создать строку из символов ['A','B','C','D','E'] длиной 3900, и каждый символ должен иметь вероятность: {'A': 0.1, 'B': 0.3, 'C': 0.3, 'D': 0.1, 'E': 0.2 } в этой строке я написал следующий код:

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]
string_ = ''
for i in range(0,3900):
    string_ = string_ + weighted_choice([("A",10), ("B",30), ("C",30),("D",10),("E",20)])

with open("rand_file","w") as f:
        f.write(string_)

но он не генерирует строку (файл) на основе вероятностей. он генерирует с такими вероятностями:

C 0.2500264583 
B 0.2499284457 
E 0.1666428313 
D 0.0833782424 
A 0.0833758065 

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

любая помощь, пожалуйста, чтобы решить эту проблему?


person anon    schedule 21.12.2016    source источник


Ответы (3)


Если вы просто используете список ['A','B','B','B','C','C','C','D','E','E'] и случайным образом выбираете из него элемент, вы можете полностью избавиться от всего этого взвешивания в своем коде, и взвешивание будет встроенным.

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

from random import random, seed

def choice(lst):
    return lst[int(random() * len(lst))];

seed()

(a, b, c, d, e, t) = (0, 0, 0, 0, 0, 0)

for i in range(1000):
    x = choice('ABBBCCCDEE')
    if (x == 'A'): a += 1
    if (x == 'B'): b += 1
    if (x == 'C'): c += 1
    if (x == 'D'): d += 1
    if (x == 'E'): e += 1
    t += 1

print ("a =", a, "which is", a * 100 / t, "%")
print ("b =", b, "which is", b * 100 / t, "%")
print ("c =", c, "which is", c * 100 / t, "%")
print ("d =", d, "which is", d * 100 / t, "%")
print ("e =", e, "which is", e * 100 / t, "%")

с выходным соответствием (примерно) желаемому распределению:

a = 101 which is 10.1 %
b = 297 which is 29.7 %
c = 299 which is 29.9 %
d = 102 which is 10.2 %
e = 201 which is 20.1 %

Теперь это, очевидно, будет раздражать, если ваш дистрибутив 99,9% A и 0,1% B (это будет довольно длинная строка, переданная в choice), но этого должно быть достаточно для вашего дистрибутива.

person paxdiablo    schedule 21.12.2016
comment
Это, вероятно, достаточно хорошо в большинстве случаев. Хотя и не в худшем случае A:0.50000000001, B:0.49999999999 - person Marijn van Vliet; 21.12.2016

Вы можете сгенерировать все буквы в соответствии с весовыми коэффициентами, затем случайным образом перемешать их и, наконец, соединить. Что-то типа:

from random import shuffle
N = 3900 # the string length
doc = {'A':0.1, 'B':0.3, 'C':0.3, 'D':0.1, 'E':0.2 } #weights
letters = []
for key in doc.keys():
    m = int(doc[key] * N) #generate correct number of letter
    letters.append(list(key * m))

letters = [item for sublist in letters for item in sublist] # flatten the list
shuffle(letters) # shuffle all letters randomly
result = ''.join(letters) # join all letter to make one string

print(len(result))
# 3900
person Mahdi    schedule 21.12.2016
comment
m = int(doc[key] * N) не гарантирует, что в конце будет len(letters) == N, так как int() всегда будет округляться в меньшую сторону. - person Marijn van Vliet; 21.12.2016

на самом деле это то же самое, что и решение paxdiablo, за исключением немного более общего (для вашего простого примера его решение лучше. +1):

import random

choice = [("A",10), ("B",30), ("C",30),("D",10),("E",20)]
choose_from = ''.join(x * letter for letter, x in choice)

print(choose_from)
#  AAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDetc...

print(random.choice(choose_from))
person hiro protagonist    schedule 21.12.2016