python - tamanho - Como fazer uma lista simples da lista de listas?




tamanho da lista python (20)

A razão pela qual sua função não funcionou: a extensão estende a matriz in-place e não a retorna. Você ainda pode retornar x de lambda, usando algum truque:

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

Nota: estender é mais eficiente que + em listas.

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

Eu posso fazer isso em um loop for, mas talvez haja algum "one-liner" legal? Eu tentei com reduzir , 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'

A resposta aceita não funcionou para mim ao lidar com listas baseadas em texto de comprimentos variáveis. 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 proposta que funcionou 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']

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

Código

from collections import Iterable


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

Nota: no Python 3, o yield from flatten(x) pode substituir for sub_x in flatten(x): yield sub_x

Demonstração

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 a partir de uma receita em Beazley, D. e B. Jones. Receita 4.14, Livro de receitas do Python 3ª Ed., O'Reilly Media Inc. Sebastopol, CA: 2013.
  • Encontrou um post anterior do SO , possivelmente a demonstração original.

Código simples para o ventilador do pacote underscore.py

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

Ele resolve todos os problemas de flatten (nenhum item de 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 o underscore.py com pip

pip install underscore.py

Eu testei a maioria das soluções sugeridas com o perfplot (um projeto de estimação meu, essencialmente um wrapper em torno do timeit ), e encontrei

list(itertools.chain.from_iterable(a))

ser a solução mais rápida (se mais de 10 listas forem concatenadas).

Código para reproduzir o enredo:

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 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,
    kernels=[
        forfor, sum_brackets, functools_reduce, itertools_chain, numpy_flat,
        numpy_concatenate
        ],
    n_range=[2**k for k in range(16)],
    logx=True,
    logy=True,
    xlabel='num lists'
    )

Outra abordagem incomum que funciona para listas heterogêneas e homogêneas de 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(',')]

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

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

Edit 11/02/2016: Funciona somente quando as sublistas tiverem dimensões idênticas.


Por que você usa estender?

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

Isso deve funcionar bem.


Se você quer achatar uma estrutura de dados onde você não sabe o quão profundo está aninhado você pode usar iteration_utilities.deepflatten 1

>>> 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 uma list ou iterar explicitamente sobre ele.

Para achatar apenas um nível e se cada um dos itens for iterável, você também pode usar iteration_utilities.flatten que por si só é apenas um wrapper fino ao seu 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 timings (com base na resposta de Nico Schlömer que não incluiu a função apresentada nesta resposta):

É um gráfico de log-log para acomodar a enorme gama de valores abrangidos. Para raciocínio qualitativo: menor é melhor.

Os resultados mostram que, se o iterável contiver apenas alguns iteráveis ​​internos, a sum será mais rápida; no entanto, por muito tempo, somente itertools.chain.from_iterable , iteration_utilities.deepflatten ou a compreensão aninhada terão desempenho razoável, sendo itertools.chain.from_iterable o mais rápido (como já notado 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 Disclaimer: Eu sou o autor dessa biblioteca


Uma característica ruim da função do Anil acima é que ela requer que o usuário sempre especifique manualmente o segundo argumento para ser uma lista vazia [] . 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]

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


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.

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

Nota do autor : Isso é ineficiente. Mas divertido, porque as mônadas 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]

Isto apenas soma os elementos de iteráveis ​​passados ​​no primeiro argumento, tratando o segundo argumento como o valor inicial da soma (se não for dado, 0 é usado no lugar e neste caso você terá um erro).

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

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


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ê poderia usar sys.version.

No caso de obj = [[1, 2,], [3, 4], [5, 6]], todas as soluções aqui são boas, incluindo a compreensão da lista e itertools.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, então as rotas acima falharão aqui.
  • Um elemento, 'abc', é tecnicamente iteráveis (todos strs são). No entanto, lendo entre as linhas um pouco, você não quer tratá-lo como tal - você quer tratá-lo como um único elemento.
  • O elemento final, em [8, [9, 10]]si , é um iterável aninhado. Compreensão da lista básica e chain.from_iterableapenas extrair "1 nível abaixo".

Você pode remediar 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 que o subelemento (1) é iterável com Iterableum ABC de itertools, mas também quer garantir que (2) o elemento não seja "semelhante a uma string".


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

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

Feito! Você pode, claro, transformá-lo em uma lista, executando a lista (l)


Você pode evitar chamadas recursivas para a pilha usando uma estrutura de dados de pilha real de forma bastante simples.

alist = [1,[1,2],[1,2,[4,5,6],3, "33"]]
newlist = []

while len(alist) > 0 :
  templist = alist.pop()
  if type(templist) == type(list()) :
    while len(templist) > 0 :
      temp = templist.pop()
      if type(temp) == type(list()) :
        for x in temp :
          templist.append(x)
      else :
        newlist.append(temp)
  else :
    newlist.append(templist)
print(list(reversed(newlist)))

Um método recursivo simples usando reducefrom functoolse o addoperador nas listas:

>>> from functools import reduce
>>> from operator import add
>>> flatten = lambda lst: [lst] if type(lst) is int else reduce(add, [flatten(ele) for ele in lst])
>>> flatten(l)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

A função flattenaceita lstcomo parâmetro. Ele faz um loop de todos os elementos de lstaté alcançar inteiros (também pode mudar intpara float, stretc. para outros tipos de dados), que são adicionados ao valor de retorno da recursão mais externa.

Recursão, diferentemente de métodos como forloops e mônadas, é que é uma solução geral não limitada pela profundidade da lista . Por exemplo, uma lista com profundidade de 5 pode ser nivelada da mesma maneira que l:

>>> l2 = [[3, [1, 2], [[[6], 5], 4, 0], 7, [[8]], [9, 10]]]
>>> flatten(l2)
[3, 1, 2, 6, 5, 4, 0, 7, 8, 9, 10]

def flatten(alist):
    if alist == []:
        return []
    elif type(alist) is not list:
        return [alist]
    else:
        return flatten(alist[0]) + flatten(alist[1:])

flat_list = []
for i in list_of_list:
    flat_list+=i

Este Código também funciona bem, pois estende a lista até o final. Embora seja muito semelhante, mas só tem um loop. Portanto, tem menos complexidade do que adicionar 2 loops for.


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

que significa:

for sublist in l:
    for item in sublist:
        flat_list.append(item)

é mais rápido que os atalhos postados até agora. ( l é a lista para achatar.)

Aqui está uma função correspondente:

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

Como evidência, como sempre, você pode usar o módulo timeit 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 na sum ) são, necessariamente, O(L**2) quando há L sublists - conforme a lista de resultados intermediários continua ficando mais longa, em cada etapa um novo resultado intermediário O objeto de lista é alocado e todos os itens no resultado intermediário anterior devem ser copiados (bem como alguns novos adicionados no final). Então (por simplicidade e sem perda real de generalidade) digamos que você tem L sub-listas de itens I: os primeiros itens são copiados para trás e para frente L-1 vezes, o segundo eu 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ído, ou seja, I * (L**2)/2 .

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







flatten