Разделить генератор/итерируемый каждый n элементов в python (splitEvery)
Я пытаюсь написать функцию Haskel 'splitEvery' в Python. Вот определение:
splitEvery :: Int -> [e] -> [[e]]
@'splitEvery' [email protected] splits a list into length-n pieces. The last
piece will be shorter if @[email protected] does not evenly divide the length of
the list.
Основная версия этого прекрасно работает, но мне нужна версия, которая работает с генераторными выражениями, списками и итераторами. И, если в качестве входа есть генератор, он должен возвращать генератор в качестве выхода!
Испытания
# should not enter infinite loop with generators or lists
splitEvery(itertools.count(), 10)
splitEvery(range(1000), 10)
# last piece must be shorter if n does not evenly divide
assert splitEvery(5, range(9)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
# should give same correct results with generators
tmp = itertools.islice(itertools.count(), 10)
assert list(splitEvery(5, tmp)) == [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Текущая реализация
Вот код, который у меня есть, но он не работает с простым списком.
def splitEvery_1(n, iterable):
res = list(itertools.islice(iterable, n))
while len(res) != 0:
yield res
res = list(itertools.islice(iterable, n))
Это не работает с выражением генератора (благодаря jellybean для его исправления):
def splitEvery_2(n, iterable):
return [iterable[i:i+n] for i in range(0, len(iterable), n)]
Должен быть простой фрагмент кода, который выполняет разделение. Я знаю, что у меня могут быть разные функции, но, похоже, это должно быть и легко. Я, вероятно, застрял на несущественной проблеме, но это действительно меня подслушивает.
Он похож на grouper из http://docs.python.org/library/itertools.html#itertools.groupby, но я не хочу, чтобы он заполнял дополнительные значения.
def grouper(n, iterable, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(fillvalue=fillvalue, *args)
Он упоминает метод, который усекает последнее значение. Это не то, что я хочу.
Порядок следования итераций слева направо гарантирован. Это делает возможным идиому для кластеризации рядов данных в n-длинные группы с использованием izip (* [iter (s)] * n).
list(izip(*[iter(range(9))]*5)) == [[0, 1, 2, 3, 4]]
# should be [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Ответы
Ответ 1
from itertools import islice
def split_every(n, iterable):
i = iter(iterable)
piece = list(islice(i, n))
while piece:
yield piece
piece = list(islice(i, n))
Некоторые тесты:
>>> list(split_every(5, range(9)))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]
>>> list(split_every(3, (x**2 for x in range(20))))
[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81, 100, 121], [144, 169, 196], [225, 256, 289], [324, 361]]
>>> [''.join(s) for s in split_every(6, 'Hello world')]
['Hello ', 'world']
>>> list(split_every(100, []))
[]
Ответ 2
Вот быстрая однострочная версия. Как и Haskell, он ленив.
from itertools import islice, takewhile, repeat
split_every = (lambda n, it:
takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))
Для этого нужно использовать iter
перед вызовом split_every
.
Пример:
list(split_every(5, iter(xrange(9))))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Хотя это не однострочный шрифт, приведенная ниже версия не требует, чтобы вы вызывали iter
, что может быть общей ошибкой.
from itertools import islice, takewhile, repeat
def split_every(n, iterable):
"""
Slice an iterable into chunks of n elements
:type n: int
:type iterable: Iterable
:rtype: Iterator
"""
iterator = iter(iterable)
return takewhile(bool, (list(islice(iterator, n)) for _ in repeat(None)))
(Спасибо @eli-korvigo за улучшения.)
Ответ 3
Однострочное, встроенное решение для этого (поддерживает v2/v3, итераторы, использует стандартную библиотеку и понимание одного генератора):
import itertools
def split_groups(iter_in, group_size):
return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))
Ответ 4
построение принятого ответа и использование менее известного использования iter
(при передаче второго аргумента он вызывает первый, пока не получит второй), вы можете сделать это очень легко:
python3:
from itertools import islice
def split_every(n, iterable):
iterable = iter(iterable)
yield from iter(lambda: list(islice(iterable, n)), [])
python2:
def split_every(n, iterable):
iterable = iter(iterable)
for chunk in iter(lambda: list(islice(iterable, n)), []):
yield chunk
Ответ 5
Я думаю, те questions почти равны
Изменив немного, чтобы обрезать последнее, я думаю, что хорошим решением для случая генератора будет:
from itertools import *
def iter_grouper(n, iterable):
it = iter(iterable)
item = itertools.islice(it, n)
while item:
yield item
item = itertools.islice(it, n)
для объекта, который поддерживает срезы (списки, строки, кортежи), мы можем сделать:
def slice_grouper(n, sequence):
return [sequence[i:i+n] for i in range(0, len(sequence), n)]
теперь это просто вопрос правильного метода:
def grouper(n, iter_or_seq):
if hasattr(iter_or_seq, "__getslice__"):
return slice_grouper(n, iter_or_seq)
elif hasattr(iter_or_seq, "__iter__"):
return iter_grouper(n, iter_or_seq)
Я думаю, вы могли бы отполировать его немного больше: -)
Ответ 6
more_itertools
имеет chucked
:
import more_itertools as mit
list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
Ответ 7
Почему бы не сделать это так? Похож на вашу функцию splitEvery_2
.
def splitEveryN(n, it):
return [it[i:i+n] for i in range(0, len(it), n)]
Фактически, это только отнимает лишний шаг шага от среза в вашем решении.:)
Ответ 8
Это ответ, который работает как для списка, так и для генератора:
from itertools import count, groupby
def split_every(size, iterable):
c = count()
for k, g in groupby(iterable, lambda x: next(c)//size):
yield list(g) # or yield g if you want to output a generator
Ответ 9
Я столкнулся с этим, поскольку я тоже пытаюсь вырезать партии, но делаю это на генераторе из потока, поэтому большинство решений здесь неприменимы или не работают в python 3.
Для людей, которые все еще спотыкаются на это, здесь общее решение, использующее itertools:
from itertools import islice, chain
def iter_in_slices(iterator, size=None):
while True:
slice_iter = islice(iterator, size)
# If no first object this is how StopIteration is triggered
peek = next(slice_iter)
# Put the first object back and return slice
yield chain([peek], slice_iter)
Ответ 10
Вот как вы справляетесь со списком vs iterator:
def isList(L): # Implement it somehow - returns True or false
...
return (list, lambda x:x)[int(islist(L))](result)
Ответ 11
def chunks(iterable,n):
"""assumes n is an integer>0
"""
iterable=iter(iterable)
while True:
result=[]
for i in range(n):
try:
a=next(iterable)
except StopIteration:
break
else:
result.append(a)
if result:
yield result
else:
break
g1=(i*i for i in range(10))
g2=chunks(g1,3)
print g2
'<generator object chunks at 0x0337B9B8>'
print list(g2)
'[[0, 1, 4], [9, 16, 25], [36, 49, 64], [81]]'
Ответ 12
это сделает трюк
from itertools import izip_longest
izip_longest(it[::2], it[1::2])
где * it * - некоторая итерируемая
Пример:
izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')
Позвольте сломать это вниз
'abcdef'[::2] -> 'ace'
'abcdef'[1::2] -> 'bdf'
Как вы можете видеть, последний номер в срезе указывает интервал, который будет использоваться для подбора элементов. Вы можете больше узнать об использовании расширенных фрагментов здесь.
Функция zip принимает первый элемент из первого итерабельного и объединяет его с первым элементом со вторым итерабельным. Затем функция zip делает то же самое для второго и третьего элементов, пока один из итераций не закончится.
В результате получается итератор. Если вы хотите, чтобы список использовал функцию list() для результата.
Ответ 13
Если вы хотите решение, которое
- использует только генераторы (без промежуточных списков или кортежей),
- работает для очень длинных (или бесконечных) итераторов,
- работает для очень больших размеров партии,
это трюк:
def one_batch(first_value, iterator, batch_size):
yield first_value
for i in xrange(1, batch_size):
yield iterator.next()
def batch_iterator(iterator, batch_size):
iterator = iter(iterator)
while True:
first_value = iterator.next() # Peek.
yield one_batch(first_value, iterator, batch_size)
Он работает, просматривая следующее значение в итераторе и передавая это в качестве первого значения генератору (one_batch()
), которое даст ему, вместе с остальной частью пакета.
Шаг заглядывания поднимет StopIteration
точно, когда итератор ввода будет исчерпан и больше нет партий. Поскольку это правильное время для повышения StopIteration
в методе batch_iterator()
, нет необходимости улавливать исключение.
Это будет обрабатывать строки из stdin партиями:
for input_batch in batch_iterator(sys.stdin, 10000):
for line in input_batch:
process(line)
finalise()
Я нашел это полезным для обработки большого количества данных и загрузки результатов в пакеты во внешний магазин.