चंक्स में एक सूची पर पुनरावृति करने के लिए सबसे "पायथोनिक" तरीका क्या है?


487

मेरे पास एक पायथन स्क्रिप्ट है जो इनपुट की एक पूर्णांक की सूची के रूप में लेती है, जिसे मुझे एक समय में चार पूर्णांक के साथ काम करने की आवश्यकता होती है। दुर्भाग्य से, मेरे पास इनपुट का नियंत्रण नहीं है, या मैं इसे चार-तत्व ट्यूपल्स की सूची के रूप में पारित कर सकता हूं। वर्तमान में, मैं इसे इस तरह से देख रहा हूँ:

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]

यह "सी-थिंक" जैसा दिखता है, हालांकि, जो मुझे संदेह करता है कि इस स्थिति से निपटने का एक अधिक पायथोनिक तरीका है। सूची को पुनरावृत्ति के बाद छोड़ दिया गया है, इसलिए इसे संरक्षित करने की आवश्यकता नहीं है। शायद ऐसा कुछ बेहतर होगा?

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

अभी भी काफी "महसूस" सही नहीं है, हालांकि। : - /

संबंधित प्रश्न: आप पायथन में समान रूप से आकार की एक सूची को कैसे विभाजित करते हैं?


3
यदि आपका सूची आकार चार से अधिक नहीं है तो आपका कोड काम नहीं करता है।
पेड्रो हेनरिक्स

5
मैं सूची का विस्तार कर रहा हूं (ताकि यह लंबाई हो, इससे पहले कि यह अभी तक हो, चार की एक बहु है।
बेन ब्लैंक

4
@ @ - प्रश्न बहुत समान हैं, लेकिन बहुत अधिक डुप्लिकेट नहीं हैं। यह "आकार एन के किसी भी संख्या में विभाजित है" बनाम "किसी भी आकार के एन विखंडू में विभाजित"। :-)
बेन ब्लैंक


जवाबों:


339

पायथन के इटर्स्टूल डॉक्स के व्यंजनों अनुभाग से संशोधित :

from itertools import zip_longest

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

उदाहरण
छद्म में उदाहरण को रखने के लिए।

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

नोट: पायथन 2 के izip_longestबजाय का उपयोग करें zip_longest


67
अंत में एक अजगर सत्र में इसके साथ खेलने का मौका मिला। उन लोगों के लिए जो मैं जितना भ्रमित था, यह एक ही पुनरावृत्ति को कई बार izip_longest को खिला रहा है, जिससे यह अलग अनुक्रमों से धारीदार मूल्यों के बजाय एक ही अनुक्रम के क्रमिक मूल्यों का उपभोग करता है। मुझे यह पसंद है!
बेन ब्लैंक

6
फ़िलवेल्यू को वापस फ़िल्टर करने का सबसे अच्छा तरीका क्या है? (ग्रूपर (iterable) में मदों के लिए आइटम में आइटम के लिए आइटम भरता नहीं है]
गत

14
मुझे संदेह है कि 256k आकार के चनों के लिए इस ग्रॉपर नुस्खा का प्रदर्शन बहुत खराब izip_longestहोगा , क्योंकि 256k तर्क खिलाए जाएंगे।
अनातोली टेकटोनिक

13
कई जगह टिप्पणीकारों का कहना है "जब मैंने आखिरकार यह कैसे काम किया ...." तो शायद थोड़ा स्पष्टीकरण की आवश्यकता है। विशेष रूप से पुनरावृत्तियों पहलू की सूची।
लंदनरॉब ऑग

6
वहाँ इस का उपयोग करने के लिए एक रास्ता है, लेकिन Noneअंतिम हिस्सा भरने के बिना है ?
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)

सरल। आसान। तेज। किसी भी अनुक्रम के साथ काम करता है:

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 का संस्करण किसी भी चलने के लिए काम करता है (उपरोक्त कोड के रूप में सिर्फ अनुक्रम नहीं); यह संक्षिप्त है और शायद उतनी ही तेजी से या इससे भी तेज। हालांकि यह itertoolsमॉड्यूल से अपरिचित लोगों के लिए थोड़ा अस्पष्ट (अस्पष्ट) हो सकता है ।
jfs

1
माना। यह सबसे सामान्य और पाइथोनिक तरीका है। स्पष्ट और संक्षिप्त। (और ऐप इंजन पर काम करता है)
मैट विलियमसन

3
ध्यान दें कि chunkerएक रिटर्न generatorreturn [...]एक सूची प्राप्त करने के लिए: को बदले में बदलें ।
Dror

11
एक समारोह के निर्माण लेखन और फिर एक जनरेटर लौटने के बजाय, आप भी सीधे एक जनरेटर लिख सकता है, का उपयोग करते हुए yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]। मुझे यकीन नहीं है कि अगर आंतरिक रूप से यह किसी भी प्रासंगिक पहलू में किसी भी तरह से संभाला जाएगा, लेकिन यह एक छोटा सा स्पष्ट भी हो सकता है।
अल्फ

3
ध्यान दें कि यह केवल उन अनुक्रमों के लिए काम करता है जो सूचकांक द्वारा वस्तुओं तक पहुंच का समर्थन करते हैं और सामान्य पुनरावृत्तियों के लिए काम नहीं करेंगे, क्योंकि वे __getitem__विधि का समर्थन नहीं कर सकते हैं ।
अपोलोव

135

मैं एक प्रशंसक हूं

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

यह कैसे व्यवहार करता है अगर len (ints) chunkSize का एक बहु नहीं है?
PlsWork

3
@AnnaVopureta chunk तत्वों के अंतिम बैच के लिए 1, 2 या 3 तत्व होंगे। यह सवाल देखें कि स्लाइस इंडेक्स सीमा से बाहर क्यों हो सकते हैं
बोरिस

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)

दूसरा रास्ता:

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
+1 जनरेटर का उपयोग करने के लिए, सभी सुझाए गए समाधानों में से सबसे "पायथोनिक" जैसे सीम
सर्गेई गोलोवचेंको

7
यह बहुत आसान है, लेकिन यह बहुत आसान है, जो बहुत आसान नहीं है। मैं एस। लोट का संस्करण पसंद करता हूं
zenazn

4
@zenazn: यह जनरेटर उदाहरणों पर काम करेगा, टुकड़ा करने की क्रिया
Janus Troelsen

जनरेटर और अन्य गैर-स्लेसेलेबल पुनरावृत्तियों के साथ ठीक से काम करने के अलावा, पहले समाधान को भी "भराव" मूल्य की आवश्यकता नहीं होती है अगर अंतिम चंक की तुलना में छोटा होता है size, जो कभी-कभी वांछनीय होता है।
डानो

1
जनरेटर के लिए भी +1। अन्य समाधानों में lenकॉल की आवश्यकता होती है और इसलिए अन्य जनरेटर पर काम नहीं करते हैं।
17

12
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)

यह करने के लिए एक पठनीय तरीका है stackoverflow.com/questions/434287/…
jfs

ध्यान दें कि अजगर 3 izip_longestमें प्रतिस्थापित किया गया हैzip_longest
mdmjsh

11

इस समस्या का आदर्श समाधान पुनरावृत्तियों (केवल अनुक्रमों) के साथ काम करता है। यह भी तेज होना चाहिए।

यह itertools के लिए प्रलेखन द्वारा प्रदान किया गया समाधान है:

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

%timeitमेरे मैक बुक एयर पर ipython का उपयोग करते हुए , मुझे प्रति लूप 47.5 मिलता है।

हालाँकि, यह वास्तव में मेरे लिए काम नहीं करता है क्योंकि परिणाम समूह के आकार के होते हैं। पैडिंग के बिना एक समाधान थोड़ा अधिक जटिल है। सबसे भोला समाधान हो सकता है:

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

सरल, लेकिन बहुत धीमा: 693 हमें प्रति पाश

सबसे अच्छा समाधान मैं isliceआंतरिक पाश के लिए उपयोग के साथ आ सकता है :

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

समान डेटासेट के साथ, मुझे 305 प्रति लूप मिलता है।

शुद्ध समाधान प्राप्त करने में किसी भी तेजी से, मैं एक महत्वपूर्ण चेतावनी के साथ निम्नलिखित समाधान प्रदान करने में असमर्थ: यदि आपके इनपुट डेटा में इसके उदाहरण हैं filldata, तो आपको गलत उत्तर मिल सकता है।

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

मुझे वास्तव में यह उत्तर पसंद नहीं है, लेकिन यह काफी तेज है। 124 हमें प्रति पाश


आप इसे नुस्खा के लिए # 3 से रनटाइम कम कर सकते हैं ~ 10-15% तक इसे सी लेयर में ले जा सकते हैं ( itertoolsआयात को छोड़ना mapचाहिए ; Py3 mapया होना चाहिए imap) def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))):। आपके अंतिम कार्य को एक प्रहरी का उपयोग करके कम भंगुर बनाया जा सकता है: fillvalueतर्क से छुटकारा पाएं ; पहली पंक्ति जोड़ें fillvalue = object(), फिर ifचेक को if i[-1] is fillvalue:उस पंक्ति में बदलें जिसे वह नियंत्रित करता है yield tuple(v for v in i if v is not fillvalue)। कोई मान नहीं है iterableकि भराव मूल्य के लिए गलत हो सकता है।
शैडो रेंजर

BTW, बड़े अंगूठे # 4 पर। मैं अब तक जो पोस्ट किया गया था, उससे बेहतर उत्तर (प्रदर्शन-वार) के रूप में # 3 के अपने अनुकूलन को पोस्ट करने वाला था, लेकिन इसे विश्वसनीय बनाने के लिए ट्विस्ट के साथ, # 4 के रूप में अनुकूलित # 3 के रूप में तेजी से दो बार # 4 रन; मुझे जीत के लिए पायथन लेवल लूप्स (और कोई सैद्धांतिक एल्गोरिदम अंतर AFAICT) के साथ समाधान की उम्मीद नहीं थी। मेरा मानना ​​है कि isliceवस्तुओं के निर्माण / पुनरावृति के खर्च के कारण # 3 हारता है (# 3 जीतता है अगर nअपेक्षाकृत बड़ी है, जैसे समूहों की संख्या छोटी है, लेकिन यह एक असामान्य मामले के लिए अनुकूलन है), लेकिन मुझे उम्मीद नहीं थी कि यह काफी होगा चरम।
शैडो रेंजर

# 4 के लिए, सशर्त की पहली शाखा को केवल अंतिम पुनरावृत्ति (अंतिम ट्यूपल) पर लिया जाता है। अंतिम टूपल को फिर से समेटने के बजाय, शीर्ष पर मूल पुनरावृत्ति की लंबाई के मोडुलो को कैश करें और izip_longestअंतिम ट्यूपल पर अवांछित पैडिंग को बंद करने के लिए उपयोग करें yield i[:modulo]:। इसके अलावा, argsचर के लिए, एक सूची के बजाय इसे टैप करें args = (iter(iterable),) * n:। कुछ और घड़ी चक्र बंद करता है। अंतिम, अगर हम भरण और उपेक्षा को अनदेखा करते हैं None, तो सशर्त if None in iऔर भी अधिक घड़ी चक्रों के लिए बन सकता है।
कुंभा

1
@ कुंभ: आपका पहला सुझाव मानता है कि इनपुट की लंबाई ज्ञात है। यदि यह एक इट्रेटर / जनरेटर है, तो ज्ञात लंबाई के साथ संग्रह नहीं, कैश करने के लिए कुछ भी नहीं है। वैसे भी इस तरह के अनुकूलन का उपयोग करने का कोई वास्तविक कारण नहीं है; आप असामान्य मामले (अंतिम yield) का अनुकूलन कर रहे हैं , जबकि सामान्य मामला अप्रभावित है।
शैडो रेंजर

10

मुझे एक समाधान की आवश्यकता थी जो सेट और जनरेटर के साथ भी काम करेगा। मैं बहुत कम और सुंदर कुछ भी नहीं कर सका, लेकिन यह कम से कम काफी पठनीय है।

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

सूची:

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

सेट:

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

जनरेटर:

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

8

अन्य प्रस्तावों के समान, लेकिन बिल्कुल समान नहीं, मुझे यह इस तरह करना पसंद है, क्योंकि यह सरल और पढ़ने में आसान है:

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)

इस तरह आपको अंतिम आंशिक हिस्सा नहीं मिलेगा। आप प्राप्त करना चाहते हैं (9, None, None, None)पिछले हिस्सा के रूप में, बस का उपयोग izip_longestसे itertools


के साथ सुधार किया जा सकता हैzip(*([it]*4))
जीन-फ्रैंकोइस फैबरे

@ जीन-फ्रांस्वा फेबरे: पढ़ने की दृष्टि से मैं इसे सुधार के रूप में नहीं देखता। और यह थोड़ा धीमा भी है। यदि आप गोल्फिंग कर रहे हैं तो यह एक सुधार है, जो मैं नहीं हूं।
kriss

नहीं, मैं गोल्फ नहीं कर रहा हूँ, लेकिन क्या होगा अगर आपके पास 10 तर्क हैं? मैं निश्चित रूप से कुछ सरकारी page.but में है कि निर्माण को पढ़ने मैं अभी यह पता लगाने के लिए :) नहीं कर पा रहे
जीन फ़्राँस्वा Fabre

@ जीन-फ्रांकोइस फेब्रे: अगर मेरे पास 10 तर्क हैं, या एक तर्क संख्या है, तो यह एक विकल्प है, लेकिन मैं लिखूंगा: zip (* (यह), * 10)
kriss

सही! यही मैंने पढ़ा है। नहीं सूची सामान है कि मैं ऊपर कर दिया है :)
जीन फ़्राँस्वा Fabre

8

अगर आपको बाहरी पैकेज का उपयोग करने में कोई आपत्ति नहीं है तो आप 1iteration_utilities.grouper से उपयोग कर सकते हैं । यह सभी पुनरावृत्तियों का समर्थन करता है (केवल अनुक्रम नहीं):iteration_utilties

from iteration_utilities import grouper
seq = list(range(20))
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, 17, 18, 19)

यदि लंबाई एक समूह में से एक नहीं है, तो यह अंतिम (अधूरा अंतिम समूह) भरने या ट्रंकटिंग (अधूरे अंतिम समूह को छोड़ने) का समर्थन करता है।

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)

मानक

मैंने कुछ उल्लिखित दृष्टिकोणों के रन-टाइम की तुलना करने का भी निर्णय लिया। यह अलग-अलग आकार की सूची के आधार पर "10" तत्वों के समूहों में एक लॉग-लॉग प्लॉट है। गुणात्मक परिणामों के लिए: निचले का अर्थ है तेज:

यहाँ छवि विवरण दर्ज करें

कम से कम इस बेंचमार्क में iteration_utilities.grouperसबसे अच्छा प्रदर्शन होता है। Craz के दृष्टिकोण द्वारा अनुसरण किया गया ।

बेंचमार्क 1 के साथ बनाया गया था । इस बेंचमार्क को चलाने के लिए इस्तेमाल किया गया कोड था: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 अस्वीकरण: मैं पुस्तकालयों के लेखक हूँ iteration_utilitiesऔर simple_benchmark


7

चूंकि किसी ने भी इसका उल्लेख नहीं किया है, इसलिए यहां zip()समाधान है:

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

यह केवल तभी काम करता है जब आपके अनुक्रम की लंबाई हमेशा चंक आकार से विभाज्य होती है या आप एक अनुगामी चक के बारे में परवाह नहीं करते हैं यदि यह नहीं है।

उदाहरण:

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

या itertools.izip का उपयोग करके सूची के बजाय एक पुनरावृत्तिकर्ता को लौटाएँ :

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

@ Answer के उत्तर का उपयोग करके पैडिंग को ठीक किया जा सकता है :

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

ज़िप () के बजाय मानचित्र () का उपयोग करना JF सेबेस्टियन के उत्तर में पैडिंग समस्या को ठीक करता है:

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

उदाहरण:

>>> 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
यह itertools.izip_longest(Py2) / itertools.zip_longest(Py3) के साथ बेहतर तरीके से संचालित होता है ; इसका उपयोग mapदोगुना-घटाया गया है, और Py3 में उपलब्ध नहीं है (आप Noneमैपर फ़ंक्शन के रूप में पारित नहीं कर सकते हैं , और यह तब बंद हो जाता है जब सबसे छोटी चलने वाली थकावट समाप्त हो जाती है, सबसे लंबी नहीं; यह पैड नहीं है)।
शैडो रेंजर

4

एक अन्य दृष्टिकोण दो-तर्क के रूप का उपयोग करना होगा iter:

from itertools import islice

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

पैडिंग का उपयोग करने के लिए इसे आसानी से अनुकूलित किया जा सकता है (यह मार्कस जार्डरॉट के उत्तर के समान है ):

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)

इन्हें वैकल्पिक पैडिंग के लिए भी जोड़ा जा सकता है:

_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
बेहतर है क्योंकि आपके पास पैडिंग को छोड़ देने का विकल्प है!
n611x007

3

यदि सूची बड़ी है, तो ऐसा करने के लिए सबसे अधिक प्रदर्शन वाला तरीका जनरेटर का उपयोग करना होगा:

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 के itertools सुझाव कार्यात्मक रूप से इसके बराबर है।)
रॉबर्ट रॉसनी

1
(वास्तव में, प्रतिबिंब पर, नहीं, मैं नहीं करता हूं। इटर्टूलेसिसिस एक पुनरावृत्ति रिटर्न देता है, लेकिन यह एक मौजूदा का उपयोग नहीं करता है।)
रॉबर्ट रॉनी

यह अच्छा और सरल है, लेकिन किसी कारण से भी बिना रूपांतरण के 4-7 गुना धीमी गति से स्वीकार किए जाने वाले विधि की तुलना में 10000 तक iterable = range(100000000)और chunksizeऊपर तक गिर जाता है।
वैलेंटाइन

हालांकि, आमतौर पर मैं इस विधि की सिफारिश करेंगे, क्योंकि स्वीकार किए जाते हैं एक बेहद धीमी गति से हो सकता है जब पिछले आइटम के लिए जाँच धीमी है docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

छोटे कार्यों और चीजों का उपयोग करना वास्तव में मेरे लिए अपील नहीं करता है; मैं सिर्फ स्लाइस का उपयोग करना पसंद करता हूं:

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:
    ...

अच्छा है लेकिन अनिश्चित धारा के लिए अच्छा नहीं है जिसका कोई पता नहीं है len। आप के साथ itertools.repeatया परीक्षण कर सकते हैं itertools.cycle
n611x007

1
इसके अलावा, स्मृति, क्योंकि एक का उपयोग करने का खाती [...for...] सूची समझ करने के लिए शारीरिक रूप से एक सूची का निर्माण करने के बजाय एक का उपयोग कर (...for...) जनरेटर अभिव्यक्ति जो सिर्फ अगले तत्व और स्पेयर स्मृति के बारे में परवाह होगा
n611x007

2

किसी सूची के सभी रूपांतरणों से बचने के लिए import itertoolsऔर:

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

पैदा करता है:

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

मैंने जाँच की groupbyऔर यह सूची या उपयोग में परिवर्तित नहीं होती हैlen इसलिए मुझे लगता है कि यह प्रत्येक मूल्य के समाधान में देरी करेगा जब तक कि यह वास्तव में उपयोग नहीं किया जाता है। अफसोस की बात है कि उपलब्ध उत्तर में से कोई भी (इस समय) इस भिन्नता की पेशकश नहीं करता था।

जाहिर है अगर आपको प्रत्येक आइटम को बारी-बारी से घोंसले के लिए जी के ऊपर संभालना है:

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

इसमें मेरी विशेष रुचि यह थी कि जेनरेटर एपीआई के 1000 तक के बैचों में परिवर्तन प्रस्तुत करने के लिए एक जनरेटर का उपभोग करने की आवश्यकता थी:

    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)

क्या होगा यदि आप जिस सूची को चुन रहे हैं वह आरोही पूर्णांकों के अनुक्रम के अलावा कुछ और है?
पॉलएमसीजी

@PaulMcGuire देख GroupBy ; आदेश का वर्णन करने के लिए एक फ़ंक्शन दिया गया तो पुनरावृत्ति के तत्व कुछ भी हो सकते हैं, है ना?
जॉन मी

1
हां, मैं ग्रुपबी से परिचित हूं। लेकिन अगर संदेश "एबीसीडीईएफजी" अक्षर थे, तो groupby(messages, lambda x: x/3)आपको एक टाइप-इटरर (एक इंट द्वारा एक स्ट्रिंग को विभाजित करने की कोशिश करने के लिए) देगा, 3-अक्षर समूह नहीं। अब अगर आपने किया तो आपके groupby(enumerate(messages), lambda x: x[0]/3)पास कुछ हो सकता है। लेकिन आपने ऐसा नहीं कहा।
पॉलएमसीजी

2

NumPy के साथ यह सरल है:

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

उत्पादन:

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

जब तक मैं कुछ याद नहीं करता, जनरेटर के भाव के साथ निम्नलिखित सरल समाधान का उल्लेख नहीं किया गया है। यह मानता है कि आकार और संख्या दोनों ज्ञात हैं (जो अक्सर मामला होता है), और यह कि कोई गद्दी की आवश्यकता नहीं है:

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

आपके दूसरे तरीके में, मैं ऐसा करके 4 के अगले समूह को आगे बढ़ाऊंगा:

ints = ints[4:]

हालाँकि, मैंने कोई प्रदर्शन माप नहीं किया है, इसलिए मुझे नहीं पता कि कौन अधिक कुशल हो सकता है।

ऐसा कहने के बाद, मैं आमतौर पर पहली विधि चुनूंगा। यह सुंदर नहीं है, लेकिन यह अक्सर बाहरी दुनिया के साथ हस्तक्षेप करने का एक परिणाम है।


1

फिर भी एक और उत्तर, जिसके फायदे हैं:

1) आसानी से समझ में आने वाला
2) किसी भी चलने योग्य पर काम करता है, न केवल अनुक्रमों (उपरोक्त कुछ उत्तर फ़ाइलहैंडल्स पर गला घोंटना होगा)
3) एक ही बार में
4 बार मेमोरी में चंक लोड नहीं करता है) संदर्भों की एक लंबी-लंबी सूची नहीं बनाता है मेमोरी
5 में समान पुनरावृत्ति ) सूची के अंत में भरण मूल्यों का कोई पैडिंग नहीं है

यह कहा जा रहा है, मैंने इसे समयबद्ध नहीं किया है इसलिए यह कुछ अधिक चतुर तरीकों की तुलना में धीमा हो सकता है, और कुछ फायदे अप्रासंगिक हो सकते हैं जो उपयोग के मामले को देखते हुए हो सकते हैं।

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 

अद्यतन:
इस तथ्य के कारण कमियों की एक जोड़ी आंतरिक और बाहरी छोरों को एक ही पुनरावृत्ति से मान खींच रहे हैं:
1) बाहरी लूप में अपेक्षित रूप से काम नहीं करता है - यह सिर्फ एक चंक को लंघन करने के बजाय अगले आइटम पर जारी रहता है । हालाँकि, यह एक समस्या की तरह नहीं लगता है क्योंकि बाहरी लूप में परीक्षण करने के लिए कुछ भी नहीं है।
2) ब्रेक आंतरिक लूप में अपेक्षित रूप से काम नहीं करता है - कंट्रोलर में अगले आइटम के साथ फिर से आंतरिक लूप में फिर से हवा निकलेगी। पूरे विखंडू को छोड़ने के लिए, या तो एक आंतरिक गुच्छे (ii ऊपर) को एक गुच्छे में लपेटें, जैसे for c in tuple(ii), या झंडे को सेट करें और पुनरावृत्ति को बाहर करें।


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 यह पैडिंग को छोड़ देता है; तुम्हारा और bcoughlan 's बहुत समान है
n611x007

1

आप उपयोग कर सकते हैं विभाजन या मात्रा से कार्य करते हैं funcy पुस्तकालय:

from funcy import partition

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

इन फ़ंक्शंस में पुनरावृत्त संस्करण भी हैं ipartitionऔर ichunks, जो इस मामले में अधिक कुशल होंगे।

आप उनके कार्यान्वयन पर भी नज़र डाल सकते हैं ।


1

J.F. Sebastian यहां दिए गए समाधान के बारे में :

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

यह चतुर है, लेकिन इसका एक नुकसान भी है - हमेशा टपल लौटाना। इसके बजाय स्ट्रिंग कैसे प्राप्त करें?
बेशक आप लिख सकते हैं ''.join(chunker(...)), लेकिन अस्थायी टपल का निर्माण वैसे भी किया जाता है।

आप zipइस तरह से लिखकर अस्थायी टपल से छुटकारा पा सकते हैं :

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

फिर

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

उदाहरण उपयोग:

>>> 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
आपके लिए अपना जवाब बदलने के लिए आलोचना का मतलब नहीं है, बल्कि एक टिप्पणी है: कोड एक दायित्व है। आप जितने अधिक कोड को लिखेंगे उतने अधिक स्थान आप बग को छिपाने के लिए बनाएंगे। इस दृष्टिकोण से, zipमौजूदा का उपयोग करने के बजाय पुनर्लेखन सबसे अच्छा विचार नहीं है।
अल्फ

1

मुझे यह तरीका पसंद है। यह सरल और जादुई नहीं लगता है और सभी चलने योग्य प्रकारों का समर्थन करता है और आयात की आवश्यकता नहीं है।

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

मैं कभी नहीं चाहता कि मेरी चूड़ियां गद्देदार हों, इसलिए यह आवश्यक है। मुझे लगता है कि किसी भी चलने योग्य पर काम करने की क्षमता भी आवश्यकता है। यह देखते हुए, मैंने स्वीकृत उत्तर, https://stackoverflow.com/a/434411/1074659 पर विस्तार करने का निर्णय लिया ।

यदि पैडिंग मूल्यों की तुलना और फ़िल्टर करने की आवश्यकता के कारण पैडिंग नहीं चाहता है, तो इस दृष्टिकोण में प्रदर्शन थोड़ा हिट होता है। हालांकि, बड़े चंक आकारों के लिए, यह उपयोगिता बहुत अच्छा है।

#!/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

यहाँ आयातकों के बिना एक हिस्सा है जो जनरेटर का समर्थन करता है:

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

उपयोग का उदाहरण:

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

पायथन 3.8 के साथ आप वालरस ऑपरेटर और का उपयोग कर सकते हैं 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

ऐसा करने के लिए एक सुंदर तरीका प्रतीत नहीं होता है। यहां एक पृष्ठ है, जिसमें कई विधियां शामिल हैं:

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

यदि सूचियां समान आकार की हैं, तो आप उन्हें 4-ट्यूपल की सूचियों में जोड़ सकते हैं zip()। उदाहरण के लिए:

# 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):
    ...

यहाँ zip()फ़ंक्शन क्या है :

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

यदि सूचियाँ बड़ी हैं, और आप उन्हें एक बड़ी सूची में उपयोग नहीं करना चाहते हैं, तो उपयोग करें itertools.izip(), जो सूची के बजाय एक पुनरावृत्त पैदा करता है।

from itertools import izip

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

0

एक-लाइनर, xआकार के विखंडू में एक सूची पर पुनरावृति के लिए एडहॉक समाधान 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 ...
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.