Как мне построить массив из генератора?

Как я могу построить массив из объекта-генератора?

Позвольте мне проиллюстрировать проблему:

>>> import numpy
>>> def gimme():
...   for x in xrange(10):
...     yield x
...
>>> gimme()
<generator object at 0x28a1758>
>>> list(gimme())
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> numpy.array(xrange(10))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> numpy.array(gimme())
array(<generator object at 0x28a1758>, dtype=object)
>>> numpy.array(list(gimme()))
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

В данном случае gimme()это генератор, вывод которого я бы хотел превратить в массив. Однако конструктор массива не выполняет итерацию по генератору, он просто хранит сам генератор. Я хочу получить такое поведение numpy.array(list(gimme())), но я не хочу платить за использование промежуточного списка и окончательного массива в памяти одновременно. Есть ли более экономичный способ?

15.12.2008 05:44:31
Это интересная проблема. Я столкнулся с этим путем from numpy import *; print any(False for i in range(1))- который затеняет встроенное any()и дает противоположный результат (как я теперь знаю).
moooeeeep 7.06.2012 11:09:26
@moooeeeep это ужасно. если numpyне может (или не хочет) обращаться с генераторами, как это делает Python, по крайней мере, он должен вызывать исключение, когда получает генератор в качестве аргумента.
max 10.12.2012 00:57:16
@max Я наступил точно так же, как мой. По-видимому, это было поднято в списке NumPyранее ), заключив, что это не будет изменено, чтобы вызвать исключение, и всегда следует использовать пространства имен.
alexei 6.01.2014 21:51:02
5 ОТВЕТОВ
РЕШЕНИЕ

Numpy массивы требуют, чтобы их длина была установлена ​​явно во время создания, в отличие от списков Python. Это необходимо для того, чтобы место для каждого элемента могло быть последовательно выделено в памяти. Последовательное выделение является ключевой особенностью массивов numpy: это в сочетании с реализацией собственного кода позволяет выполнять над ними операции намного быстрее, чем обычные списки.

Помня об этом, технически невозможно взять объект-генератор и превратить его в массив, если только вы не:

  1. может предсказать, сколько элементов он выдаст при запуске:

    my_array = numpy.empty(predict_length())
    for i, el in enumerate(gimme()): my_array[i] = el
  2. готовы хранить его элементы в промежуточном списке:

    my_array = numpy.array(list(gimme()))
  3. Можно создать два идентичных генератора, выполнить первый, чтобы найти общую длину, инициализировать массив, а затем снова запустить генератор, чтобы найти каждый элемент:

    length = sum(1 for el in gimme())
    my_array = numpy.empty(length)
    for i, el in enumerate(gimme()): my_array[i] = el

1 , вероятно, то, что вы ищете. 2 - неэффективное пространство, а 3 - неэффективное по времени (вы должны пройти через генератор дважды).

126
17.11.2016 13:31:01
Встроенный array.arrayсписок представляет собой непрерывный несвязанный список, и вы можете просто array.array('f', generator). Сказать, что это невозможно - значит ввести в заблуждение. Это просто динамическое распределение.
Cuadue 19.02.2013 20:50:06
Почему numpy.array не выделяет память так же, как встроенный array.array, как говорит Cuadue. Что такое торговля? Я спрашиваю, потому что в обоих примерах есть непрерывная выделенная память. Или нет?
jgomo3 12.07.2013 22:47:02
Numpy предполагает, что размеры его массива не изменяются. Он сильно зависит от разных представлений одного и того же куска памяти, поэтому для расширения и перераспределения массивов потребуется, например, дополнительный уровень косвенности для включения представлений.
joeln 4.08.2013 05:08:35
Использование пустых немного быстрее. Поскольку вы собираетесь инициализировать значения любым способом, нет необходимости делать это дважды.
Kaushik Ghose 24.03.2015 11:40:45
Смотрите также ответ @ dhill ниже, который быстрее, чем 1.
Bill 13.07.2019 19:16:40

Один гугл за этим результатом переполнения стека, я обнаружил, что есть numpy.fromiter(data, dtype, count). По умолчанию count=-1все элементы берутся из повторяемого. Требуется dtypeустановить явно. В моем случае это сработало:

numpy.fromiter(something.generate(from_this_input), float)

201
21.02.2014 21:09:08
как бы вы применили это к вопросу? numpy.fromiter(gimme(), float, count=-1)не работает. Что означает something?
Matthias 009 27.03.2012 18:54:16
@ Matthias009 numpy.fromiter(gimme(), float, count=-1)работает для меня.
moooeeeep 7.06.2012 10:50:50
Поток, объясняющий, почему fromiterработает только на одномерных массивах: mail.scipy.org/pipermail/numpy-discussion/2007-August/… .
max 10.12.2012 01:04:57
fwiw, указывать count=-1не нужно, так как это значение по умолчанию.
askewchan 27.03.2013 03:04:25
Если вы знаете длину итерируемого заранее, укажите countдля повышения производительности. Таким образом, он выделяет память перед заполнением ее значениями, а не изменяет размер по требованию (см. Документацию numpy.fromiter)
Eddy 14.08.2017 15:48:44

В некоторой степени тангенциально, но если ваш генератор предназначен для понимания списков, вы можете использовать его numpy.whereдля более эффективного получения результата (я обнаружил это в своем собственном коде после просмотра этого поста)

6
5.10.2016 18:38:55

Хотя вы можете создать массив 1D из генератора с помощью numpy.fromiter(), вы можете создать массив ND из генератора с помощью numpy.stack:

>>> mygen = (np.ones((5, 3)) for _ in range(10))
>>> x = numpy.stack(mygen)
>>> x.shape
(10, 5, 3)

Это также работает для 1D массивов:

>>> numpy.stack(2*i for i in range(10))
array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])

Обратите внимание, что numpy.stackон потребляет внутренний генератор и создает промежуточный список с помощью arrays = [asanyarray(arr) for arr in arrays]. Реализация может быть найдена здесь .

14
31.08.2017 11:33:39
Это аккуратное решение, спасибо за указание. Но это, кажется, немного медленнее (в моем приложении), чем использование np.array(tuple(mygen)). Вот результаты теста: %timeit np.stack(permutations(range(10), 7)) 1 loop, best of 3: 1.9 s per loopпо сравнению с%timeit np.array(tuple(permutations(range(10), 7))) 1 loop, best of 3: 427 ms per loop
Bill 17.09.2017 16:45:03
Это кажется отличным и работает для меня. Но с Numpy 1.16.1 я получаю это предупреждение:FutureWarning: arrays to stack must be passed as a "sequence" type such as list or tuple. Support for non-sequence iterables such as generators is deprecated as of NumPy 1.16 and will raise an error in the future.
Joseph Sheedy 4.03.2019 20:19:35

Функции vstack , hstack и dstack могут принимать в качестве входных генераторов, которые выдают многомерные массивы.

0
14.11.2018 20:27:17
Можете привести пример, если ссылки меняются или что-то еще? :)
Ari Cooper-Davis 14.11.2018 20:45:59
Эти функции могут принимать генератор массивов, а не генератор значений
retnikt 24.03.2020 15:59:24