Генератор на основе другого генератора - PullRequest
0 голосов
/ 03 июля 2018

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

nums = ((i+1) for i in range(4))

Вышеуказанное даст нам 1, 2, 3 и 4.

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

def batch_generator(batch_size):
    do something on nums
    yield batches of size batch_size

И тогда выходной сигнал этого пакетного генератора будет: 1 и 2, а затем 3 и 4. Кортежи / списки не имеет значения. Важно то, как вернуть эти партии. Я нашел это ключевое слово yield from, которое было введено в Python 3.3, но, похоже, в моем случае оно бесполезно.

И, очевидно, если бы у нас было 5 чисел вместо 4, а batch_size равно 2, мы бы пропустили последнее полученное значение из первого генератора.

Ответы [ 3 ]

0 голосов
/ 03 июля 2018

Под itertools у вас есть фрагмент кода, который делает именно это:

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

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

0 голосов
/ 03 июля 2018

Это было именно то, что мне было нужно:

def giveBatch(numOfItems):
    nums = (i+1 for i in range(7))

    while True:
        yield [next(nums) for i in range(numOfItems)]
0 голосов
/ 03 июля 2018

Мое собственное решение для этого может быть,

nums = (i+1 for i in range(4))

def giveBatch(gen, numOfItems):
    try:
        return [next(gen) for i in range(numOfItems)]
    except StopIteration:
        pass

giveBatch(nums, 2)
# [1, 2]
giveBatch(nums, 2)
# [3, 4]

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

from timeit import timeit

def wrapper(func, *args, **kwargs):
    def wrapped():
        return func(*args, **kwargs)
    return wrapped

nums = (i+1 for i in range(1000000))

wrappedGiveBatch = wrapper(giveBatch, nums, 2)
timeit(wrappedGiveBatch, number=1000000)
# ~ 0.998439

wrappedGrouper = wrapper(grouper, nums, 2)
timeit(wrappedGrouper, number=1000000)
# ~ 0.734342
...