ما هي الطريقة الأكثر "pythonic" لتكرار أكثر من قائمة في قطع؟




list loops (20)

لدي برنامج Python النصي الذي يأخذ كمدخلات قائمة صحيحة ، والتي أحتاج إلى العمل مع أربعة أعداد صحيحة في كل مرة. لسوء الحظ ، ليس لديّ سيطرة على المدخلات ، أو كنت أملكها كقائمة تضم مجموعات أربعة عناصر. حاليًا ، أتحدث عنه بهذه الطريقة:

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]

يبدو أن الأمر يشبه إلى حد كبير "C-think" ، مما يجعلني أشك في أن هناك طريقة أكثر جاذبية للتعامل مع هذا الوضع. يتم تجاهل القائمة بعد التكرار ، لذلك لا يلزم الاحتفاظ بها. ربما شيء من هذا القبيل سيكون أفضل؟

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

ومع ذلك ، لا يزال "لا يشعر" تماما. : - /

السؤال ذو الصلة: كيف يمكنك تقسيم قائمة إلى قطع بحجم متساوٍ في بايثون؟


أنا أحب هذا النهج. إنه شعور بسيط وغير سحري ويدعم جميع الأنواع المتكرّرة ولا يتطلب الاستيراد.

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

أنا من محبي

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

إذا كانت القائمة كبيرة ، فستكون أفضل طريقة للقيام بذلك هي استخدام المولد:

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

إذا كانت القوائم بنفس الحجم ، يمكنك دمجها في قوائم 4-tuples باستخدام 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() ، الذي ينتج itertools.izip() ، بدلاً من قائمة.

from itertools import izip

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

استخدام الوظائف الصغيرة والأشياء لا يجذبني حقاً. أفضل استخدام الشرائح فقط:

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

الحل المثالي لهذه المشكلة يعمل مع المكرر (وليس فقط التسلسل). يجب أن يكون سريعًا أيضًا.

هذا هو الحل الذي توفره الوثائق لـ 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)

باستخدام ipython %timeit على بلدي ماك كتاب الهواء ، أحصل على 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 الحل التالي مع تحذير هام: إذا كان لبيانات الإدخال الخاصة بك filldata على 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 لنا في حلقة


تم التعديل من قسم recipes مستندات itertools في Python:

from itertools import izip_longest

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

مثال
في pseudocode للحفاظ على مثال مقتضب.

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

ملاحظة: izip_longest جديد على Python 2.6. في Python 3 استخدم zip_longest .


حول حل قدمه JF Sebastian here :

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

إنه ذكي ، لكن له عيب واحد - دائمًا يعود إلى الصفوف (tuple). كيفية الحصول على سلسلة بدلا من ذلك؟
بالطبع يمكنك كتابة ''.join(chunker(...)) ، ولكن يتم إنشاء tuple المؤقت على أي حال.

يمكنك التخلص من المجموعة المؤقتة بكتابة 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'

في البداية ، قمت بتصميمه لتقسيم السلاسل إلى سلاسل فرعية لتحليل سلسلة تحتوي على ست عشري.
اليوم حولتها إلى مولد معقد ولكنه بسيط.

def chunker(iterable, size, reductor, condition):
    it = iter(iterable)
    def chunk_generator():
        return (next(it) for _ in range(size))
    chunk = reductor(chunk_generator())
    while condition(chunk):
        yield chunk
        chunk = reductor(chunk_generator())

الحجج:

تلك واضحة

  • iterable هو أي containg / iterator / itador / مولد / تكرار / تكرارات على بيانات الإدخال ،
  • size ، بالطبع ، حجم القطعة التي تريدها ،

أكثر إثارة

  • reductor هو قابل للاستدعاء ، والذي يستقبل مولد يتكرر على محتوى القطعة.
    كنت أتوقع أن ترجع السلسلة أو السلسلة ، لكنني لا أطلب ذلك.

    يمكنك تمرير مثل هذه الحجة على سبيل المثال list ، tuple ، set ، frozenset ،
    أو أي شيء مربي الحيوانات. كنت سأمر على هذه الوظيفة ، وأعود السلسلة
    (بشرط أن يحتوي iterable على / يولد / يتكرر عبر السلاسل):

    def concatenate(iterable):
        return ''.join(iterable)
    

    لاحظ أن reductor يمكن أن يؤدي إلى إغلاق المولّد عن طريق زيادة الاستثناء.

  • condition هو callable الذي يتلقى أي شيء ما عاد reductor .
    تقرر الموافقة والعرض (بإعادة أي شيء يتم تقييمه إلى True ) ،
    أو رفضه وإنهاء عمل المولد (عن طريق إعادة أي شيء آخر أو زيادة الاستثناء).

    عندما يكون عدد العناصر في iterable غير قابل للقسمة حسب size ، عندما يتم reductor ، سوف يتلقى المولّد مولدًا يولد عناصر أقل من size .
    دعونا ندعو هذه العناصر تدوم العناصر .

    دعوت وظيفتين لتمرير هذه الوسيطة:

    • lambda x:x - سيتم الحصول على عناصر lasts.

    • lambda x: len(x)==<size> - سيتم رفض عناصر lasts.
      استبدل <size> باستخدام الرقم مساويًا size


في طريقتك الثانية ، أود التقدم إلى المجموعة التالية من 4 عن طريق القيام بذلك:

ints = ints[4:]

ومع ذلك ، لم أقم بأي قياس للأداء ، لذا لا أعرف أي منها قد يكون أكثر كفاءة.

بعد قولي هذا ، عادة ما أختار الطريقة الأولى. الأمر ليس جميلاً ، ولكن هذا غالباً ما يكون نتيجة للتواصل مع العالم الخارجي.


لا يبدو أن هناك طريقة جميلة للقيام بذلك. Here صفحة تحتوي على عدد من الأساليب ، بما في ذلك:

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

لتجنب جميع التحويلات إلى قائمة 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 لذلك أنا (أعتقد) هذا سوف يؤخر حل كل قيمة حتى يتم استخدامها بالفعل. للأسف لا يبدو أي من الإجابات المتاحة (في هذا الوقت) لتقديم هذا الاختلاف.

من الواضح أنه إذا كنت بحاجة إلى التعامل مع كل عنصر بدوره ، فاحرص على وجود حلقة فوق g:

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 إلى واجهة برمجة تطبيقات gmail:

    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)

من السهل جعل itertools.groupby يعمل لك للحصول على متكرر من المتكرر ، دون إنشاء أي قوائم مؤقتة:

groupby(iterable, (lambda x,y: (lambda z: x.next()/y))(count(),100))

لا تأخذه اللامبدات المتداخلة ، يدير لامدا الخارجي مرة واحدة فقط لوضع المولدات count() و 100 ثابت في نطاق لامدا الداخلية.

أنا استخدم هذا لإرسال قطع من الصفوف إلى الخلية.

for k,v in groupby(bigdata, (lambda x,y: (lambda z: x.next()/y))(count(),100))):
    cursor.executemany(sql, v)

نشر هذا كإجابة لأنني لا أستطيع التعليق ...

باستخدام map () بدلاً من zip () إصلاح مشكلة padding في إجابة JF Sebastian:

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

هنا pythonic (يمكنك أيضًا split_groups جسم الدالة split_groups )

import itertools
def split_groups(iter_in, group_size):
    return ((x for _, x in item) for _, item in itertools.groupby(enumerate(iter_in), key=lambda x: x[0] // group_size))

for x, y, z, w in split_groups(range(16), 4):
    foo += x * y + z * w

هنا هو chunker بدون واردات تدعم المولدات:

def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(it.next() 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')]

يمكنك استخدام وظيفة partition أو chunks من مكتبة funcy :

from funcy import partition

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

تحتوي هذه الوظائف أيضًا على إصدارات ipartition و ichunks ، والتي ستكون أكثر فاعلية في هذه الحالة.

يمكنك أيضا إلقاء نظرة خاطفة على تنفيذها .


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(it.next())
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()

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

from itertools import izip_longest

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




chunks