Parça listesindeki bir listeyi yinelemenin en “pitonik” yolu nedir?


487

Bir seferde dört tamsayı ile çalışması gereken tamsayıların bir listesini girdi olarak alır bir Python komut dosyası var. Ne yazık ki, girdi üzerinde kontrolüm yok ya da dört elemanlı tuples listesi olarak geçmesini isterdim. Şu anda, bu şekilde tekrar ediyorum:

for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Yine de, "C-think" a çok benziyor, bu da beni bu durumla başa çıkmanın daha pitonik bir yolu olduğundan şüpheleniyor. Liste yinelendikten sonra atılır, bu yüzden korunmasına gerek yoktur. Belki böyle bir şey daha iyi olurdu?

while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Yine de tam olarak "hissetmiyorum". : - /

İlgili soru: Bir listeyi Python'daki eşit boyutlu parçalara nasıl ayırırsınız?


3
Liste boyutu dördün katı değilse kodunuz çalışmaz.
Pedro Henriques

5
Uzunluğunun bu kadar ileri gitmeden önce dört katı olması için listeyi uzatıyorum ().
Ben Blank

4
@ ΤΖΩΤΖΙΟΥ - Sorular çok benzer, ancak tam olarak kopya değil. "N boyutunda herhangi bir sayı grubuna bölün" veya "her boyutta N parçaya bölün". :-)
Ben Blank


Yanıtlar:


339

Python'un itertools belgelerinin tarifler bölümünden değiştirildi :

from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Örnek
Sahte kodda örneği terse tutmak.

grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Not:izip_longest yerine Python 2 kullanın zip_longest.


67
Sonunda bir python oturumunda bununla oynama şansı buldum. Benim kadar karışmış olanlar için, bu aynı yineleyiciyi izip_longest uzunluğuna birden çok kez besleyerek ayrı dizilerden çizgili değerlerden ziyade aynı dizinin ardışık değerlerini tüketmesine neden oluyor. Onu seviyorum!
Ben Blank

6
Dolgu değerini filtrelemenin en iyi yolu nedir? ([öğe dolum değeri değilse öğelerdeki öğe için öğe]] gruplayıcıdaki öğeler için (yinelenebilir))?
gotgenes

14
256k büyüklüğündeki parçalar için bu orfoz tarifinin performansının çok kötü olacağından şüpheleniyorum, çünkü izip_longest256k argümanları beslenecek.
anatoly techtonik

13
Bazı yerlerde yorumcular "nihayet bu nasıl çalıştı ...." der Belki biraz açıklama gereklidir. Özellikle yineleyicilerin listesi.
LondonRob

6
Bunu kullanmanın bir yolu var ama Noneson yığın doldurulmadan mı?
CMCDragonkai

420
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Basit. Kolay. Hızlı. Herhangi bir sekansla çalışır:

text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']

16
@Carlos Crasborn'un sürümü herhangi bir yinelenebilir (sadece yukarıdaki kod gibi diziler için değil) için çalışır; özlüdür ve muhtemelen aynı derecede hızlı ve hatta daha hızlıdır. itertoolsModüle aşina olmayan insanlar için biraz belirsiz (belirsiz) olsa da .
jfs

1
Kabul. Bu en genel ve pitonik yoldur. Açık ve özlü. (ve uygulama motorunda çalışır)
Matt Williamson

3
A chunkerdöndürdüğünü unutmayın generator. return [...]Bir liste almak için: konumuna geri dönün .
Dror

11
Bunun yerine bir işlev binayı yazma ve daha sonra bir jeneratör döndürmek, ayrıca yoluyla doğrudan bir jeneratör yazabilirsiniz yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Dahili olarak bunun ilgili herhangi bir açıdan farklı bir şekilde ele alınacağından emin değilim, ancak biraz daha net olabilir.
Alfe

3
Bunun yalnızca dizine göre öğe erişimini destekleyen diziler için geçerli olduğunu ve __getitem__yöntemi desteklemeyebildikleri için genel yineleyiciler için çalışmadığını unutmayın .
apollov


22
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Diğer yol:

import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4

2
Jeneratörleri kullanmak için +1, önerilen tüm çözümlerden en "pythonic" gibi dikişler
Sergey Golovchenko

7
Bu kadar kolay bir şey için oldukça uzun ve sakar, ki bu çok pitonik değil. S. Lott'un versiyonunu tercih ediyorum
zenazn

4
@zenazn: bu jeneratör örnekleri üzerinde çalışacak, dilimleme olmayacak
Janus Troelsen

Jeneratörler ve diğer dilimlenemez yineleyicilerle düzgün bir şekilde çalışmaya ek olarak, ilk çözüm, son yığın daha küçükse size, bazen istenen bir "dolgu" değeri gerektirmez .
dano

1
Ayrıca jeneratörler için +1. Diğer çözümler bir lenarama gerektirir ve bu nedenle diğer jeneratörlerde çalışmaz.
Cuadue


11

Bu sorun için ideal çözüm yineleyicilerle çalışır (sadece dizilerle değil). Ayrıca hızlı olmalı.

Bu, itertools belgeleri tarafından sağlanan çözümdür:

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

%timeitMac kitabımın havasında ipython'u kullanarak , döngü başına 47.5 bizi alıyorum.

Ancak, bu sonuçlar benim için işe yaramıyor, çünkü sonuçlar eşit büyüklükte gruplar olacak şekilde dolduruluyor. Dolgu içermeyen bir çözelti biraz daha karmaşıktır. En naif çözüm şunlar olabilir:

def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Basit, ama oldukça yavaş: döngü başına 693 us

isliceİç döngü için kullanabileceğim en iyi çözüm :

def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

Aynı veri kümesiyle, döngü başına 305 ABD doları alıyorum.

Bundan daha hızlı saf bir çözüm elde edilemediğinde, aşağıdaki çözümü önemli bir uyarı ile sağlarım: Giriş verilerinizde örnekler filldatavarsa, yanlış cevap alabilirsiniz.

def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Bu cevabı gerçekten sevmiyorum, ama çok daha hızlı. Döngü başına 124 us


Eğer Cı-tabakaya taşıyarak ~% 10-15 tarifi 3. için çalışma süresini azaltabilir (atlama itertoolsithalat; mapPY3 olmalıdır mapya da imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Son işleviniz bir nöbetçi kullanarak daha az kırılgan hale getirilebilir: fillvalueargümandan kurtulun ; ilk satırı ekleyin fillvalue = object(), ardından ifonay işaretini if i[-1] is fillvalue:ve kontrol ettiği satırı değiştirin yield tuple(v for v in i if v is not fillvalue). iterableDolgu değeri ile hiçbir değerin karıştırılamayacağını garanti eder.
ShadowRanger

BTW, # 4'te büyük başparmak. # 3 optimizasyonumu, şimdiye kadar yayınlananlardan daha iyi bir cevap (performans açısından) olarak göndermek üzereydim, ancak güvenilir kılmak için tweak ile # 4 optimize edilmiş # 3'ün iki katı daha hızlı çalışır; Python seviyesi döngülerle bir çözüm beklemiyordum (ve teorik algoritmik farklılıklar AFAICT) kazanmayı beklemiyordum. # 3 isliceNesneleri oluşturma / yineleme masrafı nedeniyle kaybeder ( ngöreceli olarak büyükse # 3 kazanır , örneğin grup sayısı azdır, ancak bu nadir bir durum için optimize edilir), ancak oldukça fazla olmasını beklemiyordum. aşırı.
ShadowRanger

# 4 için, koşulun ilk dalı yalnızca son yinelemede (son demet) alınır. Bunun yerine baştan nihai başlığın yeniden hazırlaması, o istenmeyen dolgu kapalı dilim üst ve kullanım orijinal iterable uzunluğunun modulo önbelleğe izip_longestnihai tupleda: yield i[:modulo]. Ayrıca, için argsdeğişken bir listenin yerine onu tuple: args = (iter(iterable),) * n. Birkaç saat döngüsünü keser. Son olarak, dolgu değerini göz ardı edersek ve varsayarsak None, koşullu if None in idaha fazla saat döngüsü için olabilir.
Kumba

1
@Kumba: İlk öneriniz girişin bilinen uzunluğa sahip olduğunu varsayar. Bilinen uzunlukta bir koleksiyon değil, bir yineleyici / jeneratörse, önbelleğe alınacak bir şey yoktur. Yine de böyle bir optimizasyonu kullanmak için gerçek bir neden yoktur; yieldsık karşılaşılan durumu (sonuncuyu ) optimize ederken, ortak durum etkilenmez.
ShadowRanger

10

Setler ve jeneratörlerle de çalışacak bir çözüme ihtiyacım vardı. Çok kısa ve güzel bir şey bulamadım, ama en azından oldukça okunabilir.

def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Liste:

>>> list(chunker([i for i in range(10)], 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Ayarlamak:

>>> list(chunker(set([i for i in range(10)]), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

Jeneratör:

>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]

8

Diğer tekliflere benzer, ancak tam olarak aynı değil, bu şekilde yapmayı seviyorum, çünkü basit ve okunması kolay:

it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

Bu şekilde son kısmi parçayı alamazsınız. Eğer almak istiyorsanız (9, None, None, None)son yığın olarak, sadece kullanmak izip_longestgelen itertools.


ile geliştirilebilirzip(*([it]*4))
Jean-François Fabre

@ Jean-François Fabre: okunabilirlik açısından bunu bir gelişme olarak görmüyorum. Ve aynı zamanda çok yavaş. Golf yaparken bir gelişme, ki ben değilim.
kriss

hayır Golf yapmıyorum, ama 10 argümanınız varsa? Bu yapıyı bazı resmi sayfalarda okudum. Ama elbette şu anda bulamıyorum :)
Jean-François Fabre

@ Jean-François Fabre: 10 argümanım veya değişken sayıda argümanım varsa, bu bir seçenek, ancak şunu yazmayı tercih ederim: zip (* (it,) * 10)
kriss

sağ! işte Okuduklarım. yaptığım liste şeyler değil :)
Jean-François Fabre

8

Harici bir paket kullanarak sakıncası yoksa sen kullanabilirsiniz iteration_utilities.grouperdan 1 . Tüm yinelemeleri destekler (sadece dizileri değil):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

hangi baskılar:

(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

Uzunluk, grup boyutunun katları değilse, sonuncuyu doldurmayı (eksik son grubu) veya kesmeyi (eksik son grubu atmayı) da destekler:

from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

Deneyler

Ayrıca, bu yaklaşımlardan bir kaçının çalışma zamanını karşılaştırmaya karar verdim. Farklı büyüklükteki bir listeye dayalı olarak "10" eleman gruplarına gruplanan bir log-log grafiğidir. Nitel sonuçlar için: Düşük, daha hızlı anlamına gelir:

resim açıklamasını buraya girin

En azından bu kriterde en iteration_utilities.grouperiyi performansı gösterir. Ardından Craz yaklaşımı geldi .

Karşılaştırma ölçütü 1 ile oluşturuldu . Bu karşılaştırmayı çalıştırmak için kullanılan kod:simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

def Craz_inner(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 Feragatname: Kütüphanelerin yazarıyım iteration_utilitiesve simple_benchmark.


7

Kimse bundan bahsetmediği için bir zip()çözüm var:

>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

Sadece dizinizin uzunluğu her zaman yığın boyutuna bölünebiliyorsa veya yoksa bir sonlu parça umursamıyorsanız çalışır.

Misal:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Veya liste yerine yineleyici döndürmek için itertools.izip komutunu kullanarak :

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Dolgu @ ΤΖΩΤΖΙΟΥ cevabı kullanılarak sabitlenebilir :

>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)

5

Zip () yerine map () kullanılması, JF Sebastian'ın yanıtındaki dolgu sorununu giderir:

>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Misal:

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

2
Bu, itertools.izip_longest(Py2) / itertools.zip_longest(Py3) ile daha iyi ele alınır ; bu kullanımı mapiki kez kullanımdan kaldırılmıştır ve Py3'te mevcut değildir ( Noneeşleyici işlevi olarak geçemezsiniz ve en kısa yinelenebilir tüketildiğinde, en uzun değil tükendiğinde durur; ped yapmaz).
ShadowRanger

4

Başka bir yaklaşım, aşağıdaki iki argüman biçimini kullanmak olacaktır iter:

from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Bu, dolguya kolayca adapte edilebilir (bu, Markus Jarderot'un cevabına benzer ):

from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Bunlar isteğe bağlı dolgu için bile birleştirilebilir:

_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

1
çünkü dolguyu atlatma seçeneğine sahipsiniz!
n611x007

3

Liste büyükse, bunu yapmanın en yüksek performansı bir jeneratör kullanmak olacaktır:

def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)

(MizardX'in itertools önerisinin işlevsel olarak buna eşdeğer olduğunu düşünüyorum.)
Robert Rossney

1
(Aslında, yansıma üzerine, hayır yapmıyorum. İtertools.islice bir yineleyici döndürür, ancak mevcut olanı kullanmaz.)
Robert Rossney

Güzel ve basit, ama hatta dönüştürme olmadan nedense üzerinde 4-7 kat daha yavaş kabul orfoz yöntemine göre daha tuplea iterable = range(100000000)& chunksize10000 kadar
Valentas

Ancak, genel olarak bu yöntemi tavsiye ederim, çünkü son öğeyi kontrol ederken kabul edilen son derece yavaş olabilir docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

Küçük işlevler ve şeyler kullanmak gerçekten bana çekici gelmiyor; Sadece dilimleri kullanmayı tercih ediyorum:

data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...

güzel ama bilinmeyen belirsiz bir akış için iyi değil len. itertools.repeatveya ile bir test yapabilirsiniz itertools.cycle.
n611x007

1
Ayrıca, bir sonraki öğeyi ve yedek belleği önemseyen bir jeneratör ifadesi kullanmak yerine fiziksel olarak bir liste oluşturmak için bir [...for...] liste kavrayışı kullandığından bellek yiyor(...for...)
n611x007

2

Bir listeye yapılan tüm dönüşümlerden kaçınmak import itertoolsve:

>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

üretir:

... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

Kontrol ettim groupbyve listeye veya kullanıma dönüşmüyorlen bu yüzden ( ) bu aslında kullanılana kadar her bir değerin çözünürlüğünü geciktirir. Ne yazık ki mevcut cevapların hiçbiri (şu anda) bu varyasyonu sunmuyor gibiydi.

Her öğeyi sırayla ele almanız gerekiyorsa, g için bir döngü için yuva yapın:

for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Bu konudaki özel ilgim gmail API'sine 1000'e kadar olan partilerdeki değişiklikleri göndermek için bir jeneratör kullanma ihtiyacıydı:

    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)

Parçaladığınız liste, artan tam sayı dizisinden başka bir şeyse ne olur?
PaulMcG

Bkz @PaulMcGuire GroupBy ; düzeni tanımlamak için bir fonksiyon verildiğinde, tekrarlanabilir unsurlar herhangi bir şey olabilir, değil mi?
John Mee

1
Evet, groupby'yi tanıyorum. Ancak mesajlar "ABCDEFG" harfleri groupby(messages, lambda x: x/3)olsaydı, 3 harfli gruplamalara değil, size bir TypeError (bir dizgiyi int ile bölmeye çalıştığınız için) verirdi. Şimdi yaptıysan bir groupby(enumerate(messages), lambda x: x[0]/3)şeyin olabilir. Ama bunu yazında söylemedin.
PaulMcG

2

NumPy ile basit:

ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

çıktı:

1 2
3 4
5 6
7 8

2
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()

2

Bir şeyi özlemediğim sürece, jeneratör ifadeleriyle aşağıdaki basit çözümden bahsedilmemiştir. Hem boyutun hem de parça sayısının bilindiğini (genellikle durumdur) ve dolgu gerektirmediğini varsayar :

def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))

1

İkinci yöntemde, bunu yaparak 4 sonraki gruba ilerlemek istiyorum:

ints = ints[4:]

Ancak, herhangi bir performans ölçümü yapmadım, bu yüzden hangisinin daha verimli olabileceğini bilmiyorum.

Bunu söyledikten sonra, genellikle ilk yöntemi seçerdim. Güzel değil, ama bu genellikle dış dünyayla etkileşimin bir sonucudur.


1

Yine başka bir cevap, avantajları:

1) Kolayca anlaşılabilir
2) Sadece diziler üzerinde değil, aynı zamanda (yukarıdaki cevaplardan bazıları dosya tutamaçlarında boğulur)
3) Yığını bir kerede belleğe yüklemez
4) bellekte aynı yineleyici
5) Listenin sonunda dolgu değerlerinde dolgu yok

Bununla birlikte, zamanlamayı yapmadım, bu yüzden daha akıllı yöntemlerden daha yavaş olabilir ve kullanım durumu göz önüne alındığında bazı avantajlar önemsiz olabilir.

def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Güncelleme:
İç ve dış döngülerin aynı yineleyiciden değerler çekmesi nedeniyle birkaç dezavantaj:
1) dış döngüde beklendiği gibi çalışmıyor - bir parçayı atlamak yerine bir sonraki öğeye devam ediyor . Ancak, dış döngüde test edilecek bir şey olmadığı için bu bir sorun gibi görünmüyor.
2) kırılma iç döngüde beklendiği gibi çalışmıyor - kontrol, yineleyicideki bir sonraki öğeyle tekrar iç döngüde sarılacaktır. Tüm parçaları atlamak için, iç yineleyiciyi (yukarıdaki ii) bir demet içine sarın, örneğin for c in tuple(ii)veya bir bayrak ayarlayıp yineleyiciyi boşaltın.


1
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist

+1 dolgu atlar; seninki ve bcoughlan 's çok benzer
n611x007

1

Funcy kütüphanesinden bölüm veya parçalar işlevini kullanabilirsiniz :

from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Bu işlevler ayrıca yineleyici sürümlerine sahiptir ipartitionve ichunksbu durumda daha verimli olacaktır.

Ayrıca uygulamalarına göz atabilirsiniz .


1

J.F. Sebastian Burada verilen çözüm hakkında :

def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

Zeki, ama bir dezavantajı var - her zaman tuple dön. Bunun yerine dize nasıl alınır?
Tabii ki yazabilirsiniz''.join(chunker(...)) , ancak geçici demet yine de inşa edilmiştir.

Geçici demetten kendiniz yazarak kurtulabilirsiniz, zipşöyle:

class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Sonra

def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

Örnek kullanım:

>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'

2
Cevabınızı değiştirmeniz için bir eleştiri değil, bir yorum: Kod bir yükümlülüktür. Ne kadar çok kod yazarsanız, hataların gizlenmesi için o kadar fazla alan yaratırsınız. Bu açıdan zip, mevcut olanı kullanmak yerine yeniden yazmak en iyi fikir değildir.
Alfe

1

Bu yaklaşımı seviyorum. Basit ve büyülü değil ve tüm yinelenebilir türleri destekler ve ithalat gerektirmez.

def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk

1

Parçalarımın yastıklı olmasını istemiyorum, bu yüzden bu şart çok önemli. Herhangi bir yinelenebilir üzerinde çalışma yeteneğinin de şart olduğunu düşünüyorum. Bunu kabul ederek, kabul edilen cevabı uzatmaya karar verdim, https://stackoverflow.com/a/434411/1074659 .

Yastıklı değerlerin karşılaştırılması ve filtrelenmesi nedeniyle dolgulama istenmiyorsa, performans bu yaklaşımda hafif bir darbe alır. Ancak, büyük yığın boyutları için, bu yardımcı program çok performanslıdır.

#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks

1

Jeneratörleri destekleyen ithalatı olmayan bir parça:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

Kullanım örneği:

>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]

1

Python 3.8 ile mors operatörünü ve itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

0

Bunu yapmanın güzel bir yolu yok gibi görünüyor. Aşağıda , aşağıdakiler de dahil olmak üzere çeşitli yöntemlerin bulunduğu bir sayfa bulunmaktadır:

def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq

0

Listeler aynı boyuttaysa, bunları 4 parçalı listelerle birleştirebilirsiniz zip(). Örneğin:

# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

İşte ne zip()fonksiyon üretir:

>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Listeler büyükse ve bunları daha büyük bir liste halinde birleştirmek istemiyorsanız, liste itertools.izip()yerine yineleyici üreten kullanın .

from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...

0

xBoyutta bir liste üzerinde yineleme yapmak için tek astarlı, adhoc çözüm 4-

for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.