Как использовать random.shuffle() для генератора? питон

Как использовать random.shuffle() для генератора без инициализации списка из генератора? Возможно ли это? если нет, как еще я должен использовать random.shuffle() в моем списке?

>>> import random
>>> random.seed(2)
>>> x = [1,2,3,4,5,6,7,8,9]
>>> def yielding(ls):
...     for i in ls:
...             yield i
... 
>>> for i in random.shuffle(yielding(x)):
...     print i
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.7/random.py", line 287, in shuffle
    for i in reversed(xrange(1, len(x))):
TypeError: object of type 'generator' has no len()

Примечание: random.seed() был спроектирован так, что он возвращает тот же результат после каждого запуска script?

Ответ 1

Чтобы равномерно перемешать последовательность, random.shuffle() должен знать, сколько времени занимает вход. Генератор не может обеспечить это; вы должны материализовать его в список:

lst = list(yielding(x))
random.shuffle(lst)
for i in lst:
    print i

Вместо этого вы можете использовать sorted() с random.random() в качестве ключа:

for i in sorted(yielding(x), key=lambda k: random.random()):
    print i

но так как это также создает список, на этом пути мало смысла.

Демо:

>>> import random
>>> x = [1,2,3,4,5,6,7,8,9]
>>> sorted(iter(x), key=lambda k: random.random())
[9, 7, 3, 2, 5, 4, 6, 1, 8]

Ответ 2

Невозможно рандомизировать выход генератора без временного сохранения всех элементов. К счастью, это довольно легко в Python:

tmp = list(yielding(x))
random.shuffle(tmp)
for i in tmp:
    print i

Обратите внимание на вызов list(), который будет читать все элементы и помещать их в список.

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

Ответ 3

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

В качестве альтернативы... Если вам нужно использовать генератор...

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

from itertools import islice

import numpy as np


def shuffle(generator, buffer_size):
    while True:
        buffer = list(islice(generator, buffer_size))
        if len(buffer) == 0:
            break
        np.random.shuffle(buffer)
        for item in buffer:
            yield item


shuffled_generator = shuffle(my_generator, 256)

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

Ответ 4

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

Вам понадобится NumPy установленный

pip install numpy

Код:

import numpy as np
x = [1, 2, 3, 4, 5, 6, 7, 8, 9]

def shuffle_generator(lst):
    return (lst[idx] for idx in np.random.permutation(len(lst)))

def yielding(ls):
    for i in ls:
        yield i

# for i in random.shuffle(yielding(x)):
#    print i

for i in yielding(shuffle_generator(x)):
    print(i)

Ответ 5

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

Например:

def scramble(gen, buffer_size):
    buf = []
    i = iter(gen)
    while True:
        try:
            e = next(i)
            buf.append(e)
            if len(buf) >= buffer_size:
                choice = random.randint(0, len(buf)-1)
                buf[-1],buf[choice] = buf[choice],buf[-1]
                yield buf.pop()
        except StopIteration:
            random.shuffle(buf)
            yield from buf
            return

Результаты должны быть полностью случайными в окне buffer_size:

for e in scramble(itertools.count(start=0, step=1), 1000):
    print(e)

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

Чтобы проверить, подтвердите, что это возвращает 1000 уникальных элементов:

for e in scramble(range(1000), 100):
    print(e)