2010-10-21 1 views
14

Sto cercando una funzione che prende un iterabile i e una dimensione n e produce tuple di lunghezza n che sono valori sequenziali da i:generatori Python che i gruppi di un altro iterabili in gruppi di N

x = [1,2,3,4,5,6,7,8,9,0] 
[z for z in TheFunc(x,3)] 

[(1,2,3),(4,5,6),(7,8,9),(0)] 

Esiste una funzione di questo tipo nella libreria standard?

Se esiste come parte della libreria standard, non riesco a trovarlo e ho esaurito i termini da cercare. Potrei scrivere il mio, ma preferirei di no.

risposta

17

Vedi la ricetta grouper nel docs for the itertools package

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

(Tuttavia, questo è un duplicato di quite a few questions.)

+2

Se sapessi cercare "cernia" non avrei avuto bisogno di chiedere niente. Ma non sapevo di quel termine. – BCS

+0

+1. Completamente dimenticato delle ricette a portata di mano nei documenti. – Skurmedel

+2

Ho finito per usare questo, ma ho dovuto hackerare nel filtrare i valori di riempimento in una fase successiva. – BCS

4

Che ne dici di questo? Tuttavia non ha un valore di riempimento.

>>> def partition(itr, n): 
...  i = iter(itr) 
...  res = None 
...  while True: 
...    res = list(itertools.islice(i, 0, n)) 
...    if res == []: 
...      break 
...    yield res 
... 
>>> list(partition([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)) 
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] 
>>> 

Utilizza una copia dell'originale iterabile, che scarica per ogni giunzione successiva. L'unico altro modo in cui il mio cervello stanco poteva arrivare era la generazione di punti finali di giuntura con raggio d'azione.

Forse dovrei cambiare list() a tuple() in modo che corrisponda meglio all'output.

+0

LOL. Mi stai prendendo in giro. C'è un bug qui nella risposta e la mia modifica per questo è stata rifiutata? Il mio rispetto per la comunità SO è appena diminuito notevolmente. –

+2

btw, itertools.islice (i, 0, 3) -> itertools.islice (i, 0, n) Ancora non riesco a credere alla comunità SO. –

+0

Non l'ho rifiutato, lo ha fatto qualcun altro. Ma tu hai ragione. Il 3 è hardcoded annullando lo scopo di n come parametro. Se vuoi posso modificarlo ma non riceverai nessuna risposta, fino a te:) – Skurmedel

0
def grouper(iterable, n): 
     while True: 
      yield itertools.chain(iterable.next(),itertools.islice(iterable, n-1)) 
+2

La tua risposta sarebbe meglio se includessi una breve spiegazione per andare con quel codice. – trooper

16

Quando si desidera raggruppare un iteratore in blocchi di nsenza imbottitura il gruppo finale con un valore di riempimento, utilizzare iter(lambda: list(IT.islice(iterable, n)), []):

import itertools as IT 

def grouper(n, iterable): 
    """ 
    >>> list(grouper(3, 'ABCDEFG')) 
    [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']] 
    """ 
    iterable = iter(iterable) 
    return iter(lambda: list(IT.islice(iterable, n)), []) 

seq = [1,2,3,4,5,6,7] 
print(list(grouper(3, seq))) 

rendimenti

[[1, 2, 3], [4, 5, 6], [7]] 

C'è una spiegazione di come funziona nella seconda metà di this answer.


Quando si desidera raggruppare un iteratore in blocchi di ne pad il gruppo finale con un valore di riempimento, utilizzare il grouper recipezip_longest(*[iterator]*n):

Per esempio, in python2:

>>> list(IT.izip_longest(*[iter(seq)]*3, fillvalue='x')) 
[(1, 2, 3), (4, 5, 6), (7, 'x', 'x')] 

In Python3, cosa è stato izip_longest è ora rinominato zip_longest:

>>> list(IT.zip_longest(*[iter(seq)]*3, fillvalue='x')) 
[(1, 2, 3), (4, 5, 6), (7, 'x', 'x')] 

Quando si desidera raggruppare una sequenzain blocchi di n è possibile utilizzare il chunks ricetta:

def chunks(seq, n): 
    # https://stackoverflow.com/a/312464/190597 (Ned Batchelder) 
    """ Yield successive n-sized chunks from seq.""" 
    for i in xrange(0, len(seq), n): 
     yield seq[i:i + n] 

Si noti che, a differenza di iteratori in generale, sequences by definition hanno un lunghezza (es __len__ è definito).

0

So che questo è stato risposto più volte ma sto aggiungendo la mia soluzione che dovrebbe migliorare in entrambi, applicabilità generale a sequenze e iteratori, leggibilità (nessuna condizione di uscita loop invisibile da eccezione StopIteration) e prestazioni rispetto alla cernia ricetta. È molto simile all'ultima risposta di Svein.

def chunkify(iterable, n): 
    iterable = iter(iterable) 
    n_rest = n - 1 

    for item in iterable: 
     rest = itertools.islice(iterable, n_rest) 
     yield itertools.chain((item,), rest) 
0

Ecco una soluzione diversa, che non fa uso di itertools e, anche se ha un altro paio di righe, si supera a quanto pare le risposte date in cui pezzi sono molto più corta della lunghezza iterabile. Tuttavia, per i pezzi grandi le altre risposte sono molto più veloci.

def batchiter(iterable, batch_size): 
    """ 
    >>> list(batchiter('ABCDEFG', 3)) 
    [['A', 'B', 'C'], ['D', 'E', 'F'], ['G']] 
    """ 
    next_batch = [] 
    for element in iterable: 
     next_batch.append(element) 
     if len(next_batch) == batch_size: 
      batch, next_batch = next_batch, [] 
      yield batch 
    if next_batch: 
     yield next_batch 


In [19]: %timeit [b for b in batchiter(range(1000), 3)] 
1000 loops, best of 3: 644 µs per loop 

In [20]: %timeit [b for b in grouper(3, range(1000))] 
1000 loops, best of 3: 897 µs per loop 

In [21]: %timeit [b for b in partition(range(1000), 3)] 
1000 loops, best of 3: 890 µs per loop 

In [22]: %timeit [b for b in batchiter(range(1000), 333)] 
1000 loops, best of 3: 540 µs per loop 

In [23]: %timeit [b for b in grouper(333, range(1000))] 
10000 loops, best of 3: 81.7 µs per loop 

In [24]: %timeit [b for b in partition(range(1000), 333)] 
10000 loops, best of 3: 80.1 µs per loop 
1

Questa è una richiesta molto comune in Python. È abbastanza comune da essere inserito nel pacchetto di utilità unificato boltons. Prima di tutto, there are extensive docs here. Inoltre, the module è progettato e testato per fare affidamento solo sulla libreria standard (compatibile con Python 2 e 3), il che significa che è possibile just download the file directly into your project.

# if you downloaded/embedded, try: 
# from iterutils import chunked 

# with `pip install boltons` use: 

from boltons.iterutils import chunked 

print(chunked(range(10), 3)) 
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] 

C'è una forma iteratore/generatore per indefinita lunghe sequenze/e:

print(list(chunked_iter(range(10), 3, fill=None))) 
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, None, None]] 

Come si può vedere, si può anche riempire la sequenza con un valore di vostra scelta, pure. Infine, in qualità di maintainer, posso assicurarti che, mentre il codice è stato scaricato/testato da migliaia di sviluppatori, se riscontri problemi, riceverai il supporto più veloce possibile tramite lo boltons GitHub Issues page. Spero che questa (e/o una qualsiasi delle altre 150 ricette di boltons) sia stata d'aiuto!

1

Questo è un quesiton molto vecchio, ma penso che sia utile menzionare il seguente approccio per il caso generale. Il suo merito principale è che ha bisogno di scorrere i dati una sola volta, quindi funzionerà con i cursori del database o altre sequenze che possono essere utilizzate solo una volta. Lo trovo anche più leggibile.

def chunks(n, iterator): 
    out = [] 
    for elem in iterator: 
     out.append(elem) 
     if len(out) == n: 
      yield out 
      out = [] 
    yield out