Подтвердить что ты не робот

Простая идиома разбивает n-длинный список на k-длинные куски, когда n% k> 0?

В Python легко разбить n-длинный список на k-размерные куски, если n кратно k (IOW, n % k == 0). Здесь мой любимый подход (прямо из docs):

>>> k = 3
>>> n = 5 * k
>>> x = range(k * 5)
>>> zip(*[iter(x)] * k)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]

(Фокус в том, что [iter(x)] * k создает список k ссылок на один и тот же итератор, возвращаемый iter(x). Затем zip генерирует каждый фрагмент, вызывая каждую из k копий итератора ровно один раз. * до [iter(x)] * k необходимо, потому что zip ожидает получить свои аргументы как "отдельные" итераторы, а не их список.)

Основной недостаток, который я вижу в этой идиоме, состоит в том, что, когда n не кратно k (IOW, n % k > 0), левые записи просто оставлены; например:.

>>> zip(*[iter(x)] * (k + 1))
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11)]

Там альтернативная идиома, которая немного длиннее для ввода, дает тот же результат, что и выше, когда n % k == 0, и имеет более приемлемое поведение, когда n % k > 0:

>>> map(None, *[iter(x)] * k)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]
>>> map(None, *[iter(x)] * (k + 1))
[(0, 1, 2, 3), (4, 5, 6, 7), (8, 9, 10, 11), (12, 13, 14, None)]

По крайней мере, здесь оставшиеся записи сохраняются, но последний кусок добавляется с помощью None. Если для заполнения требуется просто другое значение, то itertools.izip_longest решает проблему.

Но предположим, что желаемое решение - это тот, в котором последний кусок оставлен без запаса, т.е.

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

Есть ли простой способ изменить idiom map(None, *[iter(x)]*k) для создания этого результата?

(Конечно, нетрудно решить эту проблему, написав функцию (см., например, множество мелких ответов на . Как вы разбиваете список на куски с равномерным размером? или Каков самый "питонический" способ перебора списка в кусках?. Поэтому более точное название для этого вопроса будет "Как избавиться от map(None, *[iter(x)]*k) идиомы?", Но я думаю, что это сбивало бы с толку многих читателей.)

Я был поражен тем, насколько легко разбивать список на четные куски и насколько сложно (в сравнении!) это избавиться от нежелательной прокладки, хотя обе проблемы кажутся сопоставимой сложности.

4b9b3361

Ответ 1

[x[i:i+k] for i in range(0,n,k)]

Ответ 2

sentinal = object()
split = ( 
    (v for v in r if v is not sentinal) for r in
    izip_longest(*[iter(x)]*n, fillvalue=sentinal))

Конечно, лучшая идиома - это вызов функции, поскольку это будет более читаемо, чем все, что будет делать то же самое.

Ответ 3

из источника IPython:

def chop(seq,size):
    """Chop a sequence into chunks of the given size."""
    chunk = lambda i: seq[i:i+size]
    return map(chunk,xrange(0,len(seq),size))

В последнем возвращенном списке будет меньше элементов chunk, если последовательность не будет равномерно делимой, в основном она получает короткий конец палки, но без жалоб.

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

Ответ 4

Как насчет этого? Это другая идиома, но дает желаемый результат:

[x[i:i+k] for i in range(0,len(x),k)] #=> [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]
[x[i:i+k] for i in range(0,len(x),k)] #=> [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14]]

Или, если вам действительно нужны кортежи, используйте tuple(x[i:i+k]) вместо x[i:i+k].