Como fazer uma lista simples fora da lista de listas?

3375

Gostaria de saber se existe um atalho para fazer uma lista simples fora da lista de listas em Python.

Eu posso fazer isso em um forloop, mas talvez haja algum "one-liner" legal? Eu tentei reduce(), mas recebo um erro.

Código

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
reduce(lambda x, y: x.extend(y), l)

Mensagem de erro

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <lambda>
AttributeError: 'NoneType' object has no attribute 'extend'
Emma
fonte
20
Há uma discussão aprofundada sobre isso aqui: rightfootin.blogspot.com/2006/09/more-on-python-flatten.html , discutindo vários métodos de achatamento de listas de listas arbitrariamente aninhadas. Uma leitura interessante!
RichieHindle 4/06/2009
6
Algumas outras respostas são melhores, mas a razão pela qual a sua falha é que o método 'extend' sempre retorna Nenhum. Para uma lista com comprimento 2, funcionará, mas retornará Nenhum. Para uma lista mais longa, ele consumirá os 2 primeiros argumentos, que retornam Nenhum. Em seguida, continua com None.extend (<terceira arg>), o que faz com que esse Erro
mehtunguh
A solução @ shawn-chin é a mais pitônica aqui, mas se você precisar preservar o tipo de sequência, digamos que você tenha uma tupla de tuplas, em vez de uma lista de listas, use reduz (operator.concat, tuple_of_tuples). Usar operator.concat com tuplas parece ter um desempenho mais rápido que chain.from_iterables com list.
Meitham

Respostas:

4797

Dada uma lista de listas l,

flat_list = [item for sublist in l for item in sublist]

que significa:

flat_list = []
for sublist in l:
    for item in sublist:
        flat_list.append(item)

é mais rápido que os atalhos publicados até o momento. ( lé a lista para achatar.)

Aqui está a função correspondente:

flatten = lambda l: [item for sublist in l for item in sublist]

Como evidência, você pode usar o timeitmódulo na biblioteca padrão:

$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' '[item for sublist in l for item in sublist]'
10000 loops, best of 3: 143 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'sum(l, [])'
1000 loops, best of 3: 969 usec per loop
$ python -mtimeit -s'l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'reduce(lambda x,y: x+y,l)'
1000 loops, best of 3: 1.1 msec per loop

Explicação: os atalhos baseados em + (incluindo o uso implícito em sum) são, necessariamente, O(L**2)quando existem L sublistas - como a lista de resultados intermediários fica cada vez mais longa, a cada etapa um novo objeto da lista de resultados intermediários é alocado e todos os itens no resultado intermediário anterior deve ser copiado (assim como alguns novos adicionados no final). Portanto, por simplicidade e sem perda real de generalidade, digamos que você tenha L sublistas de itens I cada: os primeiros itens I são copiados para frente e para trás L-1 vezes, o segundo I itens L-2 vezes e assim por diante; o número total de cópias é I vezes a soma de x para x de 1 a L excluída, ou seja I * (L**2)/2,.

A compreensão da lista gera apenas uma lista, uma vez, e copia cada item (do local de residência original para a lista de resultados) também exatamente uma vez.

Alex Martelli
fonte
486
Eu tentei um teste com os mesmos dados, utilizando itertools.chain.from_iterable: $ python -mtimeit -s'from itertools import chain; l=[[1,2,3],[4,5,6], [7], [8,9]]*99' 'list(chain.from_iterable(l))'. Ele roda um pouco mais que o dobro da velocidade da compreensão da lista aninhada, que é a mais rápida das alternativas mostradas aqui.
intuited
274
Achei a sintaxe difícil de entender até que percebi que você pode pensar exatamente como aninhado para loops. para sublist em l: para item in sublist: yield item
Rob Crowell
23
@BorisChervenkov: Observe que eu encerrei a chamada list()para realizar o iterador em uma lista.
intuited
163
[folha por árvore na floresta por folha na árvore] pode ser mais fácil de compreender e aplicar.
John Mee
80
@ Joel, hoje em dia list(itertools.chain.from_iterable(l))é melhor - como observado em outros comentários e na resposta de Shawn.
Alex Martelli
1569

Você pode usar itertools.chain():

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain(*list2d))

Ou você pode usar o itertools.chain.from_iterable()que não requer descompactar a lista com o *operador :

import itertools
list2d = [[1,2,3], [4,5,6], [7], [8,9]]
merged = list(itertools.chain.from_iterable(list2d))
Shawn Chin
fonte
13
O *é o truque que torna chainmenos direto do que a compreensão da lista. Você precisa saber que a cadeia une apenas os iterables passados ​​como parâmetros, e o * faz com que a lista de nível superior seja expandida em parâmetros, portanto, chainune todos esses iterables, mas não desce mais. Eu acho que isso torna a compreensão mais legível do que o uso da cadeia neste caso.
Tim Dierks
52
@ TimDierks: Não sei se "isso exige que você entenda a sintaxe do Python" é um argumento contra o uso de uma determinada técnica no Python. Certamente, o uso complexo pode confundir, mas o operador "splat" geralmente é útil em muitas circunstâncias, e isso não está sendo usado de maneira particularmente obscura; rejeitar todos os recursos de idioma que não são necessariamente óbvios para os usuários iniciantes significa que você está colocando uma mão nas costas. Também pode jogar fora a compreensão da lista enquanto você está nisso; usuários de outras origens encontrariam um forloop repetidamente appendmais óbvio.
ShadowRanger #
Esta resposta e outras respostas aqui fornecem resultados incorretos se o nível superior também contiver um valor. por exemplo, list = [["abc","bcd"],["cde","def"],"efg"]resultará em uma saída de["abc", "bcd", "cde", "def", "e", "f", "g"].
gouravkr
Parece *operador não pode ser usado em python2
wkm
908

Nota do autor : Isso é ineficiente. Mas divertido, porque os monoides são incríveis. Não é apropriado para o código Python de produção.

>>> sum(l, [])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Isso apenas soma os elementos de iterável passados ​​no primeiro argumento, tratando o segundo argumento como o valor inicial da soma (se não for fornecido, 0é usado em seu lugar e, neste caso, ocorrerá um erro).

Como você está somando listas aninhadas, na verdade você obtém o [1,3]+[2,4]resultado de sum([[1,3],[2,4]],[]), que é igual a[1,3,2,4] .

Observe que funciona apenas em listas de listas. Para listas de listas de listas, você precisará de outra solução.

Tríptico
fonte
100
isso é bem arrumado e inteligente, mas eu não usaria porque é difícil de ler.
andrewrk
87
Este é um algoritmo do Shlemiel, o pintor joelonsoftware.com/articles/fog0000000319.html - desnecessariamente ineficiente e desnecessariamente feio.
Mike Graham
44
A operação de acréscimo nas listas forma a Monoid, que é uma das abstrações mais convenientes para pensar em uma +operação em um sentido geral (não limitado apenas a números). Portanto, esta resposta merece um +1 de mim pelo tratamento (correto) de listas como um monóide. O desempenho é preocupante ...
/
7
@andrewrk Bem, algumas pessoas pensam que essa é a maneira mais limpa de fazê-lo: youtube.com/watch?v=IOiZatlZtGU aqueles que não entendem por que isso é legal, só precisam esperar algumas décadas até que todo mundo faça dessa maneira: ) vamos usar linguagens de programação (e abstrações) que são descobertas e não inventadas, o Monoid é descoberto.
jhegedus
11
essa é uma maneira muito ineficiente por causa do aspecto quadrático da soma.
Jean-François Fabre
461

Testei as soluções mais sugeridas com perfplot (um projeto para animais de estimação, essencialmente um invólucro timeit) e descobri

functools.reduce(operator.iconcat, a, [])

para ser a solução mais rápida, quando muitas listas pequenas e poucas listas longas são concatenadas. ( operator.iaddé igualmente rápido.)

insira a descrição da imagem aqui

insira a descrição da imagem aqui


Código para reproduzir o gráfico:

import functools
import itertools
import numpy
import operator
import perfplot


def forfor(a):
    return [item for sublist in a for item in sublist]


def sum_brackets(a):
    return sum(a, [])


def functools_reduce(a):
    return functools.reduce(operator.concat, a)


def functools_reduce_iconcat(a):
    return functools.reduce(operator.iconcat, a, [])


def itertools_chain(a):
    return list(itertools.chain.from_iterable(a))


def numpy_flat(a):
    return list(numpy.array(a).flat)


def numpy_concatenate(a):
    return list(numpy.concatenate(a))


perfplot.show(
    setup=lambda n: [list(range(10))] * n,
    # setup=lambda n: [list(range(n))] * 10,
    kernels=[
        forfor,
        sum_brackets,
        functools_reduce,
        functools_reduce_iconcat,
        itertools_chain,
        numpy_flat,
        numpy_concatenate,
    ],
    n_range=[2 ** k for k in range(16)],
    xlabel="num lists (of length 10)",
    # xlabel="len lists (10 lists total)"
)
Nico Schlömer
fonte
25
Para listas aninhadas enormes, 'list (numpy.array (a) .flat)' é a mais rápida entre todas as funções acima.
Sara
Tentei usar regex: 'list (map (int, re.findall (r "[\ w] +", str (a))))' '. A velocidade é um pouco mais lenta que numpy_concatenate
Justas
Existe uma maneira de fazer um perfplot 3D? número de matrizes por tamanho médio da matriz?
Leo
Eu amo sua solução. Curto, simples e eficiente :-)
ShadyMBA
181
from functools import reduce #python 3

>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(lambda x,y: x+y,l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

O extend()método no seu exemplo modifica, em xvez de retornar um valor útil (quereduce() espera).

Uma maneira mais rápida de fazer a reduceversão seria

>>> import operator
>>> l = [[1,2,3],[4,5,6], [7], [8,9]]
>>> reduce(operator.concat, l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Greg Hewgill
fonte
19
reduce(operator.add, l)seria a maneira correta de fazer a reduceversão. Os embutidos são mais rápidos que os lambdas.
agf
3
@agf aqui está como: * timeit.timeit('reduce(operator.add, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) ,017956018447875977 * timeit.timeit('reduce(lambda x, y: x+y, l)', 'import operator; l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]', number=10000) ,025218963623046875
lukmdo
8
Este é um Shlemiel o pintor algoritmo joelonsoftware.com/articles/fog0000000319.html
Mike Graham
2
isso pode ser usado apenas para integers. Mas e se a lista contiver string?
Freddy
3
@ Fred: A operator.addfunção funciona igualmente bem para listas de números inteiros e listas de strings.
Greg Hewgill
121

Não reinvente a roda se você usar o Django :

>>> from django.contrib.admin.utils import flatten
>>> l = [[1,2,3], [4,5], [6]]
>>> flatten(l)
>>> [1, 2, 3, 4, 5, 6]

... Pandas :

>>> from pandas.core.common import flatten
>>> list(flatten(l))

... Ferramentas :

>>> import itertools
>>> flatten = itertools.chain.from_iterable
>>> list(flatten(l))

... Matplotlib

>>> from matplotlib.cbook import flatten
>>> list(flatten(l))

... Unipath :

>>> from unipath.path import flatten
>>> list(flatten(l))

... Setuptools :

>>> from setuptools.namespaces import flatten
>>> list(flatten(l))
Max Malysh
fonte
4
flatten = itertools.chain.from_iterabledeve ser a resposta certa
lagartixas
3
Ótima resposta! funciona também para G = [[[1, 2, 3], [4, 5]], 5] no caso de pandas
Markus Dutschke
1
Eu gosto da solução do Pandas. Se você tiver algo como: list_of_menuitems = [1, 2, [3, [4, 5, [6]]]], que irá resultar em: [1, 2, 3, 4, 5, 6]. O que sinto falta é o nível achatado.
imjoseangel 20/02
115

Aqui está uma abordagem geral que se aplica a números , seqüências de caracteres , listas aninhadas e contêineres mistos .

Código

#from typing import Iterable 
from collections import Iterable                            # < py38


def flatten(items):
    """Yield items from any nested iterable; see Reference."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            for sub_x in flatten(x):
                yield sub_x
        else:
            yield x

Notas :

  • No Python 3, yield from flatten(x)pode substituirfor sub_x in flatten(x): yield sub_x
  • Em Python 3,8, classes base sumário são movidos a partir de collection.abcpara o typingmódulo.

Demo

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(flatten(lst))                                         # nested lists
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

mixed = [[1, [2]], (3, 4, {5, 6}, 7), 8, "9"]              # numbers, strs, nested & mixed
list(flatten(mixed))
# [1, 2, 3, 4, 5, 6, 7, 8, '9']

Referência

  • Esta solução é modificada de uma receita em Beazley, D. e B. Jones. Receita 4.14, Python Cookbook 3rd Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Encontrei um post anterior do SO , possivelmente a demonstração original.
pylang
fonte
5
Eu só escreveu praticamente o mesmo, porque eu não vi a sua solução ... aqui está o que eu procurava "recursivamente achatar várias listas completas" ... (+1)
Martin Thoma
3
@MartinThoma Muito apreciado. Para sua informação, se o achatamento de iteráveis ​​aninhados é uma prática comum para você, existem alguns pacotes de terceiros que lidam com isso muito bem. Isso pode evitar reinventar a roda. Eu mencionei more_itertoolsentre outros discutidos neste post. Felicidades.
pylang
Talvez traversetambém possa ser um bom nome para esse tipo de árvore, enquanto eu o manteria menos universal para esta resposta, aderindo a listas aninhadas.
Lobo
Você pode verificar em if hasattr(x, '__iter__')vez de importar / verificar Iterablee isso excluirá também as strings.
Ryan Allen
o código acima parece não funcionar se uma das listas aninhadas tiver uma lista de seqüências de caracteres. [1, 2, [3, 4], [4], [], 9, 9.5, 'ssssss', ['str', 'sss', 'ss'], [3, 4, 5]] saída: - [1, 2, 3, 4, 4, 9, 9,5, 'ssssss', 3, 4, 5]
sunnyX 12/19/19
51

Se você deseja achatar uma estrutura de dados em que não sabe o quão profundo está aninhado, você pode usar 1iteration_utilities.deepflatten

>>> from iteration_utilities import deepflatten

>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(deepflatten(l, depth=1))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> l = [[1, 2, 3], [4, [5, 6]], 7, [8, 9]]
>>> list(deepflatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

É um gerador, então você precisa converter o resultado em listou iterar explicitamente sobre ele.


Para nivelar apenas um nível e se cada um dos itens for iterável, você também poderá usar o iteration_utilities.flattenque é apenas um invólucro fino itertools.chain.from_iterable:

>>> from iteration_utilities import flatten
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> list(flatten(l))
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Apenas para adicionar alguns horários (com base na resposta de Nico Schlömer que não incluiu a função apresentada nesta resposta):

insira a descrição da imagem aqui

É um gráfico de log-log para acomodar a enorme variedade de valores estendidos. Para raciocínio qualitativo: quanto mais baixo, melhor.

Os resultados mostram que se o iterable contém apenas alguns iterables internas, em seguida, sumserá mais rápido, no entanto por longos iterables única as itertools.chain.from_iterable, iteration_utilities.deepflattenou a compreensão aninhada ter um desempenho razoável com itertools.chain.from_iterablesendo o mais rápido (como já foi observado por Nico Schlömer).

from itertools import chain
from functools import reduce
from collections import Iterable  # or from collections.abc import Iterable
import operator
from iteration_utilities import deepflatten

def nested_list_comprehension(lsts):
    return [item for sublist in lsts for item in sublist]

def itertools_chain_from_iterable(lsts):
    return list(chain.from_iterable(lsts))

def pythons_sum(lsts):
    return sum(lsts, [])

def reduce_add(lsts):
    return reduce(lambda x, y: x + y, lsts)

def pylangs_flatten(lsts):
    return list(flatten(lsts))

def flatten(items):
    """Yield items from any nested iterable; see REF."""
    for x in items:
        if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
            yield from flatten(x)
        else:
            yield x

def reduce_concat(lsts):
    return reduce(operator.concat, lsts)

def iteration_utilities_deepflatten(lsts):
    return list(deepflatten(lsts, depth=1))


from simple_benchmark import benchmark

b = benchmark(
    [nested_list_comprehension, itertools_chain_from_iterable, pythons_sum, reduce_add,
     pylangs_flatten, reduce_concat, iteration_utilities_deepflatten],
    arguments={2**i: [[0]*5]*(2**i) for i in range(1, 13)},
    argument_name='number of inner lists'
)

b.plot()

1 Isenção de responsabilidade: eu sou o autor dessa biblioteca

MSeifert
fonte
sumnão funciona mais em seqüências arbitrárias como ele inicia 0, fazendo functools.reduce(operator.add, sequences)a substituição (não estamos felizes por elas terem sido removidas reducedos componentes internos?). Quando os tipos são conhecidos, pode ser mais rápido usar type.__add__.
Yann Vernier
@YannVernier Obrigado pela informação. Eu pensei que corri esses benchmarks no Python 3.6 e funcionou sum. Você sabe em quais versões do Python ele parou de funcionar?
MSeifert 15/0518
Eu estava um pouco enganado. 0é apenas o valor inicial padrão, portanto, funciona se alguém usar o argumento start para começar com uma lista vazia ... mas ainda assim casos especiais seqüências de caracteres e me dizem para usar join. Está implementando em foldlvez de foldl1. O mesmo problema aparece no 2.7.
Yann Vernier
39

Eu retiro minha declaração. soma não é o vencedor. Embora seja mais rápido quando a lista é pequena. Mas o desempenho diminui significativamente com listas maiores.

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10000'
    ).timeit(100)
2.0440959930419922

A versão sum ainda está em execução por mais de um minuto e ainda não foi processada!

Para listas médias:

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
20.126545906066895
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
22.242258071899414
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]] * 10'
    ).timeit()
16.449732065200806

Usando listas pequenas e timeit: number = 1000000

>>> timeit.Timer(
        '[item for sublist in l for item in sublist]',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
2.4598159790039062
>>> timeit.Timer(
        'reduce(lambda x,y: x+y,l)',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.5289170742034912
>>> timeit.Timer(
        'sum(l, [])',
        'l=[[1, 2, 3], [4, 5, 6, 7, 8], [1, 2, 3, 4, 5, 6, 7]]'
    ).timeit()
1.0598428249359131
Nadia Alramli
fonte
23
para uma lista verdadeiramente minúscula, por exemplo, uma com três sublistas, talvez - mas como o desempenho da soma segue com O (N ** 2), enquanto a compreensão da lista segue com O (N), apenas aumentar a lista de entrada um pouco reverterá as coisas - - de fato, o LC será "infinitamente mais rápido" do que a soma no limite à medida que N cresce. Fui responsável por projetar sum e fazer sua primeira implementação no tempo de execução do Python, e ainda gostaria de ter encontrado uma maneira de restringi-lo efetivamente à soma de números (no que é realmente bom) e bloquear o "incômodo atraente" que ele oferece às pessoas quem quer "somar" listas ;-).
Alex Martelli
38

Parece haver uma confusão com operator.add! Quando você adiciona duas listas, o termo correto para isso é concatnão adicionar. operator.concaté o que você precisa usar.

Se você está pensando funcional, é tão fácil quanto isso ::

>>> from functools import reduce
>>> list2d = ((1, 2, 3), (4, 5, 6), (7,), (8, 9))
>>> reduce(operator.concat, list2d)
(1, 2, 3, 4, 5, 6, 7, 8, 9)

Você vê reduzir os aspectos do tipo de sequência; portanto, quando você fornece uma tupla, recebe uma tupla. Vamos tentar com uma lista:

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> reduce(operator.concat, list2d)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Aha, você volta a lista.

Como sobre o desempenho ::

>>> list2d = [[1, 2, 3],[4, 5, 6], [7], [8, 9]]
>>> %timeit list(itertools.chain.from_iterable(list2d))
1000000 loops, best of 3: 1.36 µs per loop

from_iterableé bem rápido! Mas não há comparação com a qual reduzir concat.

>>> list2d = ((1, 2, 3),(4, 5, 6), (7,), (8, 9))
>>> %timeit reduce(operator.concat, list2d)
1000000 loops, best of 3: 492 ns per loop
Meitham
fonte
1
Hmm ser exemplo justo segundo deve ser lista também (ou primeira tupla?)
Mr_and_Mrs_D
2
Usar essas entradas pequenas não é uma comparação justa. Para 1000 seqüências de comprimento 1000, recebo 0,037 segundos list(chain.from_iterable(...))e 2,5 segundos para reduce(concat, ...). O problema é que reduce(concat, ...)possui tempo de execução quadrático, enquanto chainé linear.
kaya3
33

Por que você usa estender?

reduce(lambda x, y: x+y, l)

Isso deve funcionar bem.

Andrea Ambu
fonte
7
para python3from functools import reduce
andorov 19/01
Desculpe, isso é muito lento, veja o restante das respostas
Mr_and_Mrs_D
Essa é, de longe, a solução mais fácil de entender, mas curta, que funciona no Python 2 e 3. Percebo que muitas pessoas do Python estão no processamento de dados, onde há enormes quantidades de dados para processar e, portanto, se preocupam muito com a velocidade, mas quando você está escrevendo um script de shell e tem apenas algumas dezenas de elementos em algumas sub-listas, então isso é perfeito.
Asfand Qazi
27

Considere instalar o more_itertoolspacote.

> pip install more_itertools

É fornecido com uma implementação para flatten( fonte , das receitas do itertools ):

import more_itertools


lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.flatten(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

A partir da versão 2.4, você pode nivelar iteráveis aninhados mais complicados com more_itertools.collapse( fonte , contribuído por abarnet).

lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
list(more_itertools.collapse(lst)) 
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

lst = [[1, 2, 3], [[4, 5, 6]], [[[7]]], 8, 9]              # complex nesting
list(more_itertools.collapse(lst))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
pylang
fonte
De fato. Esta deve ser a resposta aceita
brunetton
Se você puder adicionar um pacote ao seu projeto - esta resposta é melhor
viddik13
22

A razão pela qual sua função não funcionou é porque a extensão estende uma matriz no local e não a retorna. Você ainda pode retornar x do lambda, usando algo como isto:

reduce(lambda x,y: x.extend(y) or x, l)

Nota: estender é mais eficiente que + nas listas.

Igor Krivokon
fonte
7
extendé melhor usado como newlist = [], extend = newlist.extend, for sublist in l: extend(l)uma vez que evita o (bastante grande) sobrecarga do lambda, a procura do atributo no x, e a or.
AGF
para python 3 addfrom functools import reduce
Markus Dutschke
17
def flatten(l, a):
    for i in l:
        if isinstance(i, list):
            flatten(i, a)
        else:
            a.append(i)
    return a

print(flatten([[[1, [1,1, [3, [4,5,]]]], 2, 3], [4, 5],6], []))

# [1, 1, 1, 3, 4, 5, 2, 3, 4, 5, 6]
Anil
fonte
def flatten(l, a=None): if a is None: a = [][...]
Poik 22/02/19
16

Versão recursiva

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

def flatten_list(k):
    result = list()
    for i in k:
        if isinstance(i,list):

            #The isinstance() function checks if the object (first argument) is an 
            #instance or subclass of classinfo class (second argument)

            result.extend(flatten_list(i)) #Recursive call
        else:
            result.append(i)
    return result

flatten_list(x)
#result = [1,2,3,4,5,6,7,8,9,10]
Saurabh Singh
fonte
1
bom, sem importações necessárias e é claro sobre o que ele está fazendo ... achatamento uma lista, período :)
Goran B.
1
simplesmente brilhante!
Sachin Sharma
15

matplotlib.cbook.flatten() funcionará para listas aninhadas, mesmo que elas se aninhem mais profundamente que o exemplo.

import matplotlib
l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
print(list(matplotlib.cbook.flatten(l)))
l2 = [[1, 2, 3], [4, 5, 6], [7], [8, [9, 10, [11, 12, [13]]]]]
print list(matplotlib.cbook.flatten(l2))

Resultado:

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

Isso é 18x mais rápido que o sublinhado ._. Flatten:

Average time over 1000 trials of matplotlib.cbook.flatten: 2.55e-05 sec
Average time over 1000 trials of underscore._.flatten: 4.63e-04 sec
(time for underscore._)/(time for matplotlib.cbook) = 18.1233394636
EL_DON
fonte
14

A resposta aceita não funcionou para mim ao lidar com listas de tamanho variável de textos. Aqui está uma abordagem alternativa que funcionou para mim.

l = ['aaa', 'bb', 'cccccc', ['xx', 'yyyyyyy']]

Resposta aceita que não funcionou:

flat_list = [item for sublist in l for item in sublist]
print(flat_list)
['a', 'a', 'a', 'b', 'b', 'c', 'c', 'c', 'c', 'c', 'c', 'xx', 'yyyyyyy']

Nova solução que propôs fez trabalho para mim:

flat_list = []
_ = [flat_list.extend(item) if isinstance(item, list) else flat_list.append(item) for item in l if item]
print(flat_list)
['aaa', 'bb', 'cccccc', 'xx', 'yyyyyyy']
user9074332
fonte
13

Uma característica ruim da função do Anil acima é que ele exige que o usuário sempre especifique manualmente o segundo argumento como uma lista vazia [] . Em vez disso, isso deve ser um padrão. Devido à maneira como os objetos Python funcionam, eles devem ser definidos dentro da função, não nos argumentos.

Aqui está uma função de trabalho:

def list_flatten(l, a=None):
    #check a
    if a is None:
        #initialize with empty list
        a = []

    for i in l:
        if isinstance(i, list):
            list_flatten(i, a)
        else:
            a.append(i)
    return a

Teste:

In [2]: lst = [1, 2, [3], [[4]],[5,[6]]]

In [3]: lst
Out[3]: [1, 2, [3], [[4]], [5, [6]]]

In [11]: list_flatten(lst)
Out[11]: [1, 2, 3, 4, 5, 6]
Deleet
fonte
13

A seguir, parece-me mais simples:

>>> import numpy as np
>>> l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
>>> print (np.concatenate(l))
[1 2 3 4 5 6 7 8 9]
diabo nos detalhes
fonte
Não funciona para listas com dimensões diferentes. -1
nurub 20/01
10

Pode-se também usar o apartamento do NumPy :

import numpy as np
list(np.array(l).flat)

Editar 11/02/2016: Funciona apenas quando sublistas têm dimensões idênticas.

mdh
fonte
essa seria a solução ideal?
RetroCode 22/09/16
6

Você pode usar numpy:
flat_list = list(np.concatenate(list_of_list))

A. Attia
fonte
Isso funciona para numérica, cordas e listas mistas também
Nitin
2
Falha em dados aninhados de maneira desigual, como[1, 2, [3], [[4]], [5, [6]]]
EL_DON 22/04/19
5

Se você estiver disposto a abrir uma pequena quantidade de velocidade para obter uma aparência mais limpa, poderá usar numpy.concatenate().tolist()ou numpy.concatenate().ravel().tolist():

import numpy

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 99

%timeit numpy.concatenate(l).ravel().tolist()
1000 loops, best of 3: 313 µs per loop

%timeit numpy.concatenate(l).tolist()
1000 loops, best of 3: 312 µs per loop

%timeit [item for sublist in l for item in sublist]
1000 loops, best of 3: 31.5 µs per loop

Você pode descobrir mais aqui nos documentos numpy.concatenate e numpy.ravel

mkultra
fonte
1
Não funciona para listas aninhadas de maneira desigual, como[1, 2, [3], [[4]], [5, [6]]]
EL_DON 22/04/19
5

Solução mais rápida que encontrei (para lista grande de qualquer maneira):

import numpy as np
#turn list into an array and flatten()
np.array(l).flatten()

Feito! Obviamente, você pode transformá-lo novamente em uma lista executando a lista (l)

Canuck
fonte
1
Isso está errado, o nivelamento reduzirá as dimensões da matriz nd para uma, mas não concatenará as listas internas como uma.
precisa saber é o seguinte
5

Código simples para o underscore.pyventilador do pacote

from underscore import _
_.flatten([[1, 2, 3], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Resolve todos os problemas de nivelamento (nenhum item da lista ou aninhamento complexo)

from underscore import _
# 1 is none list item
# [2, [3]] is complex nesting
_.flatten([1, [2, [3]], [4, 5, 6], [7], [8, 9]])
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Você pode instalar underscore.pycom pip

pip install underscore.py
Vu Anh
fonte
Da mesma forma, você pode usar o pydash . Acho que esta versão é muito mais legível do que a compreensão da lista ou qualquer outra resposta.
gliemezis
2
Isso é super lento.
Nico Schlömer
2
Por que ele possui um módulo chamado _? Parece um nome ruim. Veja stackoverflow.com/a/5893946/6605826
EL_DON
2
@EL_DON: Na página leia-me underscore.py "Underscore.py é uma porta python da excelente biblioteca javascript underscore.js". Eu acho que é a razão para esse nome. E sim, não é um bom nome para python
Vu Anh
5
def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])
englealuze
fonte
Falha no python2.7 para a lista aninhada de exemplo na pergunta:[[1, 2, 3], [4, 5, 6], [7], [8, 9]]
EL_DON 22/04/19
@EL_DON testado em python 2.7.5. funciona bem
englealuze
5

Nota : Abaixo se aplica ao Python 3.3+ porque ele usa yield_from. sixtambém é um pacote de terceiros, embora seja estável. Como alternativa, você pode usarsys.version .


No caso de obj = [[1, 2,], [3, 4], [5, 6]], todas as soluções aqui são boas, incluindo compreensão de lista eitertools.chain.from_iterable .

No entanto, considere este caso um pouco mais complexo:

>>> obj = [[1, 2, 3], [4, 5], 6, 'abc', [7], [8, [9, 10]]]

Existem vários problemas aqui:

  • Um elemento 6 ,, é apenas um escalar; não é iterável; portanto, as rotas acima falharão aqui.
  • Um elemento, 'abc', é tecnicamente iterable (todosstr s são). No entanto, lendo um pouco as entrelinhas, você não deseja tratá-lo como tal - você deseja tratá-lo como um único elemento.
  • O elemento final [8, [9, 10]]é ele próprio um iterável aninhado. Compreensão básica da lista e chain.from_iterableextraia apenas "1 nível abaixo".

Você pode corrigir isso da seguinte maneira:

>>> from collections import Iterable
>>> from six import string_types

>>> def flatten(obj):
...     for i in obj:
...         if isinstance(i, Iterable) and not isinstance(i, string_types):
...             yield from flatten(i)
...         else:
...             yield i


>>> list(flatten(obj))
[1, 2, 3, 4, 5, 6, 'abc', 7, 8, 9, 10]

Aqui, você verifica se o subelemento (1) é iterável com Iterableum ABC de itertools, mas também deseja garantir que (2) o elemento não seja "semelhante a uma string".

Brad Solomon
fonte
1
Se você ainda estiver interessado em Python 2 compatibilidade, a mudança yield frompara um forloop, por exemplofor x in flatten(i): yield x
pylang
5
flat_list = []
for i in list_of_list:
    flat_list+=i

Este código também funciona bem, pois apenas estende a lista até o fim. Embora seja muito parecido, mas só tem um para loop. Portanto, ele tem menos complexidade do que adicionar 2 para loops.

Deepak Yadav
fonte
5
from nltk import flatten

l = [[1, 2, 3], [4, 5, 6], [7], [8, 9]]
flatten(l)

A vantagem desta solução sobre a maioria das outras aqui é que, se você tiver uma lista como:

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

enquanto a maioria das outras soluções gera um erro, esta solução lida com elas.

Alijy
fonte
A pergunta indica uma "lista de listas", mas sua lista de exemplos inclui um item que não é da lista. A maioria das outras soluções está aderindo à pergunta original. Sua solução resolve um problema mais amplo, mas também requer um pacote Python não básico (nltk) que deve ser instalado primeiro.
simonobo 14/04
4

Esta pode não ser a maneira mais eficiente, mas pensei em colocar uma linha (na verdade, uma linha). Ambas as versões funcionarão em listas aninhadas na hierarquia arbitrária e explorarão os recursos da linguagem (Python3.5) e a recursão.

def make_list_flat (l):
    flist = []
    flist.extend ([l]) if (type (l) is not list) else [flist.extend (make_list_flat (e)) for e in l]
    return flist

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = make_list_flat(a)
print (flist)

A saída é

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Isso funciona de maneira profunda e profunda. A recursão diminui até encontrar um elemento que não seja da lista, estende a variável local fliste a reverte para o pai. Sempre que flisté retornado, ele é estendido aosflist na compreensão da lista. Portanto, na raiz, uma lista simples é retornada.

A lista acima cria várias listas locais e as retorna, que são usadas para estender a lista dos pais. Eu acho que o caminho para isso pode estar criando um gloabl flist, como abaixo.

a = [[1, 2], [[[[3, 4, 5], 6]]], 7, [8, [9, [10, 11], 12, [13, 14, [15, [[16, 17], 18]]]]]]
flist = []
def make_list_flat (l):
    flist.extend ([l]) if (type (l) is not list) else [make_list_flat (e) for e in l]

make_list_flat(a)
print (flist)

A saída é novamente

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Embora não tenha certeza no momento sobre a eficiência.

phoxis
fonte
Por que estender ([l]) em vez de acrescentar (l)?
Maciek 9/04
3

Outra abordagem incomum que funciona para listas hetero e homogêneas de números inteiros:

from typing import List


def flatten(l: list) -> List[int]:
    """Flatten an arbitrary deep nested list of lists of integers.

    Examples:
        >>> flatten([1, 2, [1, [10]]])
        [1, 2, 1, 10]

    Args:
        l: Union[l, Union[int, List[int]]

    Returns:
        Flatted list of integer
    """
    return [int(i.strip('[ ]')) for i in str(l).split(',')]
tharndt
fonte
Essa é apenas uma maneira mais complicada e um pouco mais lenta do que 0003000 já postou antes. Eu reinventou sua proposta ontem, então esta abordagem parece bastante popular nos dias de hoje;)
Darkonaut
Não exatamente: wierd_list = [[1, 2, 3], [4, 5, 6], [7], [8, 9], 10]>>nice_list=[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 0]
tharndt 11/01
meu código como um liner seria: flat_list = [int(e.replace('[','').replace(']','')) for e in str(deep_list).split(',')]
tharndt
1
Você está realmente certo em +1, a proposta de 0003000 não funcionará com números de vários dígitos, eu também não testei isso antes, embora deva ser óbvio. Você pode simplificar seu código e escrever [int(e.strip('[ ]')) for e in str(deep_list).split(',')]. Mas eu sugiro manter a proposta de Deleet para casos de uso reais. Ele não contém transformações de tipo hacky, é mais rápido e versátil, porque naturalmente também lida com listas com tipos mistos.
Darkonaut
2
Infelizmente não. Mas eu vi esse código recentemente aqui: Python Practice Book 6.1.2
tharndt