podziel generator / iterowalny co N elementów w Pythonie (podziel)

Próbuję napisać funkcję Haskela 'splitEvery' w Pythonie. Oto jego definicja:

splitEvery :: Int -> [e] -> [[e]]
    @'splitEvery' n@ splits a list into length-n pieces.  The last
    piece will be shorter if @n@ does not evenly divide the length of
    the list.

Podstawowa wersja tego działa dobrze, ale chcę wersję, która działa z wyrażeniami generatora, listami i iteratorami. i , jeśli jest generator jako wejście, powinien zwrócić generator jako wyjście!

Testy

# 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]]

Bieżąca Realizacja

Oto kod, który obecnie mam, ale nie działa z prostą listą.

def splitEvery_1(n, iterable):
    res = list(itertools.islice(iterable, n))
    while len(res) != 0:
        yield res
        res = list(itertools.islice(iterable, n))

Ten nie działa z wyrażeniem generatora (dzięki jellybean za naprawienie go):

def splitEvery_2(n, iterable): 
    return [iterable[i:i+n] for i in range(0, len(iterable), n)]

Musi być prosty fragment kodu, który dokonuje podziału. Wiem, że mogę po prostu mieć różne funkcje, ale wydaje się, że powinno to być i łatwe do zrobienia. Pewnie utknąłem z nieistotnym problemem, ale to mnie naprawdę wkurza.


Jest podobny do groupera z http://docs.python.org/library/itertools.html#itertools.groupby Ale ja nie chcę, żeby wypełniał dodatkowe wartości.

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)

Wspomina o metodzie, która obcina ostatnią wartość. Ja też tego nie chcę.

Kolejność oceny od lewej do prawej jest gwarantowana. Umożliwia to tworzenie idiomu grupowania serii danych w grupy o długości n przy użyciu 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]]
Author: James Brooks, 2009-12-16

13 answers

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))

Niektóre testy:

>>> 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, []))
[]
 42
Author: Roberto Bonvallet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-16 15:28:15

Oto szybka wersja jednowarstwowa. Jak u Haskella, jest leniwy.

from itertools import islice, takewhile, repeat
split_every = (lambda n, it:
    takewhile(bool, (list(islice(it, n)) for _ in repeat(None))))

Wymaga to użycia iter przed wywołaniem split_every.

Przykład:

list(split_every(5, iter(xrange(9))))
[[0, 1, 2, 3, 4], [5, 6, 7, 8]]

Chociaż nie jednolinijkowy, poniższa wersja nie wymaga, aby zadzwonić iter, co może być częstą pułapką.

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)))

(podziękowania dla @ eli-korvigo za ulepszenia.)

 18
Author: Elliot Cameron,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-04 22:42:55

Bazując na zaakceptowanej odpowiedzi i stosując mniej znane użycie iter (że po przejściu drugiego arg, wywołuje pierwszą, dopóki nie otrzyma drugiej), możesz to zrobić naprawdę łatwo:

Python3:

from itertools import islice

def split_every(n, iterable):
    iterable = iter(iterable)
    yield from iter(lambda: list(islice(iterable, n)), [])

Pyton2:

def split_every(n, iterable):
    iterable = iter(iterable)
    for chunk in iter(lambda: list(islice(iterable, n)), []):
        yield chunk
 6
Author: acushner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-22 13:40:24

more_itertools ma chunked Funkcja:

import more_itertools as mit


list(mit.chunked(range(9), 5))
# [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
 5
Author: pylang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-01 18:49:22

Jednoliniowe, inlineable rozwiązanie do tego (obsługuje v2/v3, Iteratory, wykorzystuje standardową bibliotekę i rozumienie pojedynczego generatora):

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))
 3
Author: Andrey Cizov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-06 22:11:20

Myślę, że Ci pytania {[5] } są prawie równe

Zmieniając trochę, aby przyciąć ostatni, myślę, że dobrym rozwiązaniem dla przypadku generatora byłoby:

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)

Dla obiektu, który obsługuje plasterki (listy, ciągi znaków, krotki), możemy wykonać:

def slice_grouper(n, sequence):
   return [sequence[i:i+n] for i in range(0, len(sequence), n)]

Teraz to tylko kwestia podania poprawnej metody:

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)

Myślę, że mógłbyś to trochę Dopracować: -)

 2
Author: fortran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:21

Dlaczego nie zrobić tego w ten sposób? Wygląda prawie jak twoja splitEvery_2 funkcja.

def splitEveryN(n, it):
    return [it[i:i+n] for i in range(0, len(it), n)]

W rzeczywistości usuwa tylko niepotrzebny odstęp kroków z plasterka w Twoim roztworze. :)

 1
Author: Johannes Charra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-16 15:06:18

Jest to odpowiedź, która działa zarówno dla listy, jak i generatora:

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
 1
Author: justhalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-02 02:58:37

Natknąłem się na to, ponieważ próbuję też pociąć partie, ale robię to na generatorze ze strumienia, więc większość rozwiązań tutaj nie ma zastosowania, lub nie działa w Pythonie 3.

Dla ludzi wciąż natykających się na to, oto ogólne rozwiązanie przy użyciu 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)
 1
Author: Ashley Waite,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-02 03:03:30

Oto jak radzisz sobie z list vs iterator:

def isList(L): # Implement it somehow - returns True or false
...
return (list, lambda x:x)[int(islist(L))](result)
 0
Author: Hamish Grubijan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-16 15:15:48
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]]'
 0
Author: robert king,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-13 04:53:47

This will do the trick

from itertools import izip_longest
izip_longest(it[::2], it[1::2])

Gdzie * to * jest jakieś iterable


Przykład:

izip_longest('abcdef'[::2], 'abcdef'[1::2]) -> ('a', 'b'), ('c', 'd'), ('e', 'f')

Let ' s break this down

'abcdef'[::2] -> 'ace'
'abcdef'[1::2] -> 'bdf'

Jak widać, ostatnia liczba w plasterku określa interwał, który będzie używany do podnoszenia przedmiotów. Więcej o używaniu plastrów rozszerzonych przeczytasz tutaj .

Funkcja zip pobiera pierwszą pozycję z pierwszej iterable i łączy ją z pierwszą pozycją z drugą iterable. Funkcja zip następnie robi to samo dla drugiej i trzeciej pozycji, dopóki jedna z iterabli nie skończy się z wartościami.

Rezultatem jest iterator. Jeśli chcesz mieć listę, użyj funkcji list () na wyniku.

 -1
Author: Mr. Me,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-29 00:19:24

Jeśli chcesz rozwiązania, które

  • używa tylko generatorów (bez list pośrednich i krotek),
  • działa przez bardzo długie (lub nieskończone) Iteratory,
  • działa na bardzo duże partie,

To robi sztuczkę:

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)

Działa poprzez Podglądanie następnej wartości w iteratorze i przekazanie jej jako pierwszej wartości generatorowi (one_batch()), który uzyska ją wraz z resztą partii.

Krok peek podniesie StopIteration dokładnie wtedy, gdy wejście iterator jest wyczerpany i nie ma więcej partii. Ponieważ jest to właściwy czas na wywołanie StopIteration w metodzie batch_iterator(), nie ma potrzeby przechwytywania wyjątku.

To będzie przetwarzać linie ze standardowego wejścia w partiach:

for input_batch in batch_iterator(sys.stdin, 10000):
    for line in input_batch:
        process(line)
    finalise()

Uznałem to za przydatne do przetwarzania wielu danych i przesyłania wyników partiami do zewnętrznego sklepu.

 -1
Author: Carl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-06 13:33:13