Создать список без похожих переходов

#python #python-2.7 #open-sesame

#python #python-2.7 #откройся-сезам

Вопрос:

Я пытаюсь создать список длиной = 120, который должен включать 4 числа. Сложность в том, что числа не должны отображаться подряд, и каждое число должно совпадать с одинаковым количеством.

Это мой скрипт.

 import random
List = [1,2,3,4]
seq_CG = random.sample(List,len(List))
for i in range(121):
    randomList = random.sample(List, len(List))
    if randomList[0]!=seq_CG[-1]:
        seq_CG.append(randomList)
        i = len(seq_CG)
print List, randomList, seq_CG
  

Я довольно близок. Однако что-то не работает. И, может быть, есть даже более короткое и случайное решение?

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

Комментарии:

1. However something does not work <- не могли бы вы, пожалуйста, объяснить, что вы здесь имеете в виду? Каков наблюдаемый результат? Какой результат вы хотели? Чем они отличаются?

2. Числа не должны отображаться подряд. Поэтому в списке не должно быть [1,1] . Однако это так. Я предполагаю, что seq_CG[-1] не занимает последнее место в самом актуальном списке.

3. Numbers shall not appear in a row предполагает, что [1,1] этого не должно быть randomList , чего, согласно вашему коду, не будет. Помимо этого, я действительно не понимаю, где у вас проблемы

Ответ №1:

Вот несколько решений.

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

 from random import randrange
from itertools import islice
from collections import Counter

def non_repeating(seq):
    m = len(seq)
    idx = randrange(0, m)
    while True:
        yield seq[idx]
        idx = (idx   randrange(1, m)) % m

seq = [1, 2, 3, 4]
print(''.join(map(str, islice(non_repeating(seq), 60))))

ctr = Counter(islice(non_repeating(seq), 12000))
print(ctr)
  

типичный результат

 313231412323431321312312131413242424121414314243432414241413
Counter({1: 3017, 4: 3012, 3: 2993, 2: 2978})
  

Распределение значений, создаваемых этим кодом, выглядит довольно однородным, но я не анализировал его математически, и я не даю никаких гарантий относительно его однородности.

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

 from random import choice
from itertools import islice
from collections import Counter

def non_repeating(seq):
    pool = []
    prev = None
    while True:
        p = set(pool).difference([prev])
        if p:
            current = p.pop()
            pool.remove(current)
        else:
            current = choice(seq)
            if current == prev:
                pool.append(current)
                continue
        yield current
        prev = current

seq = [1, 2, 3, 4]
print(''.join(map(str, islice(non_repeating(seq), 60))))

ctr = Counter(islice(non_repeating(seq), 12000))
print(ctr)
  

типичный результат

 142134314121212124343242324143123212323414131323434212124232
Counter({4: 3015, 2: 3005, 3: 3001, 1: 2979})
  

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


Наконец, вот версия, которая дает точно равномерное распределение. Не пытайтесь использовать его для входной последовательности из 2 (или менее) элементов, потому что это может застрять в бесконечном цикле; конечно, в любом случае есть только 2 решения для такой входной последовательности. 🙂

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

 from random import shuffle
from itertools import groupby
from collections import Counter

def non_repeating(seq, copies=3):
    seq = seq * copies
    while True:
        shuffle(seq)
        result, pool = [], []
        for k, g in groupby(seq):
            result.append(k)
            n = len(list(g)) - 1
            if n:
                pool.extend(n * [k])

        for u in pool:
            for i in range(len(result) - 1):
                if result[i] != u != result[i   1]:
                    result.insert(i 1, u)
                    break
            else:
                break
        else:
            return result

# Test that sequence doesn't contain repeats
def verify(seq):
    return all(len(list(g)) == 1 for _, g in groupby(seq))

seq = [1, 2, 3, 4]
result = non_repeating(seq, 15)
print(''.join(map(str, result)))
print(verify(result))
print(Counter(result))
  

типичный результат

 241413414241343212423232123241234123124342342141313414132313
True
Counter({1: 15, 2: 15, 3: 15, 4: 15})
  

Комментарии:

1. Возможно, мне нужно еще раз задать вопрос. Мне действительно нужно одинаковое количество каждого числа. Я знаю, что с длинными последовательностями они приближаются. Но мне нужно, чтобы они были равны в последовательности из 120 чисел.

2. @SDahm: Извините, я не понимал, что вы хотите, чтобы распределение было абсолютно равномерным. Это возможно, но, конечно, это делает результирующую последовательность намного менее случайной.

Ответ №2:

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

 from itertools import groupby
from random import choice

def non_repeating(values):
    if not len(values) > 1:
        raise ValueError('must have more than 1 value')
    candidates = iter(lambda: choice(values), object())
    # Python 3.x -- yield from (k for k, g in groupby(candidates))
    # Python 2.x
    for k, g in groupby(candidates):
        yield k

data = [1, 2, 3, 4]
sequence = list(islice(non_repeating(data), 20))
# [3, 2, 1, 4, 1, 4, 1, 4, 2, 1, 2, 1, 4, 1, 4, 3, 2, 3, 4, 3]
# [3, 2, 3, 4, 1, 3, 1, 4, 1, 4, 2, 1, 2, 3, 2, 4, 1, 4, 2, 3]
# etc...
  

Комментарии:

1. Это выглядит многообещающе. Однако я получаю синтаксическую ошибку: недопустимый синтаксис для формы yield … Я использую python 2.7 в OpenSesame.

2. Я добавил «импортировать groupby, islice». Но теперь объект списка не вызывается.

3. @SDahm в какой строке?

4. Странно. Теперь это работает 🙂 Я только закрыл и открыл программу?? Одной вещи все еще не хватает. Числа должны встречаться одинаковое количество раз. В примере каждый 5 раз. В первой строке всего четыре 3s.

5. @SDahm это совершенно другой вопрос — это гарантирует только непоследовательные вхождения, которые перед вашим редактированием являются допустимым ответом 🙂