Bir listeyi eşit boyutlu parçalara nasıl ayırırsınız?


2266

Keyfi uzunluk listesi var ve eşit boyutta parçalara bölmem ve üzerinde çalışmam gerekiyor. Bunu yapmak için bir sayaç ve iki liste tutmak gibi bazı bariz yollar vardır ve ikinci liste dolduğunda, ilk listeye ekleyin ve bir sonraki veri turu için ikinci listeyi boşaltın, ancak bu potansiyel olarak son derece pahalıdır.

Herkesin herhangi bir uzunlukta listeler için iyi bir çözüm olup olmadığını merak ediyordum, örneğin jeneratörler kullanarak.

Yararlı bir şey arıyordum itertoolsama açıkça faydalı bir şey bulamadım. Yine de kaçırmış olabilir.

İlgili soru: Parça listesindeki bir listeyi yinelemenin en “pythonic” yolu nedir?


1
Yeni bir yanıt göndermeden önce, bu soru için zaten 60+ yanıt olduğunu düşünün. Lütfen yanıtınızın mevcut yanıtlar arasında yer almayan bilgilere katkıda bulunduğundan emin olun.
janniks

Bir keyfi küçük nihai yığın önlemek isteyen kullanıcılar için, en bakmak Yarma yaklaşık olarak eşit uzunlukta N bölüme bir liste
wim

Yanıtlar:


3151

İşte istediğiniz parçaları veren bir jeneratör:

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in range(0, len(lst), n):
        yield lst[i:i + n]

import pprint
pprint.pprint(list(chunks(range(10, 75), 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]]

Python 2 kullanıyorsanız aşağıdakiler xrange()yerine kullanmalısınız range():

def chunks(lst, n):
    """Yield successive n-sized chunks from lst."""
    for i in xrange(0, len(lst), n):
        yield lst[i:i + n]

Ayrıca, bir işlev yazmak yerine liste kavrayışını da kullanabilirsiniz, ancak kodunuzun daha kolay anlaşılabilmesi için bu gibi işlemleri adlandırılmış işlevlerde kapsüllemek iyi bir fikirdir. Python 3:

[lst[i:i + n] for i in range(0, len(lst), n)]

Python 2 sürümü:

[lst[i:i + n] for i in xrange(0, len(lst), n)]

71
Listenin uzunluğunu söyleyemezsek ne olur? Bunu itertools.repeat ([1, 2, 3]) üzerinde deneyin, örn.
jespern

47
Bu, sorunun ilginç bir uzantısıdır, ancak orijinal soru, bir listede çalışmayı açıkça sordu.
Ned Batchelder

33
bu işlevlerin lanet standart kütüphanede olması gerekiyor
dgan

6
@Calimo: ne öneriyorsun? Size 47 elemanlı bir liste veriyorum. Nasıl "eşit büyüklükte parçalara" bölmek istersiniz? OP cevabı kabul etti, bu yüzden son farklı boyutta yığın ile açıkça sorun yok. Belki de İngilizce ifade kesin değildir?
Ned Batchelder

8
Lütfen değişkenlerinizi l olarak adlandırmayın, tam olarak 1'e benziyor ve kafa karıştırıcı. İnsanlar kodunuzu kopyalıyor ve bunun iyi olduğunu düşünüyorlar.
Yasen

555

Çok basit bir şey istiyorsanız:

def chunks(l, n):
    n = max(1, n)
    return (l[i:i+n] for i in range(0, len(l), n))

Python 2.x xrange()yerine kullanınrange()


6
Veya (bu özel işlevin farklı temsillerini yapıyorsak) lambda fonksiyonunu şu şekilde tanımlayabilirsiniz: lambda x, y: [x [i: i + y] (i, 0, len (x), y) aralığında ]. Bu liste anlama yöntemini seviyorum!
JP

4
döndükten sonra olmalı [, değil (
alwbtc

2
"Süper basit" anlamına gelir sonsuz döngüler hata ayıklamak zorunda değil - kudos için max().
Bob Stein

Bu çözüm hakkında basit bir şey yok
mit

1
@Nhoj_Gonk Hata bu sonsuz bir döngü değil, ancak parçalar (L, 0) max () olmadan bir ValueError değeri yükseltirdi. Bunun yerine, max () 1'den küçük herhangi bir şeyi 1'e dönüştürür.
Bob Stein

295

Doğrudan (eski) Python belgelerinden (itertools için tarifler):

from itertools import izip, chain, repeat

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

JFSebastian tarafından önerildiği gibi mevcut sürüm:

#from itertools import izip_longest as zip_longest # for Python 2.x
from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(n, iterable, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

Guido'nun zaman makinesi çalışmaları - çalıştı - çalışacak - çalışacak - sanırım tekrar çalışıyordu.

Bu çözümler işe yarar [iter(iterable)]*n(veya önceki sürümdeki eşdeğeri) listede tekrarlanan kez bir yineleyici oluşturur n. izip_longestdaha sonra etkili bir şekilde "her" yineleyicinin yuvarlak bir robin gerçekleştirir; Bu aynı yineleyici olduğundan, bu çağrıların her biri tarafından ilerletilir, bu da her bir zip-roundrobin'in bir tuple nöğe üretir .


@ninjagecko: list(grouper(3, range(10)))döner [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)]ve tüm tuples uzunluğu 3'tür. Lütfen yorumunuzu üzerinde durun , çünkü anlayamıyorum; bir şeye ne denir ve onu “şeyinizin 3'ün katı olmasını beklemek” te 3'ün katları olarak nasıl tanımlarsınız ? Şimdiden teşekkür ederim.
tzot

14
bunu geliştirdi çünkü jeneratörler üzerinde çalışıyor (len yok) ve genellikle daha hızlı itertools modülünü kullanıyor.
Michael Dillon

88
itertoolsBasit ve saf saf python uygulamasıyla karşılaştırıldığında bazı okunamayan çamurları ortaya çıkaran klasik fonksiyonel yaklaşımın klasik bir örneği
wim

15
@wim Bu yanıtın Python belgelerinden bir pasaj olarak başladığı göz önüne alındığında, bugs.python.org'da bir sorun açmanızı öneririm .
tzot

1
@pedrosaurio ise l==[1, 2, 3]o zaman f(*l)eşdeğerdir f(1, 2, 3). Bkz bu soruyu ve resmi belge .
tzot

225

Bunun biraz eski olduğunu biliyorum ama henüz kimse belirtmedi numpy.array_split:

import numpy as np

lst = range(50)
np.array_split(lst, 5)
# [array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
#  array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19]),
#  array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29]),
#  array([30, 31, 32, 33, 34, 35, 36, 37, 38, 39]),
#  array([40, 41, 42, 43, 44, 45, 46, 47, 48, 49])]

12
Bu, yığın başına öğe sayısını değil, toplam parça sayısını ayarlamanızı sağlar.
FizxMike

6
matematiği kendiniz yapabilirsiniz. 10 elementiniz varsa bunları 2, 5 element parçası veya beş 2 element parçası halinde gruplayabilirsiniz
Moj

24
+1 Bu, benim en sevdiğim çözüm, diziyi eşit boyutlu dizilere böldüğü için, diğer çözümler ise yok (baktığım diğer tüm çözümlerde, son dizi keyfi olarak küçük olabilir).
MiniQuark

@MiniQuark, ancak blok sayısı orijinal dizi boyutunun bir faktörü olmadığında ne yapar?
Baldrickk

1
@Baldrickk N öğelerini K parçalarına böldüğünüzde, ilk% N K parçasında N // K + 1 öğeleri olacaktır, geri kalanında da N // K öğeleri olacaktır. Örneğin, 108 öğe içeren bir diziyi 5 parçaya böldüğünüzde, ilk% 108 5 = 3 parça 108 // 5 + 1 = 22 öğe içerecek ve diğer parçaların sayısı 108 // 5 = 21 olacaktır. elementler.
MiniQuark

147

Sürpriz kimse kullanılmasını düşünmüştür olduğum iters' İki argüman formu :

from itertools import islice

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

Demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]

Bu, herhangi bir yinelenebilir ile çalışır ve tembel olarak çıktı üretir. Yineleyiciler yerine tuples döndürür, ancak yine de belirli bir zarafeti olduğunu düşünüyorum. Ayrıca ped yapmaz; Eğer dolgu istiyorsanız, yukarıdaki basit bir varyasyon yeterli olacaktır:

from itertools import islice, chain, repeat

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

Demo:

>>> list(chunk_pad(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk_pad(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Gibi izip_longesttabanlı çözümlerin, yukarıda hep pedleri. Bildiğim kadarıyla, isteğe bağlı olarak ped yapan bir işlev için bir veya iki satırlı itertools tarifi yoktur . Yukarıdaki iki yaklaşımı birleştirerek, bu oldukça yakınlaşır:

_no_padding = object()

def chunk(it, size, padval=_no_padding):
    if padval == _no_padding:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(padval))
        sentinel = (padval,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)

Demo:

>>> list(chunk(range(14), 3))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13)]
>>> list(chunk(range(14), 3, None))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, None)]
>>> list(chunk(range(14), 3, 'a'))
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 'a')]

Bu isteğe bağlı dolgu sunan önerilen en kısa chunker olduğuna inanıyorum.

Tomasz Gandor'un gözlemlediği gibi, iki dolgu parçası, uzun bir dizi ped değeriyle karşılaşırsa beklenmedik bir şekilde duracaktır. İşte bu soruna makul bir şekilde çalışan son bir varyasyon:

_no_padding = object()
def chunk(it, size, padval=_no_padding):
    it = iter(it)
    chunker = iter(lambda: tuple(islice(it, size)), ())
    if padval == _no_padding:
        yield from chunker
    else:
        for ch in chunker:
            yield ch if len(ch) == size else ch + (padval,) * (size - len(ch))

Demo:

>>> list(chunk([1, 2, (), (), 5], 2))
[(1, 2), ((), ()), (5,)]
>>> list(chunk([1, 2, None, None, 5], 2, None))
[(1, 2), (None, None), (5, None)]

7
Harika, basit versiyonun benim favorim. Diğerleri de temel islice(it, size)ifadeyi buldular ve bunu (yaptığım gibi) bir döngü yapısına gömdüler. Sadece iki argümanlı versiyonunu düşündünüz ( iter()tamamen farkında değildim), bu da onu süper zarif (ve muhtemelen en etkili performans) yapar. iterNöbetçi verildiğinde ilk argümanın 0 argüman işlevine değiştiğine dair hiçbir fikrim yoktu . Parçaların (pot. İnfinite) yineleyicisini döndürür, giriş olarak (pot. İnfinite) yineleyici kullanabilirsiniz, len()dizi dilimi var ve yok. Müthiş!
ThomasH

1
Bu yüzden sadece en iyi çifti taramak yerine cevapları okudum. Benim durumumda isteğe bağlı dolgu bir gereklilikti ve ben de onun iki argüman formunu öğrendim.
Kerr

Bunu iptal ettim, ama yine de - hadi overhype yapmayalım! Her şeyden önce, lambda kötü olabilir ( ityineleyiciye yavaş kapanır . İkincisi ve en önemlisi - padvalyinelenebilirinizde gerçekten bir yığın varsa ve işlenmelidir.
Tomasz Gandor

@TomaszGandor, ilk noktanı anladım! Benim anlayışım lambda'nın sıradan bir fonksiyondan daha yavaş olmamasına rağmen, elbette fonksiyon çağrısı ve kapatma aramasının bunu yavaşlatacağı konusunda haklısınız. Bunun göreceli performans vuruşunun izip_longestyaklaşıma karşı ne olacağını bilmiyorum , örneğin - bunun karmaşık bir değiş tokuş olabileceğinden şüpheleniyorum. Ama ... padvalsorun padvalparametre sunan her cevapta paylaşılmıyor mu?
18:38, senderle

1
@TomaszGandor, yeterince adil! Ancak bunu düzelten bir sürüm oluşturmak çok zor değildi. (Aynı zamanda, not kullanan ilk versiyon, bu ()sentinel olarak, yapar doğru iş olmasıdır. tuple(islice(it, size))Verimleri ()zaman itboştur.)
senderle

93

İşte keyfi yinelemeler üzerinde çalışan bir jeneratör:

def split_seq(iterable, size):
    it = iter(iterable)
    item = list(itertools.islice(it, size))
    while item:
        yield item
        item = list(itertools.islice(it, size))

Misal:

>>> import pprint
>>> pprint.pprint(list(split_seq(xrange(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

52
def chunk(input, size):
    return map(None, *([iter(input)] * size))

map(None, iter)eşittir izip_longest(iter).
Thomas Ahle

1
@TomaszWysocki Önünüzdeki *yineleyici demetini açıklayabilir misiniz? Muhtemelen cevap metninizde, ancak daha *önce Python'da bu şekilde kullanıldığını gördüm . Teşekkürler!
theJollySin

1
@theJollySin Bu bağlamda, splat operatörü olarak adlandırılır. Kullanımı burada açıklanmaktadır - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
13:14, rlms

2
Kapat, ancak son yığının doldurmak için Hiçbiri öğesi yok. Bu bir kusur olabilir veya olmayabilir. Gerçekten harika desen olsa.

49

Basit ama zarif

l = range(1, 1000)
print [l[x:x+10] for x in xrange(0, len(l), 10)]

veya isterseniz:

def chunks(l, n): return [l[x: x+n] for x in xrange(0, len(l), n)]
chunks(l, 10)

18
Arapça bir sayıya benzeyen bir değişkeni kopyalamayacaksınız. Bazı yazı tiplerinde 1ve layırt edilemez. Olduğu gibi 0ve O. Bazen de Ive 1.
Alfe

14
@Alfe Arızalı yazı tipleri. İnsanlar bu yazı tiplerini kullanmamalıdır. Programlama için değil, hiçbir şey için değil .
Jerry B

17
Lambdaların isimsiz fonksiyonlar olarak kullanılması amaçlanmıştır. Bunları böyle kullanmanın bir anlamı yok. Ayrıca, geri izleme hata durumunda "yığınlar" yerine "<lambda>" da rapor edeceğinden hata ayıklamayı daha da zorlaştırır. Bunların bir sürü varsa sorun bulma şansı diliyorum :)
Chris Koston

1
0 ve xrange içinde 1 olmamalıdırprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta

NOT: Python 3 kullanıcıları için kullanın range.
Christian Dean

40

Buradaki diğer cevapların eleştirisi:

Bu cevapların hiçbiri eşit boyutta değil, hepsi sonunda bir runt parçası bırakıyor, bu yüzden tamamen dengeli değiller. İşi dağıtmak için bu işlevleri kullanıyorsanız, birinin diğerinden önce bitirmesi olasılığını yerleşik hale getirmişsinizdir, bu yüzden diğerleri çok çalışmaya devam ederken hiçbir şey yapmadan etrafta oturacaktır.

Örneğin, mevcut en iyi yanıt şununla biter:

[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74]]

Sonunda sadece bu işten nefret ediyorum!

Diğerleri gibi list(grouper(3, xrange(7))), ve chunk(xrange(7), 3)de dönüp: [(0, 1, 2), (3, 4, 5), (6, None, None)]. NoneNakarat doldurma ve bence oldukça zarafetsiz edilir. Tekrarlanabilirleri eşit olarak parçalamazlar.

Bunları neden daha iyi bölemiyoruz?

Çözümlerim

İşte (yerine Python 3'te Not üretiminde kullanılan olduğunuz bir fonksiyonu uyarlanan dengeli bir çözüm, var xrangeolan range):

def baskets_from(items, maxbaskets=25):
    baskets = [[] for _ in xrange(maxbaskets)] # in Python 3 use range
    for i, item in enumerate(items):
        baskets[i % maxbaskets].append(item)
    return filter(None, baskets) 

Ve bir listeye koyarsanız aynı şeyi yapan bir jeneratör oluşturdum:

def iter_baskets_from(items, maxbaskets=3):
    '''generates evenly balanced baskets from indexable iterable'''
    item_count = len(items)
    baskets = min(item_count, maxbaskets)
    for x_i in xrange(baskets):
        yield [items[y_i] for y_i in xrange(x_i, item_count, baskets)]

Ve son olarak, yukarıdaki tüm işlevlerin (verilen gibi) bitişik bir sırayla öğeleri döndürdüğünü gördüğüm için:

def iter_baskets_contiguous(items, maxbaskets=3, item_count=None):
    '''
    generates balanced baskets from iterable, contiguous contents
    provide item_count if providing a iterator that doesn't support len()
    '''
    item_count = item_count or len(items)
    baskets = min(item_count, maxbaskets)
    items = iter(items)
    floor = item_count // baskets 
    ceiling = floor + 1
    stepdown = item_count % baskets
    for x_i in xrange(baskets):
        length = ceiling if x_i < stepdown else floor
        yield [items.next() for _ in xrange(length)]

Çıktı

Test etmek için:

print(baskets_from(xrange(6), 8))
print(list(iter_baskets_from(xrange(6), 8)))
print(list(iter_baskets_contiguous(xrange(6), 8)))
print(baskets_from(xrange(22), 8))
print(list(iter_baskets_from(xrange(22), 8)))
print(list(iter_baskets_contiguous(xrange(22), 8)))
print(baskets_from('ABCDEFG', 3))
print(list(iter_baskets_from('ABCDEFG', 3)))
print(list(iter_baskets_contiguous('ABCDEFG', 3)))
print(baskets_from(xrange(26), 5))
print(list(iter_baskets_from(xrange(26), 5)))
print(list(iter_baskets_contiguous(xrange(26), 5)))

Hangi çıktılar:

[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0], [1], [2], [3], [4], [5]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 8, 16], [1, 9, 17], [2, 10, 18], [3, 11, 19], [4, 12, 20], [5, 13, 21], [6, 14], [7, 15]]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19], [20, 21]]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'D', 'G'], ['B', 'E'], ['C', 'F']]
[['A', 'B', 'C'], ['D', 'E'], ['F', 'G']]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 5, 10, 15, 20, 25], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24]]
[[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]]

Bitişik jeneratörün diğer ikisi ile aynı uzunluk modellerinde parçalar sağladığına, ancak öğelerin hepsinin sırayla olduğuna ve ayrı elemanların bir listesini bölebileceği kadar eşit olarak bölünmüş olduğuna dikkat edin.


Yukarıdakilerin hiçbirinin eşit boyutlu parçalar sağlamadığını söylüyorsunuz. Ama bu bir yaptığı gibi, yapar bu bir .
senderle

1
@senderle, ilki, list(grouper(3, xrange(7)))ve ikincisi ise, chunk(xrange(7), 3)her iki dönüş: [(0, 1, 2), (3, 4, 5), (6, None, None)]. NoneNakarat doldurma ve bence oldukça zarafetsiz edilir. Tekrarlanabilirleri eşit olarak parçalamazlar. Oy verdiğin için teşekkürler!
Aaron Hall

4
Eşit büyüklükteki parçaların (mümkün değilse sonuncusu hariç) veya dengeli (mümkün olduğunca iyi) bir sonucun daha sık ihtiyaç duyulan şey olup olmadığı sorusunu (açıkça yapmadan, bunu şimdi yapıyorum) soruyorsunuz. Dengeli çözümün tercih edeceğini varsayıyorsunuz; programladığınız şey gerçek dünyaya yakınsa (örneğin, simüle edilmiş bir kart oyunu için kart dağıtma algoritması) bu doğru olabilir. Diğer durumlarda (satırları kelimelerle doldurmak gibi) satırları mümkün olduğunca dolu tutmak isteyecektir. Yani gerçekten birini diğerine tercih edemem; bunlar sadece farklı kullanım durumları içindir.
Alfe

@ ChristopherBarrington-Leigh İyi bir nokta, DataFrames için muhtemelen dilimleri kullanmalısınız, çünkü DataFrame nesnelerinin genellikle dilimleme üzerine kopyalanmadığına inanıyorum, örneğinimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHall Hata. Yorumumu sildim çünkü eleştirimi ikinci olarak tahmin ettim, ama hızlıca berabere kaldın. Teşekkürler! Aslında, veri çerçeveleri için işe yaramadığı iddiam doğrudur. Öğeler bir veri çerçevesi ise, son satır olarak yalnızca getiri öğeleri [range (x_i, item_count, baskets)] kullanın. İstediğiniz (minimum) grup boyutunu belirttiğiniz ayrı (yine başka) bir cevap sundum.
CPBL

38

Bu sorunun bir kopyasında en müthiş Python-ish cevabını gördüm :

from itertools import zip_longest

a = range(1, 16)
i = iter(a)
r = list(zip_longest(i, i, i))
>>> print(r)
[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

Herhangi bir n için n-tuple oluşturabilirsiniz. Eğer a = range(1, 15)öyleyse, sonuç şöyle olacaktır:

[(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, None)]

Liste eşit bir şekilde bölünür, o zaman yerini alabilir zip_longestile zipaksi üçlü, (13, 14, None)kaybedilecek. Python 3 yukarıda kullanılmıştır. Python 2 için kullanın izip_longest.


eğer listeniz ve parçalarınız kısaysa, bunu listenizi 1000 parçaya bölmek için nasıl uyarlayabilirsiniz? siz "zip kodunu kodlamayacaksınız (i, i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Tom Smith

9
zip(i, i, i, ... i)zip () için "chunk_size" argümanlarıyla zip(*[i]*chunk_size)bu iyi bir fikir olup olmadığı tartışmalıdır elbette tartışmalıdır.
Wilson F

1
Bunun dezavantajı, eşit olarak bölmüyorsanız, zip'leri en kısa sürede durduracağınız için öğeleri düşüreceksiniz - & izip_longest varsayılan öğeleri ekleyecektir.
Aaron Hall

zip_longestşu şekilde kullanılmalıdır: stackoverflow.com/a/434411/1959808
Ioannis Filippidis

range(1, 15)range(1, 15)
İle

35

Liste boyutunu biliyorsanız:

def SplitList(mylist, chunk_size):
    return [mylist[offs:offs+chunk_size] for offs in range(0, len(mylist), chunk_size)]

Yapmazsanız (bir yineleyici):

def IterChunks(sequence, chunk_size):
    res = []
    for item in sequence:
        res.append(item)
        if len(res) >= chunk_size:
            yield res
            res = []
    if res:
        yield res  # yield the last, incomplete, portion

İkinci durumda, dizinin her zaman verilen boyutta çok sayıda parça içerdiğinden emin olursanız daha güzel bir şekilde yeniden ifade edilebilir (yani, eksik son yığın yoktur).


Üzgünüm, bu şimdiye kadar gömüldü. IterChunks her şey için çalışır ve genel çözümdür ve bildiğim uyarılar yoktur.
Jason Dunkelberger

18

Toolz kütüphanesi vardır partitionbunun için işlevi:

from toolz.itertoolz.core import partition

list(partition(2, [1, 2, 3, 4]))
[(1, 2), (3, 4)]

Bu, tüm önerilerin en basitine benziyor. Sadece böyle bir bölüm işlevi almak için bir üçüncü taraf kitaplığı kullanmak zorunda gerçekten doğru olup olmadığını merak ediyorum. Bu bölme işleviyle eşdeğer bir şeyin dil yerleşik olarak var olmasını beklerdim.
kasperd

1
itertools ile bir bölüm yapabilirsiniz. ama toolz kütüphanesini seviyorum. fonksiyonel bir tarzda koleksiyonlar üzerinde çalışmak için clojure esinli bir kütüphane. değişmezlik elde edemezsiniz ama basit koleksiyonlar üzerinde çalışmak için küçük bir kelime haznesi elde edersiniz. Artı olarak, sitoolz cython ile yazılır ve güzel bir performans artışı elde eder. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
zach

Sondaki eğik çizgiyi atlarsanız zach'ın yorumundan
mit


16

Python doc'ın tzot ve JFSebastian tarafından önerilen sürümünü çok seviyorum, ancak iki eksikliği var:

  • çok açık değil
  • Genellikle son yığınta bir dolgu değeri istemiyorum

Kodumda bu bir çok kullanıyorum:

from itertools import islice

def chunks(n, iterable):
    iterable = iter(iterable)
    while True:
        yield tuple(islice(iterable, n)) or iterable.next()

GÜNCELLEME: Tembel parçalar sürümü:

from itertools import chain, islice

def chunks(n, iterable):
   iterable = iter(iterable)
   while True:
       yield chain([next(iterable)], islice(iterable, n-1))

while TrueDöngü için mola durumu nedir ?
wjandrea

@wjandrea: Boş StopIterationolduğunda tupleve iterable.next()çalıştırıldığında ortaya çıkar. Modern Python'da düzgün bir şekilde çalışmaz, bir jeneratörden çıkılmalı return, yükseltilmemelidir StopIteration. Bir try/except StopIteration: returnbütün döngü (ve değişen çevresinde iterable.next()için next(iterable)çapraz sürüm COMPAT için) en az asgari yük ile bu giderir.
ShadowRanger

15
[AA[i:i+SS] for i in range(len(AA))[::SS]]

AA dizi olduğunda, SS yığın boyutudur. Örneğin:

>>> AA=range(10,21);SS=3
>>> [AA[i:i+SS] for i in range(len(AA))[::SS]]
[[10, 11, 12], [13, 14, 15], [16, 17, 18], [19, 20]]
# or [range(10, 13), range(13, 16), range(16, 19), range(19, 21)] in py3

2
en iyisi ve basit.
F.Tamy

2
kısa ve basit. karmaşıklık üzerinde basitlik.
dkrynicki

15

Farklı yaklaşımların performansını merak ettim ve işte burada:

Python 3.5.1 üzerinde test edilmiştir

import time
batch_size = 7
arr_len = 298937

#---------slice-------------

print("\r\nslice")
start = time.time()
arr = [i for i in range(0, arr_len)]
while True:
    if not arr:
        break

    tmp = arr[0:batch_size]
    arr = arr[batch_size:-1]
print(time.time() - start)

#-----------index-----------

print("\r\nindex")
arr = [i for i in range(0, arr_len)]
start = time.time()
for i in range(0, round(len(arr) / batch_size + 1)):
    tmp = arr[batch_size * i : batch_size * (i + 1)]
print(time.time() - start)

#----------batches 1------------

def batch(iterable, n=1):
    l = len(iterable)
    for ndx in range(0, l, n):
        yield iterable[ndx:min(ndx + n, l)]

print("\r\nbatches 1")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#----------batches 2------------

from itertools import islice, chain

def batch(iterable, size):
    sourceiter = iter(iterable)
    while True:
        batchiter = islice(sourceiter, size)
        yield chain([next(batchiter)], batchiter)


print("\r\nbatches 2")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in batch(arr, batch_size):
    tmp = x
print(time.time() - start)

#---------chunks-------------
def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in range(0, len(l), n):
        yield l[i:i + n]
print("\r\nchunks")
arr = [i for i in range(0, arr_len)]
start = time.time()
for x in chunks(arr, batch_size):
    tmp = x
print(time.time() - start)

#-----------grouper-----------

from itertools import zip_longest # for Python 3.x
#from six.moves import zip_longest # for both (uses the six compat library)

def grouper(iterable, n, padvalue=None):
    "grouper(3, 'abcdefg', 'x') --> ('a','b','c'), ('d','e','f'), ('g','x','x')"
    return zip_longest(*[iter(iterable)]*n, fillvalue=padvalue)

arr = [i for i in range(0, arr_len)]
print("\r\ngrouper")
start = time.time()
for x in grouper(arr, batch_size):
    tmp = x
print(time.time() - start)

Sonuçlar:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
modül kullandığımızda timekütüphane kullanarak kıyaslama harika bir fikir değiltimeit
Azat Ibrakov

13

kod:

def split_list(the_list, chunk_size):
    result_list = []
    while the_list:
        result_list.append(the_list[:chunk_size])
        the_list = the_list[chunk_size:]
    return result_list

a_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

print split_list(a_list, 3)

sonuç:

[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]

12

Kütüphanenin get_chunksişlevini şu şekilde de kullanabilirsiniz utilspie:

>>> from utilspie import iterutils
>>> a = [1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> list(iterutils.get_chunks(a, 5))
[[1, 2, 3, 4, 5], [6, 7, 8, 9]]

utilspiePip ile kurabilirsiniz :

sudo pip install utilspie

Feragatname: Utilspie kütüphanesinin yaratıcısıyım .


11

Bu noktada, her durumda, özyinelemeli bir jeneratöre ihtiyacımız olduğunu düşünüyorum ...

Python 2'de:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

Python 3'te:

def chunks(li, n):
    if li == []:
        return
    yield li[:n]
    yield from chunks(li[n:], n)

Ayrıca, büyük Uzaylı istilası durumunda, dekore edilmiş bir özyinelemeli jeneratör kullanışlı olabilir:

def dec(gen):
    def new_gen(li, n):
        for e in gen(li, n):
            if e == []:
                return
            yield e
    return new_gen

@dec
def chunks(li, n):
    yield li[:n]
    for e in chunks(li[n:], n):
        yield e

9

Python 3.8'deki Atama İfadeleri ile oldukça güzel olur:

import itertools

def batch(iterable, size):
    it = iter(iterable)
    while item := list(itertools.islice(it, size)):
        yield item

Bu, sadece bir liste üzerinde değil, keyfi bir yinelenebilir üzerinde çalışır.

>>> import pprint
>>> pprint.pprint(list(batch(range(75), 10)))
[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
 [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]]

1
Şimdi bu soruya layık yeni bir cevap. Aslında bunu çok seviyorum. Ödev ifadeleri konusunda şüpheliyim, ama işe yaradıklarında işe yarıyorlar.
juanpa.arrivillaga

7

heh, bir satır versiyonu

In [48]: chunk = lambda ulist, step:  map(lambda i: ulist[i:i+step],  xrange(0, len(ulist), step))

In [49]: chunk(range(1,100), 10)
Out[49]: 
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

36
Lütfen "chunk = lambda" yerine "def chunk" kullanın. Aynı şekilde çalışır. Tek çizgi. Aynı özellikler. N00bz okumak ve anlamak ÇOK daha kolay.
S.Lott

4
@ S.Lott: n00bz şemadan geliyorsa: P bu gerçek bir sorun değildir. Google'da bir anahtar kelime bile var! n00bz uğruna kaçındığımız diğer özellikler nelerdir? Sanırım verim de o zaman n00b dostu olmak için yeterli / c gibi değil.
Janus Troelsen

16
Elde edilen işlevi nesnesi def chunkyerine chunk=lambda.__ name__ özelliği, 'yığın' yerine '<lamda>' sahiptir. Belirli ad geri izlemelerde daha kullanışlıdır.
Terry Jan Reedy

1
@Alfe: Ana anlamsal fark olarak adlandırılıp adlandırılamayacağından emin değilim, ancak geri izlemede yararlı bir ad olup olmadığı en <lamba>azından dikkate değer bir farktır.
martineau

1
Bir demet performans için test ettikten sonra, bu harika!
Sunny Patel

7
def split_seq(seq, num_pieces):
    start = 0
    for i in xrange(num_pieces):
        stop = start + len(seq[i::num_pieces])
        yield seq[start:stop]
        start = stop

kullanımı:

seq = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for seq in split_seq(seq, 3):
    print seq

7

Başka bir daha açık sürüm.

def chunkList(initialList, chunkSize):
    """
    This function chunks a list into sub lists 
    that have a length equals to chunkSize.

    Example:
    lst = [3, 4, 9, 7, 1, 1, 2, 3]
    print(chunkList(lst, 3)) 
    returns
    [[3, 4, 9], [7, 1, 1], [2, 3]]
    """
    finalList = []
    for i in range(0, len(initialList), chunkSize):
        finalList.append(initialList[i:i+chunkSize])
    return finalList

(2016 Eyl 12) Bu cevap, dilden bağımsız ve okunması en kolay olanıdır.
D Adams

7

Büyük listeler için iyi olan len () öğesini çağırmadan:

def splitter(l, n):
    i = 0
    chunk = l[:n]
    while chunk:
        yield chunk
        i += n
        chunk = l[i:i+n]

Ve bu yinelenebilirler için:

def isplitter(l, n):
    l = iter(l)
    chunk = list(islice(l, n))
    while chunk:
        yield chunk
        chunk = list(islice(l, n))

Yukarıdakilerin fonksiyonel lezzeti:

def isplitter2(l, n):
    return takewhile(bool,
                     (tuple(islice(start, n))
                            for start in repeat(iter(l))))

VEYA:

def chunks_gen_sentinel(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return iter(imap(tuple, continuous_slices).next,())

VEYA:

def chunks_gen_filter(n, seq):
    continuous_slices = imap(islice, repeat(iter(seq)), repeat(0), repeat(n))
    return takewhile(bool,imap(tuple, continuous_slices))

16
len()Büyük listelerden kaçınmak için hiçbir neden yoktur ; sabit zamanlı bir işlemdir.
Thomas Wouters

7

İşte ek yaklaşımların bir listesi:

verilmiş

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

kod

Standart Kütüphane

list(it.zip_longest(*[iter(iterable)] * n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

d = {}
for i, x in enumerate(iterable):
    d.setdefault(i//n, []).append(x)

list(d.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

dd = ct.defaultdict(list)
for i, x in enumerate(iterable):
    dd[i//n].append(x)

list(dd.values())
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

more_itertools+

list(mit.chunked(iterable, n))
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]

list(mit.sliced(iterable, n))
# [range(0, 3), range(3, 6), range(6, 9), range(9, 11)]

list(mit.grouper(n, iterable))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

list(mit.windowed(iterable, len(iterable)//n, step=n))
# [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, None)]

Referanslar

+ Itertools tarifleri ve daha fazlasını uygulayan bir üçüncü taraf kütüphanesi .> pip install more_itertools


6

Bu referansa bakın

>>> orange = range(1, 1001)
>>> otuples = list( zip(*[iter(orange)]*10))
>>> print(otuples)
[(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ... (991, 992, 993, 994, 995, 996, 997, 998, 999, 1000)]
>>> olist = [list(i) for i in otuples]
>>> print(olist)
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ..., [991, 992, 993, 994, 995, 996, 997, 998, 999, 1000]]
>>> 

Python3


3
Güzel, ancak boyut tüm parça sayılarıyla eşleşmezse öğelerin sonuna düşer, örn. zip(*[iter(range(7))]*3)Yalnızca girişi döndürür [(0, 1, 2), (3, 4, 5)]ve unutur 6.
Alfe

6

Buradaki herkes yineleyiciler hakkında konuştuğundan beri. boltonsbunun için mükemmel bir yönteme sahiptir iterutils.chunked_iter.

from boltons import iterutils

list(iterutils.chunked_iter(list(range(50)), 11))

Çıktı:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 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]]

Ancak hafızada merhamet etmek istemiyorsanız, eski yolu kullanabilir ve dolu olanı listilk etapta saklayabilirsiniz iterutils.chunked.


Ve bu aslında bir alt yükleniciler bakar sırasına bakılmaksızın çalışır !!
Peter Gerdes

6

Bir çözüm daha

def make_chunks(data, chunk_size): 
    while data:
        chunk, data = data[:chunk_size], data[chunk_size:]
        yield chunk

>>> for chunk in make_chunks([1, 2, 3, 4, 5, 6, 7], 2):
...     print chunk
... 
[1, 2]
[3, 4]
[5, 6]
[7]
>>> 

5
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]]'

1
Bu, itertools tabanlı yanıtların çoğu kadar kısa veya güzel görünmese de, ilkine erişmeden önce ikinci alt listeyi yazdırmak istiyorsanız, bu gerçekten işe yarar, yani i0 = next (g2); i1 = aşağıdaki (g2); ve i0 kullanmadan önce i1 kullanın ve kırılmaz !!
Peter Gerdes

5

Matplotlib.cbook kullanmayı düşünün parçalarını

Örneğin:

import matplotlib.cbook as cbook
segments = cbook.pieces(np.arange(20), 3)
for s in segments:
     print s

Görünüşe göre yanlışlıkla iki hesap oluşturdunuz. Sen edebilirsiniz ekibine başvurun onlara Katkılarınız üzerinde doğrudan düzenleme ayrıcalıkları geri alma olanağı sağlayacak olan birleştirilmiş olması.
Georgy
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.