Dividir uma lista em N partes de comprimento aproximadamente igual


150

Qual é a melhor maneira de dividir uma lista em partes aproximadamente iguais? Por exemplo, se a lista possui 7 elementos e é dividida em 2 partes, queremos obter 3 elementos em uma parte e a outra deve ter 4 elementos.

Estou procurando por algo assim se even_split(L, n)divide Lem npartes.

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

O código acima fornece pedaços de 3, em vez de 3. Eu poderia simplesmente transpor (iterar sobre isso e pegar o primeiro elemento de cada coluna, chamar essa parte um, depois pegar o segundo e colocá-lo na parte dois, etc.), mas isso destrói a ordem dos itens.

Respostas:


64

Este código está quebrado devido a erros de arredondamento. Não use isso!!!

assert len(chunkIt([1,2,3], 10)) == 10  # fails

Aqui está um que poderia funcionar:

def chunkIt(seq, num):
    avg = len(seq) / float(num)
    out = []
    last = 0.0

    while last < len(seq):
        out.append(seq[int(last):int(last + avg)])
        last += avg

    return out

Teste:

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

9
Seu exemplo não irá funcionar para >>> chunkIt(range(8), 6)=>[[0], [1], [2, 3], [4], [5], [6], [7]]
nopper

1
@nopper, eu adicionei um "se num == 1:" condicional para lidar com esse caso de borda.
paulie4

24
Novos visitantes: por favor, não use ou atualize este código , ele está quebrado. por exemplo, chunkIt(range(10), 9)deve retornar 9 partes, mas não.
Wim

3
Este tópico de comentário é realmente confuso, pois a resposta foi editada várias vezes. Essa é uma boa resposta? Não é uma boa resposta?
conchoecia 21/02

6
@conchoecia Não é uma boa resposta, continue rolando para baixo. Isso foi editado apenas uma vez até agora e foi apenas uma edição trivial (recuo de 2 espaços alterado para 4). Infelizmente, o OP "user248237dfsf" não é visto no site há mais de 3 anos; portanto, há pouca esperança de obter a resposta aceita alterada.
Wim

183

Você pode escrevê-lo simplesmente como um gerador de lista:

def split(a, n):
    k, m = divmod(len(a), n)
    return (a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n))

Exemplo:

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

Insira n = min(n, len(a)) # don't create empty bucketsna linha 1 para evitar a criação de baldes vazios em cenários como list(split(range(X, Y)))ondeX < Y
abanana

Visto que não posso editar meu comentário - devo acrescentar que minha alteração anterior poderia aumentar a divisão por um erro zero se a lista estiver vazia, de modo que precisa ser controlado externamente ou adicionado à solução.
Abanana

4
Das N respostas no SO, este é o único que passou em todos os meus testes. gj!
Avishayp 17/05/19

2
O stackoverflow.com/a/37414115/210971 usa o mesmo método, mas também funciona para a lista vazia e o contador de 0 divisões.
LookAheadAtYourTypes

Lindo! Além disso, n pode ser feito para trabalhar como batch_size trocando k e n na declaração de retorno :)
haraprasadj

162

Esta é a razão de ser de numpy.array_split*:

>>> import numpy as np
>>> print(*np.array_split(range(10), 3))
[0 1 2 3] [4 5 6] [7 8 9]
>>> print(*np.array_split(range(10), 4))
[0 1 2] [3 4 5] [6 7] [8 9]
>>> print(*np.array_split(range(10), 5))
[0 1] [2 3] [4 5] [6 7] [8 9]

* crédito para Zero Pireu no quarto 6


1
Qual é o *no printpara?
Yuqli

2
Hey @yuqli, converte uma lista de algo em argumentos individuais em uma função. tente print(L)e `imprima (* L). Consulte também stackoverflow.com/a/36908/2184122 ou procure por "uso de asterisco em python".
Robert Lugg

121

Contanto que você não queira nada bobo como pedaços contínuos:

>>> def chunkify(lst,n):
...     return [lst[i::n] for i in xrange(n)]
... 
>>> chunkify(range(13), 3)
[[0, 3, 6, 9, 12], [1, 4, 7, 10], [2, 5, 8, 11]]

14
Eu não diria que pedaços contínuos são bobos. Talvez você queira manter os pedaços classificados (por exemplo, chunk [0] <chunk [1]), por exemplo.
Tixxit

1
Eu estava brincando. Mas se você realmente não se importa, desta maneira, com a compreensão da lista, é agradável e conciso.
job

3
Isto é subscripting com um passo de N
SMCI

8
enviando esta saída em 'zip' dá-lhe a sua lista ordenada: zip(*chunkify(range(13), 3))resultados em[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11)]
gens

2
Essas soluções funcionam bem, até que você precise da ordem da lista a mesma.
precisa saber é o seguinte

18

Alterando o código para gerar npedaços em vez de pedaços de n:

def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    newn = int(len(l) / n)
    for i in xrange(0, n-1):
        yield l[i*newn:i*newn+newn]
    yield l[n*newn-newn:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

que dá:

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

Isso atribuirá os elementos extras ao grupo final, o que não é perfeito, mas está dentro da especificação de "aproximadamente N partes iguais" :-) Com isso, quero dizer que 56 elementos seriam melhores que (19,19,18), enquanto isso fornece (18,18,20).

Você pode obter uma saída mais equilibrada com o seguinte código:

#!/usr/bin/python
def chunks(l, n):
    """ Yield n successive chunks from l.
    """
    newn = int(1.0 * len(l) / n + 0.5)
    for i in xrange(0, n-1):
        yield l[i*newn:i*newn+newn]
    yield l[n*newn-newn:]

l = range(56)
three_chunks = chunks (l, 3)
print three_chunks.next()
print three_chunks.next()
print three_chunks.next()

quais saídas:

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

isso me dá um resultado estranho. Para p em pedaços (intervalo (54), 3): print len ​​(p) retorna 18, 18, 51 ...

Corrigido, esse era o rendimento final.
precisa saber é

ver também a solition a ligação
Jakob Kroeker

Esta é a resposta mais útil para considerações práticas. Obrigado!
MVChr

Quando uso isso, for x in chunks(mylist,num): print xobtenho os pedaços desejados, mas entre eles recebo uma lista vazia. Alguma idéia do porquê? Ou seja, recebo muito [], um após cada pedaço.
synaptik

12

Se você dividir nelementos em kpartes aproximadas, poderá torná- n % klas 1 elemento maiores que as outras partes para distribuir os elementos extras.

O código a seguir fornecerá o comprimento dos pedaços:

[(n // k) + (1 if i < (n % k) else 0) for i in range(k)]

Exemplo: n=11, k=3resulta em[4, 4, 3]

Você pode calcular facilmente os índices de início dos blocos:

[i * (n // k) + min(i, n % k) for i in range(k)]

Exemplo: n=11, k=3resulta em[0, 4, 8]

Usando o i+1th chunk como limite, obtemos que o ith chunk da lista lcom len né

l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)]

Como etapa final, crie uma lista de todos os pedaços usando a compreensão da lista:

[l[i * (n // k) + min(i, n % k):(i+1) * (n // k) + min(i+1, n % k)] for i in range(k)]

Exemplo: n=11, k=3, l=range(n)resulta em[range(0, 4), range(4, 8), range(8, 11)]


6

Isso fará a divisão por uma única expressão:

>>> myList = range(18)
>>> parts = 5
>>> [myList[(i*len(myList))//parts:((i+1)*len(myList))//parts] for i in range(parts)]
[[0, 1, 2], [3, 4, 5, 6], [7, 8, 9], [10, 11, 12, 13], [14, 15, 16, 17]]

A lista neste exemplo tem o tamanho 18 e é dividida em 5 partes. O tamanho das peças não difere em mais de um elemento.



4

Aqui está um que é adicionado Nonepara tornar as listas iguais em tamanho

>>> from itertools import izip_longest
>>> def chunks(l, n):
    """ Yield n successive chunks from l. Pads extra spaces with None
    """
    return list(zip(*izip_longest(*[iter(l)]*n)))

>>> l=range(54)

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

>>> chunks(l,4)
[(0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52), (1, 5, 9, 13, 17, 21, 25, 29, 33, 37, 41, 45, 49, 53), (2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, None), (3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, None)]

>>> chunks(l,5)
[(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50), (1, 6, 11, 16, 21, 26, 31, 36, 41, 46, 51), (2, 7, 12, 17, 22, 27, 32, 37, 42, 47, 52), (3, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53), (4, 9, 14, 19, 24, 29, 34, 39, 44, 49, None)]

4

Aqui está a minha solução:

def chunks(l, amount):
    if amount < 1:
        raise ValueError('amount must be positive integer')
    chunk_len = len(l) // amount
    leap_parts = len(l) % amount
    remainder = amount // 2  # make it symmetrical
    i = 0
    while i < len(l):
        remainder += leap_parts
        end_index = i + chunk_len
        if remainder >= amount:
            remainder -= amount
            end_index += 1
        yield l[i:end_index]
        i = end_index

Produz

    >>> list(chunks([1, 2, 3, 4, 5, 6, 7], 3))
    [[1, 2], [3, 4, 5], [6, 7]]

4

Aqui está um gerador que pode lidar com qualquer número positivo (inteiro) de chunks. Se o número de blocos for maior que o comprimento da lista de entrada, alguns blocos estarão vazios. Esse algoritmo alterna entre pedaços curtos e longos, em vez de segregá-los.

Eu também incluí algum código para testar a ragged_chunksfunção.

''' Split a list into "ragged" chunks

    The size of each chunk is either the floor or ceiling of len(seq) / chunks

    chunks can be > len(seq), in which case there will be empty chunks

    Written by PM 2Ring 2017.03.30
'''

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(1, chunks + 1):
        stop = i * size // chunks
        yield seq[start:stop]
        start = stop

# test

def test_ragged_chunks(maxsize):
    for size in range(0, maxsize):
        seq = list(range(size))
        for chunks in range(1, size + 1):
            minwidth = size // chunks
            #ceiling division
            maxwidth = -(-size // chunks)
            a = list(ragged_chunks(seq, chunks))
            sizes = [len(u) for u in a]
            deltas = all(minwidth <= u <= maxwidth for u in sizes)
            assert all((sum(a, []) == seq, sum(sizes) == size, deltas))
    return True

if test_ragged_chunks(100):
    print('ok')

Podemos tornar isso um pouco mais eficiente exportando a multiplicação para a rangechamada, mas acho que a versão anterior é mais legível (e DRYer).

def ragged_chunks(seq, chunks):
    size = len(seq)
    start = 0
    for i in range(size, size * chunks + 1, size):
        stop = i // chunks
        yield seq[start:stop]
        start = stop

3

Dê uma olhada no numpy.split :

>>> a = numpy.array([1,2,3,4])
>>> numpy.split(a, 2)
[array([1, 2]), array([3, 4])]

5
E o numpy.array_split () é ainda mais adequado porque se divide aproximadamente.
Yariv

11
Isso não funciona se o tamanho da matriz não for divisível pelo número de divisões.
Dan

1
Esta é uma resposta errada, sua solução retorna uma lista de ndarrays, não de listas
Chłop Z Lasu

3

Implementação usando o método numpy.linspace.

Basta especificar o número de partes em que você deseja dividir a matriz. As divisões terão tamanho quase igual.

Exemplo:

import numpy as np   
a=np.arange(10)
print "Input array:",a 
parts=3
i=np.linspace(np.min(a),np.max(a)+1,parts+1)
i=np.array(i,dtype='uint16') # Indices should be floats
split_arr=[]
for ind in range(i.size-1):
    split_arr.append(a[i[ind]:i[ind+1]]
print "Array split in to %d parts : "%(parts),split_arr

Dá:

Input array: [0 1 2 3 4 5 6 7 8 9]
Array split in to 3 parts :  [array([0, 1, 2]), array([3, 4, 5]), array([6, 7, 8, 9])]

3

Minha solução, fácil de entender

def split_list(lst, n):
    splitted = []
    for i in reversed(range(1, n + 1)):
        split_point = len(lst)//i
        splitted.append(lst[:split_point])
        lst = lst[split_point:]
    return splitted

E o menor verso desta página (escrito por minha garota)

def split(l, n):
    return [l[int(i*len(l)/n):int((i+1)*len(l)/n-1)] for i in range(n)]

FYI: Seu one-liner está quebrado, produz resultados errados. O outro funciona lindamente.
Paulo Freitas

2

Usando a compreensão da lista:

def divide_list_to_chunks(list_, n):
    return [list_[start::n] for start in range(n)]

Isso não trata da questão de uniformizar todos os pedaços.
SuperBiasedMan

0

Outra maneira seria algo assim, a idéia aqui é usar garoupa, mas se livrar None. Nesse caso, todos os 'small_parts' serão formados a partir de elementos na primeira parte da lista e 'large_parts' da parte posterior da lista. O comprimento das 'partes maiores' é len (small_parts) + 1. Precisamos considerar x como duas sub-partes diferentes.

from itertools import izip_longest

import numpy as np

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

def another_chunk(x,num):
    extra_ele = len(x)%num #gives number of parts that will have an extra element 
    small_part = int(np.floor(len(x)/num)) #gives number of elements in a small part

    new_x = list(grouper(small_part,x[:small_part*(num-extra_ele)]))
    new_x.extend(list(grouper(small_part+1,x[small_part*(num-extra_ele):])))

    return new_x

A maneira como eu o configurei retorna uma lista de tuplas:

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

0

Aqui está outra variante que espalha os elementos "restantes" uniformemente entre todos os blocos, um de cada vez, até que não haja mais nenhum. Nesta implementação, os blocos maiores ocorrem no início do processo.

def chunks(l, k):
  """ Yield k successive chunks from l."""
  if k < 1:
    yield []
    raise StopIteration
  n = len(l)
  avg = n/k
  remainders = n % k
  start, end = 0, avg
  while start < n:
    if remainders > 0:
      end = end + 1
      remainders = remainders - 1
    yield l[start:end]
    start, end = end, end+avg

Por exemplo, gere 4 pedaços de uma lista de 14 elementos:

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

0

O mesmo que a resposta do trabalho , mas leva em consideração listas com tamanho menor que o número de pedaços.

def chunkify(lst,n):
    [ lst[i::n] for i in xrange(n if n < len(lst) else len(lst)) ]

se n (número de blocos) for 7 e lst (a lista a ser dividida) for [1, 2, 3], os blocos serão [[0], [1], [2]] em vez de [[0], [1 ], [2], [], [], [], []]


0

Você também pode usar:

split=lambda x,n: x if not x else [x[:n]]+[split([] if not -(len(x)-n) else x[-(len(x)-n):],n)][0]

split([1,2,3,4,5,6,7,8,9],2)

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

0
def evenly(l, n):
    len_ = len(l)
    split_size = len_ // n
    split_size = n if not split_size else split_size
    offsets = [i for i in range(0, len_, split_size)]
    return [l[offset:offset + split_size] for offset in offsets]

Exemplo:

l = [a for a in range(97)] deve ser composto por 10 partes, cada uma com 9 elementos, exceto o último.

Resultado:

[[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, 75, 76, 77, 78, 79, 80],
 [81, 82, 83, 84, 85, 86, 87, 88, 89],
 [90, 91, 92, 93, 94, 95, 96]]

0

Digamos que você queira dividir uma lista [1, 2, 3, 4, 5, 6, 7, 8] em 3 listas de elementos

como [[1,2,3], [4, 5, 6], [7, 8]] , onde se os últimos elementos restantes restantes forem menores que 3, eles serão agrupados.

my_list = [1, 2, 3, 4, 5, 6, 7, 8]
my_list2 = [my_list[i:i+3] for i in range(0, len(my_list), 3)]
print(my_list2)

Saída: [[1,2,3], [4, 5, 6], [7, 8]]

Onde o comprimento de uma peça é 3. Substitua 3 pelo seu próprio tamanho de pedaço.


0

1>

import numpy as np

data # your array

total_length = len(data)
separate = 10
sub_array_size = total_length // separate
safe_separate = sub_array_size * separate

splited_lists = np.split(np.array(data[:safe_separate]), separate)
splited_lists[separate - 1] = np.concatenate(splited_lists[separate - 1], 
np.array(data[safe_separate:total_length]))

splited_lists # your output

2>

splited_lists = np.array_split(np.array(data), separate)

0
def chunk_array(array : List, n: int) -> List[List]:
    chunk_size = len(array) // n 
    chunks = []
    i = 0
    while i < len(array):
        # if less than chunk_size left add the remainder to last element
        if len(array) - (i + chunk_size + 1) < 0:
            chunks[-1].append(*array[i:i + chunk_size])
            break
        else:
            chunks.append(array[i:i + chunk_size])
            i += chunk_size
    return chunks

aqui está a minha versão (inspirada no Max's)


-1

Arredondar o espaço lins e usá-lo como um índice é uma solução mais fácil do que o proposto pelo amit12690.

function chunks=chunkit(array,num)

index = round(linspace(0,size(array,2),num+1));

chunks = cell(1,num);

for x = 1:num
chunks{x} = array(:,index(x)+1:index(x+1));
end
end

-1
#!/usr/bin/python


first_names = ['Steve', 'Jane', 'Sara', 'Mary','Jack','Bob', 'Bily', 'Boni', 'Chris','Sori', 'Will', 'Won','Li']

def chunks(l, n):
for i in range(0, len(l), n):
    # Create an index range for l of n items:
    yield l[i:i+n]

result = list(chunks(first_names, 5))
print result

Escolhido a partir deste link , e foi isso que me ajudou. Eu tinha uma lista predefinida.


-1

digamos que você deseja dividir em 5 partes:

p1, p2, p3, p4, p5 = np.split(df, 5)

4
Isso não fornece uma resposta para a pergunta, por exemplo, como você a escreveria se não souber antecipadamente que deseja dividi-la em cinco partes. Além disso, você está (suponho) assumindo numpy e talvez um dataframe de pandas. O OP está perguntando sobre uma lista genérica.
21818 NickD #

-1

Escrevi código neste caso:

def chunk_ports(port_start, port_end, portions):
    if port_end < port_start:
        return None

    total = port_end - port_start + 1

    fractions = int(math.floor(float(total) / portions))

    results = []

    # No enough to chuck.
    if fractions < 1:
        return None

    # Reverse, so any additional items would be in the first range.
    _e = port_end
    for i in range(portions, 0, -1):
        print "i", i

        if i == 1:
            _s = port_start
        else:
            _s = _e - fractions + 1

        results.append((_s, _e))

        _e = _s - 1

    results.reverse()

    return results

divide_ports (1, 10, 9) retornaria

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

-1

este código funciona para mim (compatível com Python3):

def chunkify(tab, num):
    return [tab[i*num: i*num+num] for i in range(len(tab)//num+(1 if len(tab)%num else 0))]

exemplo (para o tipo bytearray , mas também funciona para as listas s):

b = bytearray(b'\x01\x02\x03\x04\x05\x06\x07\x08')
>>> chunkify(b,3)
[bytearray(b'\x01\x02\x03'), bytearray(b'\x04\x05\x06'), bytearray(b'\x07\x08')]
>>> chunkify(b,4)
[bytearray(b'\x01\x02\x03\x04'), bytearray(b'\x05\x06\x07\x08')]

-1

Este fornece pedaços de comprimento <= n,> = 0

def

 chunkify(lst, n):
    num_chunks = int(math.ceil(len(lst) / float(n))) if n < len(lst) else 1
    return [lst[n*i:n*(i+1)] for i in range(num_chunks)]

por exemplo

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

-1

Tentei a maior parte das soluções, mas elas não funcionaram para o meu caso, por isso crio uma nova função que funciona na maioria dos casos e em qualquer tipo de matriz:

import math

def chunkIt(seq, num):
    seqLen = len(seq)
    total_chunks = math.ceil(seqLen / num)
    items_per_chunk = num
    out = []
    last = 0

    while last < seqLen:
        out.append(seq[last:(last + items_per_chunk)])
        last += items_per_chunk

    return out
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.