Як розділити список на шматки рівномірного розміру?


2264

У мене є список довільної довжини, і мені потрібно розділити його на шматки рівних розмірів і оперувати ним. Є кілька очевидних способів зробити це, як збереження лічильника та двох списків, і коли другий список заповнюється, додайте його до першого списку та очистіть другий список для наступного раунду даних, але це потенційно надзвичайно дорого.

Мені було цікаво, чи хтось вдало вирішив це для списків будь-якої довжини, наприклад, використовуючи генератори.

Я шукав щось корисне, itertoolsале я не міг знайти нічого, очевидно, корисного. Можливо, я це пропустив.

Питання, пов'язані з цим: Який найбільш "пітонічний" спосіб переглядати список по шматках?


1
Перш ніж опублікувати нову відповідь, подумайте, що на це питання вже є 60+ відповідей. Будь ласка, переконайтеся, що ваша відповідь вносить інформацію, яка не входить до наявних відповідей.
janniks

Користувачі, які хочуть уникнути довільно невеликого остаточного шматка, перегляньте розділення списку на N частин приблизно однакової довжини
вім

Відповіді:


3150

Ось генератор, який дає потрібні шматки:

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, вам слід використовувати xrange()замість 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]

Крім того, ви можете просто використовувати розуміння списку замість написання функції, хоча це гарна ідея інкапсулювати такі операції у названих функціях, щоб ваш код був легший для розуміння. Пітон 3:

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

Версія Python 2:

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

71
Що станеться, якщо ми не можемо визначити довжину списку? Спробуйте це на itertools.repeat ([1, 2, 3]), наприклад
jespern

47
Це цікаве розширення питання, але оригінальне запитання чітко задається питанням роботи в списку.
Нед Батчелдер

33
ця функція повинна бути в проклятій стандартній бібліотеці
1818

6
@Calimo: що ти пропонуєш? Я передаю вам список із 47 елементами. Як би ви хотіли розділити його на "рівномірні шматки"? ОП прийняла відповідь, тому вони чітко в порядку з останнім шматом різного розміру. Можливо, англійська фраза неточна?
Нед Батчелдер

8
Будь ласка, не називайте своїх змінних l, це виглядає рівно як 1 і заплутано. Люди копіюють ваш код і вважають, що це нормально.
Ясен

555

Якщо ви хочете чогось надзвичайно простого:

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

Використовуйте xrange()замість range()у випадку Python 2.x


6
Або (якщо ми робимо різні подання цієї конкретної функції), ви можете визначити функцію лямбда через: lambda x, y: [x [i: i + y] для i в діапазоні (0, len (x), y) ]. Я люблю цей метод розуміння списку!
JP

4
після повернення повинно бути [, не (
alwbtc

2
"Супер просто" означає, що не потрібно налагоджувати нескінченні петлі - кудо для max().
Боб Штейн

немає нічого просто про це рішення
міт

1
@Nhoj_Gonk На жаль, це не нескінченний цикл, але фрагменти (L, 0) піднімуть ValueError без max (). Натомість max () перетворює щось менше, ніж 1, на 1.
Боб Штейн

295

Безпосередньо із (старої) документації на Python (рецепти для itertools):

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:

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

Я здогадуюсь, що машина часу Гайдо працює - працювала - буде працювати - буде працювати - знову працювала.

Ці рішення працюють тому, що [iter(iterable)]*n(або еквівалент у попередній версії) створюється один ітератор, повторний nраз у списку. izip_longestпотім ефективно виконує кругообіг "кожного" ітератора; оскільки це той самий ітератор, він розширюється кожним таким викликом, в результаті чого кожен такий zip-roundrobin генерує по одному кортежу nелементів.


@ ninjagecko: list(grouper(3, range(10)))повертається [(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, None, None)], а всі кортежі мають довжину 3. Будь ласка, детальніше розробимо ваш коментар, тому що я не можу його зрозуміти; як ви називаєте річ і як ви визначаєте її як кратну 3, "очікуючи, що ваша річ буде кратною 3"? Спасибі заздалегідь.
tzot

14
це схвалило, тому що він працює на генераторах (без len) і використовує загально швидший модуль itertools.
Майкл Діллон

88
Класичний приклад фантазійного itertoolsфункціонального підходу, який виявляє деякий нечитабельний шлам, порівняно з простою та наївною чистою реалізацією пітона
відвідання

15
@wim З огляду на те, що ця відповідь почалася як фрагмент документації Python, я б запропонував вам відкрити проблему на bugs.python.org .
tzot

1
@pedrosaurio, якщо l==[1, 2, 3]тоді f(*l)еквівалентно f(1, 2, 3). Дивіться це питання та офіційну документацію .
tzot

225

Я знаю, що це щось старе, але ніхто ще не згадав 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
Це дозволяє встановити загальну кількість фрагментів, а не кількість елементів на шматок.
FizxMike

6
ви можете займатися математикою самостійно. якщо у вас є 10 елементів , ви можете згрупувати їх в 2, 5 елементів шматки або п'ять 2 елементів скибки
Moj

24
+1 Це моє улюблене рішення, оскільки він розбиває масив на масиви рівномірного розміру, тоді як інші рішення не мають (у всіх інших рішеннях, на які я розглядав, останній масив може бути довільно малим).
MiniQuark

@MiniQuark, але що це робити, коли кількість блоків не є фактором вихідного розміру масиву?
Балдрік

1
@Baldrickk Якщо розділити N елементів на K шматки, то перші N% K шматки матимуть N // K + 1 елементів, а решта матимуть N // K елементів. Наприклад, якщо ви розділите масив, що містить 108 елементів, на 5 фрагментів, то перші 108% 5 = 3 шматки будуть містити 108 // 5 + 1 = 22 елементи, а решта фрагментів матиме 108 // 5 = 21 елементів.
MiniQuark

147

Я здивований , що ніхто не думав про використання iter«S форми з двома аргументами :

from itertools import islice

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

Демонстрація:

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

Це працює з будь-яким ітерабельним і дає лінивий результат. Він повертає кортежі, а не ітератори, але я думаю, що він має певну елегантність. Він також не вкладається; якщо ви хочете прокладки, буде достатньо простого варіанту вищезазначеного:

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)

Демонстрація:

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

Як і izip_longestрішення, засновані на основі, вищезазначені завжди прокладки. Наскільки я знаю, не існує одно- або дворядкового рецепту itertools для функції, яка необов'язково . Поєднуючи вищевказані два підходи, цей підходить досить близько:

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

Демонстрація:

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

Я вважаю, що це найкоротший запропонований шнурок, який пропонує додаткові накладки.

Як зауважив Томаш Гандор, два забійні колодки несподівано зупиняться, якщо зустрінуть довгу послідовність значень колодки. Ось остаточний варіант, який вирішує проблему розумним чином:

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

Демонстрація:

>>> 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
Дивовижно, ваша проста версія - моя улюблена. Інші теж придумали основний islice(it, size)вираз і вклали його (як я це зробив) у циклічну конструкцію. Тільки ви подумали про двоаргументальну версію iter()(я зовсім не знав про це), що робить її надзвичайно елегантною (і, мабуть, найбільш ефективною). Я не мав уявлення, що перший аргумент iterзмінюється на функцію 0-аргументів, коли дається дозорним. Ви повертаєте (pot. Нескінченний) ітератор фрагментів, можете використовувати (вкладення. Нескінченний) ітератор як вхідний дані, не маючи len()і не маючи фрагментів масиву. Дивовижно!
ThomasH

1
Ось чому я читаю відповіді, а не скануючи лише верхню пару. Необов’язкові прокладки були вимогою в моєму випадку, і я теж дізнався про двоаргументальну форму ітера.
Керр

Я підтримав це, але все-таки - не давайте цього перебільшувати! По-перше, лямбда може бути поганим (повільне закриття itітератора. По-друге, і найважливіше - ви закінчитесь передчасно, якщо шматок padvalдійсно існує у вашому ітерабельному, і його слід обробити.
Томаш Гандор

@TomaszGandor, я вважаю, що ти перший! Хоча я розумію, що лямбда не повільніше, ніж звичайна функція, звичайно, ви праві, що виклик функції та пошук закриття це сповільнить. izip_longestНаприклад, я не знаю, який би був відносний показник ефективності порівняно з підходом - я підозрюю, що це може бути складний компроміс. Але ... чи не padvalпроблема, на яку поширюється кожна відповідь, що пропонує padvalпараметр?
senderle

1
@TomaszGandor, досить справедливо! Але створити версію, яка це виправляє, було не надто важко. (Крім того , зверніть увагу , що перша версія, яка використовує в ()якості дозорних, робить роботу правильно Це відбувається тому, tuple(islice(it, size))врожаї , ()коли itпорожньо.)
senderle

93

Ось генератор, який працює на довільних ітерабелях:

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

Приклад:

>>> 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)дорівнює izip_longest(iter).
Томас Ейл

1
@TomaszWysocki Чи можете ви пояснити *перед собою ітераторський кортеж? Можливо, у вашому тексті відповідей, але я зазначив, що *раніше це було в Python. Дякую!
theJollySin

1
@theJollySin У цьому контексті його називають оператором splat. Його використання пояснюється тут - stackoverflow.com/questions/5917522/unzipping-and-the-operator .
rlms

2
Закрийте, але в останньому шматку немає елементів для його заповнення. Це може бути або не бути дефектом. Дійсно класна модель, хоча.

48

Простий, але елегантний

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

або якщо ви віддаєте перевагу:

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

18
Не можна дублювати змінну в подобі арабського числа. У деяких шрифтах вони 1і lне відрізняються. Як є 0і O. А іноді навіть Iі 1.
Альфе

14
@Alfe Несправні шрифти. Люди не повинні використовувати такі шрифти. Не для програмування, ні для чого .
Джері Б

17
Лямбди призначені для використання в якості неназваних функцій. Немає сенсу використовувати їх так. Крім того, це робить налагодження більш складним, оскільки прослідкування буде повідомляти "in <lambda>" замість "in chunks" у разі помилки. Бажаю вам удачі в пошуку проблеми, якщо у вас є ціла купа таких :)
Кріс Костон

1
це має бути 0, а не 1 всередині xrange inprint [l[x:x+10] for x in xrange(1, len(l), 10)]
scottydelta

ПРИМІТКА. Для користувачів Python 3 range.
Крістіан Дін

40

Критика інших відповідей тут:

Жодна з цих відповідей не має однакового розміру, вони залишають кінець шматка в кінці, тому вони не є повністю збалансованими. Якщо ви використовували ці функції для розподілу роботи, ви вбудували перспективу того, що один, ймовірно, закінчить задовго до інших, тож він би сидів, не роблячи нічого, поки інші продовжували наполегливо працювати.

Наприклад, поточна відповідь закінчується на:

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

Я просто ненавиджу цю рут наприкінці!

Інші, як list(grouper(3, xrange(7))), і chunk(xrange(7), 3)як повернення: [(0, 1, 2), (3, 4, 5), (6, None, None)]. TheNone просто підкладка і, на мій погляд, неелегантний. Вони НЕ рівномірно збивають ітерабелі.

Чому ми не можемо розділити їх краще?

Мої рішення

Ось збалансоване рішення, адаптоване з функції я використав у виробництві (Примітка в Python 3 , щоб замінити xrangeз 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) 

І я створив генератор, який робить те саме, якщо ви помістите його в список:

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

І нарешті, оскільки я бачу, що всі перераховані вище функції повертають елементи у постійному порядку (як вони були задані):

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

Вихідні дані

Щоб перевірити їх:

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

Що виводить:

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

Зауважте, що суміжний генератор надає шматки у тій же схемі довжини, що й два інших, але елементи в порядку, і вони поділені рівномірно, оскільки можна розділити список дискретних елементів.


Ви кажете, що жодне з перерахованого вище не забезпечує шматки рівномірного розміру. Але цей робить, як і цей .
відправник

1
@senderle, перший один, list(grouper(3, xrange(7)))і другий, chunk(xrange(7), 3)і повернення: [(0, 1, 2), (3, 4, 5), (6, None, None)]. Це Noneпросто підкладка і, на мій погляд, неелегантний. Вони НЕ рівномірно збивають ітерабелі. Дякуємо за ваш голос!
Аарон Холл

4
Ви ставите питання (не роблячи це явно, тому я це зараз роблю), чи будуть однакові за розміром шматки (за винятком останніх, якщо це неможливо), чи врівноважений (наскільки можливий) результат частіше буде необхідним. Ви припускаєте, що перевагу слід забезпечити збалансованим рішенням; це може бути правдою, якщо те, що ви програмуєте, близьке до реального світу (наприклад, алгоритм розробки карт для імітованої карткової гри). В інших випадках (як, наприклад, заповнення рядків словами), скоріше хочеться зберегти рядки максимально повно. Тому я не можу реально віддавати перевагу одному над іншим; вони просто для різних випадків використання.
Альфе

@ ChristopherBarrington-Leigh Добре, що для DataFrames, ви, ймовірно, повинні використовувати фрагменти, оскільки я вважаю, що об'єкти DataFrame зазвичай не копіюють на нарізку, наприкладimport pandas as pd; [pd.DataFrame(np.arange(7))[i::3] for i in xrange(3)]
Aaron Hall

1
@AaronHall На жаль Я видалив свій коментар, тому що я вдруге здогадався про свою критику, але ви швидко поспішали. Дякую! Насправді моє твердження, що воно не працює для фреймів даних, вірно. Якщо елементи є фреймом даних, просто використовуйте елементи доходу [діапазон (x_i, item_count, кошики)] як останній рядок. Я запропонував окрему (ще одну) відповідь, в якій ви вказуєте бажаний (мінімальний) розмір групи.
CPBL

38

Я бачив найдивовижнішу відповідь Python-ish у двох примірниках цього питання:

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

Ви можете створити n-кортеж для будь-якого n. Якщоa = range(1, 15) , тоді результат буде:

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

Якщо список ділиться порівну, то ви можете замінити zip_longestз zip, в іншому випадку триплет (13, 14, None)буде втрачена. Python 3 використовується вище. Для Python 2 використовуйте izip_longest.


це добре, якщо ваш список і шматки короткі, як би ви могли адаптувати це, щоб розділити свій список на шматки 1000, хоча? ви "не збираєтеся кодувати zip (i, i, i, i, i, i, i, i, i, i ..... i = 1000)
Том Сміт,

9
zip(i, i, i, ... i)З "chunk_size" аргументи до zip () можна записати так, zip(*[i]*chunk_size)чи це хороша ідея чи ні, звичайно, дискусійні.
Вілсон F

1
Мінус цього полягає в тому, що якщо ви не розділите рівномірно, ви скинете елементи, оскільки блискавка зупиняється на найкоротшому ітерабельному рівні - & izip_longth додасть елементи за замовчуванням.
Аарон Холл

zip_longestслід використовувати, як це було зроблено в: stackoverflow.com/a/434411/1959808
Іоанніс Filippidis

У відповіді range(1, 15)вже відсутні елементи, тому що в них 14 елементів range(1, 15), а не 15.
Іоанніс Філіппідіс

35

Якщо ви знаєте розмір списку:

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

Якщо ви цього не зробите (ітератор):

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

В останньому випадку це можна перефразувати більш красивим чином, якщо ви можете бути впевнені, що послідовність завжди містить цілу кількість шматочків заданого розміру (тобто немає неповного останнього відрізка).


Мені сумно, що це поховано так далеко. IterChunks працює на все і є загальним рішенням і не має жодних застережень, про які я знаю.
Джейсон Данкельбергер

18

Toolz бібліотека має partitionфункцію для цього:

from toolz.itertoolz.core import partition

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

Це виглядає як найпростіша з усіх пропозицій. Мене просто цікавить, чи дійсно може бути правдою, що для отримання такої функції розділів треба використовувати бібліотеку третьої сторони. Я б очікував, що щось еквівалентне цій функції розділів існуватиме як вбудована мова.
kasperd

1
ви можете зробити розділ за допомогою itertools. але мені подобається бібліотека інструментів. це бібліотека, натхненна клоджур, для роботи над колекціями у функціональному стилі. ви не отримуєте незмінності, але отримуєте невеликий словник для роботи над простими колекціями. Як плюс, cytoolz написаний цитоном і отримує приємне підвищення продуктивності. github.com/pytoolz/cytoolz matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
Zach

Зв'язок з коментарями Зака працює , якщо ви ommit слеш: matthewrocklin.com/blog/work/2014/05/01/Introducing-CyToolz
міт

17

Якщо у вас, наприклад, розмір шматка 3, ви можете зробити:

zip(*[iterable[i::3] for i in range(3)]) 

джерело: http://code.activestate.com/recipes/303060-group-a-list-into-sequences-n-tuples/

Я би використовував це, коли мій розмір відрізка є фіксованим номером, який я можу набрати, наприклад, "3", і ніколи не змінився.


11
Це не працює, якщо len (ітерабельний)% 3! = 0. Остання (коротка) група номерів не буде повернута.
шербанг

16

Мені дуже подобається версія доктора Python, запропонована tzot та JFSebastian, але у неї є два недоліки:

  • це не дуже явно
  • Зазвичай я не хочу значення заливки в останньому фрагменті

Я дуже багато використовую цей код у своєму коді:

from itertools import islice

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

ОНОВЛЕННЯ: Вершина версія лінь:

from itertools import chain, islice

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

Яка умова перерви в while Trueциклі?
wjandrea

@wjandrea: StopIterationПіднятий, коли поле tupleпорожнє і iterable.next()його виконують. Не працює належним чином у сучасному Python, але там, де вихід із генератора слід робити return, не піднімаючи StopIteration. try/except StopIteration: returnНавколо всього контуру (і зміна iterable.next()в next(iterable)крос-версії Compat) фіксує це з мінімальними витратами , по крайней мере.
ShadowRanger

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

Там, де AA - масив, SS - розмір шматка. Наприклад:

>>> 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
це найкраще і просте.
F.Tamy

2
короткий і простий. простота над складністю.
dryrynicki

15

Мені було цікаво виконувати різні підходи, і ось це:

Тестований на Python 3.5.1

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)

Результати:

slice
31.18285083770752

index
0.02184295654296875

batches 1
0.03503894805908203

batches 2
0.22681021690368652

chunks
0.019841909408569336

grouper
0.006506919860839844

3
бенчмаркінг з використанням timeбібліотеки - не чудова ідея, коли у нас є timeitмодуль
Азат Ібраков

13

код:

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)

результат:

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

12

Ви також можете використовувати get_chunksфункцію 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]]

Ви можете встановити utilspieчерез pip:

sudo pip install utilspie

Відмова: Я творець бібліотеки utilspie .


11

На даний момент я думаю, що нам потрібен рекурсивний генератор , про всяк випадок ...

У python 2:

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

У пітоні 3:

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

Крім того, у випадку масового вторгнення прибульців, може стати в нагоді прикрашений рекурсивний генератор :

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

З Assignment изразами в Python 3.8 це стає дуже приємно:

import itertools

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

Це працює за довільним ітерабельним, а не лише списком.

>>> 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
Тепер це гідна нова відповідь на це питання. Мені насправді це дуже подобається. Я скептично ставлюсь до виразів присвоєння, але коли вони працюють, вони працюють.
juanpa.arrivillaga

7

хе, однорядкова версія

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
Будь ласка, використовуйте "def chunk" замість "chunk = лямбда". Це працює так само. Один рядок. Ті ж особливості. МНОГО простіше n00bz для читання та розуміння.
С.Лотт

4
@ S.Lott: ні, якщо n00bz походить із схеми: P це не реальна проблема. є навіть ключове слово для google! які інші функції показують, що ми уникаємо заради n00bz? Я здогадуюсь, врожайність не є необхідною / с-подібною достатньо, щоб бути n00b дружнім і тоді.
Янус Троельсен

16
Об'єкт функції, що виникає def chunkзамість chunk=lambdaатрибута .__ name__ 'chunk' замість '<lambda>'. Конкретна назва корисніша у відстеженнях.
Террі Ян Реді

1
@Alfe: Я не впевнений, чи можна назвати основну семантичну різницю, але чи є корисна назва в прослідкуванні замість цього <lamba>чи ні, принаймні, помітна різниця.
мартіно

1
Випробувавши купу їх на продуктивність, ЦЕ чудово!
Сонячний Патель

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

використання:

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

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

7

Ще одна більш чітка версія.

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 р., 12 вересня) Ця відповідь є найбільш незалежною від мови та найпростішою для читання.
D Адамс

7

Без виклику len (), що добре для великих списків:

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

І це для ітерабелів:

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

Функціональний аромат вищезазначеного:

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

АБО:

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

АБО:

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()у великих списках; це операція постійного часу.
Thomas Wouters

7

Ось перелік додаткових підходів:

Дано

import itertools as it
import collections as ct

import more_itertools as mit


iterable = range(11)
n = 3

Код

Стандартна бібліотека

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

Список літератури

+ Стороння бібліотека, яка реалізує рецепти itertools та інше.> pip install more_itertools


6

Дивіться цю посилання

>>> 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
Приємно, але в кінці випадають елементи, якщо розмір не відповідає цілій кількості шматочків, наприклад, zip(*[iter(range(7))]*3)тільки повертається [(0, 1, 2), (3, 4, 5)]та забуває 6з вводу.
Альфе

6

Оскільки всі тут говорять про ітераторів. boltonsмає ідеальний для цього метод iterutils.chunked_iter.

from boltons import iterutils

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

Вихід:

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

Але якщо ви не хочете помилуватися пам’яттю, ви можете скористатися старовинним способом і зберегти повне listспочатку за допомогою iterutils.chunked.


І цей насправді працює незалежно від порядку, який дивиться на підрозділи !!
Пітер Гердес

6

Ще одне рішення

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
Хоча це може не виглядати настільки коротко або настільки ж досить, як багато відповідей на основі itertools, цей насправді працює, якщо ви хочете роздрукувати другий підсклад, перш ніж отримати доступ до першого, тобто ви можете встановити i0 = next (g2); i1 = наступний (g2); і використовуйте i1 перед використанням i0, і він не зламається !!
Пітер Гердес

5

Розгляньте можливість використання matplotlib.cbook штук

наприклад:

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

Схоже, ви випадково створили два акаунти. Ви можете зв’язатися з командою для об'єднання їх, що дозволить вам відновити привілеї прямого редагування своїх внесків.
Георгій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.