Рандомизация списка нулей и единиц с ограничениями - PullRequest
0 голосов
/ 03 марта 2020

В настоящее время я пытаюсь рандомизировать список из 0 и 1, который должен давать случайный порядок нулей и единиц со следующими ограничениями:

  1. 1/3 из элементов имеют быть равными 1 с (соответственно 2/3 равны 0)

  2. Должно быть не более двух единиц 1

  3. Не должно быть более четырех нулей подряд

Я работал над опцией, но это не совсем то, что мне нужно. Вот мой вариант:

for prevItem, nextItem in enumerate(WordV[: -1]):
            if nextItem  == WordV[prevItem+1] and WordV[prevItem+1] == WordV[prevItem+2] and nextItem ==1: 
                WordV[prevItem+2] = 0
            if nextItem  == WordV[prevItem+1] and WordV[prevItem+1] == WordV[prevItem+2] and WordV[prevItem+2] == WordV[prevItem+3] and WordV[prevItem+3] == WordV[prevItem+4] and nextItem == 0: 
                WordV[prevItem+2] = 1

# Check the number of ones & zeros
print(WordV)
ones= WordV.count(1)
zeros= WordV.count(0)
print(ones, zeros)

В настоящее время количество единиц и нулей не складывается в пропорции от 1/3 до 2/3, поскольку ограничения заменяют числа. Список WordV - это список, содержащий 24 единицы и 48 нулей, которые перемешиваются случайным образом (с random.shuffle (WordV)).

Существует ли более разумный (и более правильный) способ интеграции ограничений в код?

Ответы [ 3 ]

2 голосов
/ 03 марта 2020
import numpy as np

def consecutive(data, stepsize=0):
    return np.split(data, np.where(np.diff(data) != stepsize)[0]+1)

def check(list_to_check):
    groups = consecutive(list_to_check)
    for group in groups:
        if group[0] == 1 and group.size > 2:
            return True
        if group[0] == 0 and group.size > 4:
            return True

wordv = np.array([1]*24+[0]*48)


while check(wordv):
    np.random.shuffle(wordv)

wordv будет содержать что-то вроде:

array([0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1,
       0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1,
       0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0,
       0, 0, 1, 0, 1, 0])

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

[ins] In [32]: consecutive([1,1,1,0,0,1])
Out[32]: [array([1, 1, 1]), array([0, 0]), array([1])]

Проверка проверит оба условия Вы указали, и мы будем перетасовывать список, пока не выполним условия

1 голос
/ 03 марта 2020

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

import itertools, random

def penalty(lst):
    return sum(1 for k, g in itertools.groupby(lst)
               if k == 0 and len(list(g)) > 4 or k == 1 and len(list(g)) > 2)

def constrained_shuffle(lst):
    # penalty of original list
    p = penalty(lst)
    while p > 0:
        # randomly swap two elements, get new penalty
        a, b = random.randrange(len(lst)), random.randrange(len(lst))
        lst[a], lst[b] = lst[b], lst[a]
        p2 = penalty(lst)
        if p2 > p:
            # worse than before, swap back
            lst[a], lst[b] = lst[b], lst[a]
        else:
            p = p2

lst = [0] * 20 + [1] * 10
random.shuffle(lst)
constrained_shuffle(lst)
print(lst)

Для 200 0 и 100 1 это займет несколько от сотен до нескольких тысяч итераций, пока он не найдет правильный список, что нормально. Для списков с тысячами элементов это слишком медленно, но, вероятно, его можно улучшить, запомнив позиции слишком длинных полос и предпочтительно поменяв их местами.


О «случайности» подход: Конечно, это менее случайно, чем просто многократное создание нового перетасованного списка до тех пор, пока один из них не будет соответствовать ограничениям, но я не вижу, как это создаст смещение для или против определенных списков, если они удовлетворяют ограничениям. Я провел короткий тест, многократно генерируя перетасованные списки и подсчитывая частоту появления каждого варианта:

counts = collections.Counter()
for _ in range(10000):
    lst = [0] * 10 + [1] * 5
    random.shuffle(lst)
    constrained_shuffle(lst)
    counts[tuple(lst)] += 1
print(collections.Counter(counts.values()).most_common())
[(7, 197), (6, 168), (8, 158), (9, 157), (5, 150), (10, 98), (4, 92), 
 (11, 81), (12, 49), (3, 49), (13, 43), (14, 23), (2, 20), (15, 10),
 (1, 8), (16, 4), (17, 3), (18, 1)]

Итак, да, может быть, есть несколько списков, которые более вероятны, чем другие (один появился 18 раз, три 17 раз, а большинство других 5-9 раз). Для 100 000 итераций «более вероятные» списки появляются на ~ 50% чаще, чем остальные, но все еще только в 120 раз из этих 100 000 итераций, поэтому я думаю, что это не слишком большая проблема.

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

0 голосов
/ 03 марта 2020

Я действительно не знаю python, поэтому я дам вам псевдокод:

int length;
int[] onesAndZeros = new int[length];

for(int i: onesAndZeros) { // generate a random list
    i = random(0, 1);
}

int zeroCount() { // correct the ratio
    int c;
    for(int i: onesAndZeros) {
        if(i == 0) {
            c++;
        }
    }
    return c;
}

int wantedZeros;
if(zeroCount() / (length - zeroCount()) != 2) { // you should probably check a small interval, but this answer is already long
    int a = 2*(length - zeroCount()) - zeroCount(); // I will include the math if necessary
    wantedZeros = zeroCount() + a;
}
while(zeroCount() != wantedZeros) {
    boolean isLess = zeroCount < wantedZeros;
    if(isLess) {
        onesAndZeros[random(0, length - 1)] = 0;
    } else {
        onesAndZeros[random(0, length - 1)] = 0;
    }
}

string isCorrect() { // fix the 2 1s and 4 0s
    for(int i = 0; i < length; i++) {
        if(onesAndZeros[i] == 0 &&
           onesAndZeros[i + 1] == 0 &&
           onesAndZeros[i + 2] == 0 &&
           onesAndZeros[i + 3] == 0 &&
           onesAndZeros[i + 4] == 0) { // be sure not to go out of bounds!
            return "0" + i;
        } else 
        if(onesAndZeros[i] == 1 &&
           onesAndZeros[i + 1] == 1 &&
           onesAndZeros[i + 2] == 1) {
            return "1" + i;
        } else {
            return "a";
        }
    }
}

void fix(int type, int idx) {
    if(type == 0) {
        onesAndZeros[idx + 4] = 1;
    } else {
        onesAndZeros[idx + 2] = 0;
    }
}

string corr = isCorrect();
while(length(corr) >= 2) { // note: this step will screw up the ones/zeros ratio a bit, if you want to restore it, consider running the last 2 steps again
    if(corr[0] == '0') {
        fix(0, toInt(removeFirstChar(corr)));
    } else {
        fix(1, toInt(removeFirstChar(corr)));
    }
}

// done!

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...