Achatar uma lista irregular de listas

440

Sim, eu sei que esse assunto já foi abordado antes ( aqui , aqui , aqui , aqui ), mas, tanto quanto eu sei, todas as soluções, exceto uma, falham em uma lista como esta:

L = [[[1, 2, 3], [4, 5]], 6]

Onde a saída desejada é

[1, 2, 3, 4, 5, 6]

Ou talvez ainda melhor, um iterador. A única solução que vi que funciona para um aninhamento arbitrário é encontrada nesta pergunta :

def flatten(x):
    result = []
    for el in x:
        if hasattr(el, "__iter__") and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

flatten(L)

Este é o melhor modelo? Eu esqueci alguma coisa? Quaisquer problemas?

telliott99
fonte
16
O fato de haver tantas respostas e tanta ação sobre essa questão realmente sugere que essa deve ser uma função interna em algum lugar, certo? É especialmente muito ruim o compiler.ast foi removido do Python 3.0
Mittenchops
3
Eu diria que o que o Python realmente precisa é de recursão ininterrupta, em vez de outra embutida.
argila
2
@ Mittenchops: discordo totalmente, o fato de as pessoas que trabalham com APIs obviamente ruins / estruturas de dados excessivamente complicadas (apenas uma observação: se listpretende ser homogênea) não significa que é uma falha do Python e precisamos de um built-in para essa tarefa
Azat Ibrakov
1
Se você puder adicionar um pacote ao seu projeto - suponho que a solução more_itertools.collapse faça o melhor. Nesta resposta: stackoverflow.com/a/40938883/3844376
viddik13

Respostas:

382

O uso das funções do gerador pode facilitar a leitura do seu exemplo e provavelmente aumentar o desempenho.

Python 2

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el

Eu usei o ABC Iterável adicionado no 2.6.

Python 3

No Python 3, basestringnão existe mais, mas você pode usar uma tupla de stre bytesobter o mesmo efeito lá.

O yield fromoperador retorna um item de um gerador, um de cada vez. Esta sintaxe para delegar a um subgerador foi adicionada no 3.3

def flatten(l):
    for el in l:
        if isinstance(el, collections.Iterable) and not isinstance(el, (str, bytes)):
            yield from flatten(el)
        else:
            yield el
Cristian
fonte
6
De todas as sugestões nesta página, esta é a única que achatou esta lista l = ([[chr(i),chr(i-32)] for i in xrange(ord('a'), ord('z')+1)] + range(0,9))em um piscar de olhos quando eu fiz isso list(flatten(l)). Todos os outros começariam a trabalhar e demorariam para sempre!
Nemesisfixx
7
Isso também facilita os dicionários. Talvez você queira usar em collections.Sequencevez de collections.Iteratable?
Josch
1
Isso não funciona com coisas que não são listas inicialmente, por exemplo for i in flatten(42): print (i). Isso pode ser corrigido movendo a isinstancecláusula -test e else para fora do for elloop. (Então você pode jogar qualquer coisa para ele, e faria uma lista achatado fora dele)
RolKau
6
Para Python 3.7, o uso collections.Iterableestá obsoleto. Use em collections.abc.Iterablevez disso.
dawg
5
De fato, a recursão nunca é necessária. Nesse caso específico, usar recursão não é a melhor solução, pois trava em listas profundamente aninhadas (profundidade> 1000). Mas se você não pretende ter algo seguro, então a função recursiva é melhor, pois é muito mais fácil de ler / escrever.
Cglacet 11/03/19
50

Minha solução:

import collections


def flatten(x):
    if isinstance(x, collections.Iterable):
        return [a for i in x for a in flatten(i)]
    else:
        return [x]

Um pouco mais conciso, mas praticamente o mesmo.

Josh Lee
fonte
5
Você pode fazer isso sem importar nada, apenas try: iter(x)para testar se é iterável ... Mas não acho que ter que importar um módulo stdlib seja uma desvantagem que vale a pena evitar.
abarnert
8
Vale a pena notar que esta solução só funciona se todos os itens são do tipoint
alfasin
1
Poderia torná-lo mais conciso, def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x]mas a legibilidade pode ser subjetiva aqui.
Zero
4
isso não funciona em strings porque strings também são iteráveis. Substitua a condição porif isinstance(x, collections.Iterable) and not isinstance(x, basestring)
aandis
36

Gerador usando recursão e digitação de patos (atualizado para Python 3):

def flatten(L):
    for item in L:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

list(flatten([[[1, 2, 3], [4, 5]], 6]))
>>>[1, 2, 3, 4, 5, 6]
dansalmo
fonte
1
Obrigado, isso funciona bem para o Python 3. Para o 2.x, é necessário o anterior: for i in flatten(item): yield i
dansalmo 8/15
lista (achatar ([['X'], 'Y'])) falha na variante 2.X
sten
@ user1019129 veja meu comentário acima do seu
dansalmo
sim ele falha com o ciclo .. Acho que é porque uma string é também um "array" -de-chars
Sten
35

Versão do gerador da solução não recursiva do @ unutbu, conforme solicitado por @Andrew em um comentário:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    i = 0
    while i < len(l):
        while isinstance(l[i], ltypes):
            if not l[i]:
                l.pop(i)
                i -= 1
                break
            else:
                l[i:i + 1] = l[i]
        yield l[i]
        i += 1

Versão ligeiramente simplificada deste gerador:

def genflat(l, ltypes=collections.Sequence):
    l = list(l)
    while l:
        while l and isinstance(l[0], ltypes):
            l[0:1] = l[0]
        if l: yield l.pop(0)
Alex Martelli
fonte
é uma travessia de pré-ordem da árvore formada pelas listas aninhadas. somente as folhas são devolvidas. Observe que essa implementação consumirá a estrutura de dados original, para melhor ou para pior. Pode ser divertido escrever um que preserve a árvore original, mas também não precise copiar as entradas da lista.
Andrew Wagner
6
Eu acho que você precisa testar as strings - por exemplo, add "e não isinstance (l [0], basestring)" como na solução de Cristian. Caso contrário, você começa um loop infinito em torno de l [0: 1] = l [0]
c-ouriço-
Este é um bom exemplo de criação de um gerador, mas, como c-urchin menciona, o próprio algoritmo falha quando a sequência contém seqüências de caracteres.
Daniel 'Dang' Griffith
28

Aqui está minha versão funcional do achatamento recursivo, que lida com tuplas e listas, e permite que você use qualquer combinação de argumentos posicionais. Retorna um gerador que produz toda a sequência em ordem, arg por arg:

flatten = lambda *n: (e for a in n
    for e in (flatten(*a) if isinstance(a, (tuple, list)) else (a,)))

Uso:

l1 = ['a', ['b', ('c', 'd')]]
l2 = [0, 1, (2, 3), [[4, 5, (6, 7, (8,), [9]), 10]], (11,)]
print list(flatten(l1, -2, -1, l2))
['a', 'b', 'c', 'd', -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
samplebias
fonte
1
grande solução, contudo, seria muito útil se você adicionou algum comentário para descrever o que e, a, nreferem-se
Kristof Pal
2
@WolfgangKuehne: Tente argspara n, intermediate(ou o mais curto midou você pode preferir element) para ae resultpara e, então:flatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))
Em pausa até novo aviso.
Isso é significativamente mais rápido que compiler.ast.flatten. Código ótimo e compacto, funciona para qualquer tipo de objeto (eu acho).
bcdan
Uau, essa deve ser a resposta mais votada e aceita ... funciona como um encanto!
Sub-
27

Esta versão flattenevita o limite de recursão do python (e, portanto, funciona com iteráveis ​​aninhados arbitrariamente profundos). É um gerador que pode lidar com strings e iterables arbitrários (mesmo os infinitos).

import itertools as IT
import collections

def flatten(iterable, ltypes=collections.Iterable):
    remainder = iter(iterable)
    while True:
        first = next(remainder)
        if isinstance(first, ltypes) and not isinstance(first, (str, bytes)):
            remainder = IT.chain(first, remainder)
        else:
            yield first

Aqui estão alguns exemplos que demonstram seu uso:

print(list(IT.islice(flatten(IT.repeat(1)),10)))
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

print(list(IT.islice(flatten(IT.chain(IT.repeat(2,3),
                                       {10,20,30},
                                       'foo bar'.split(),
                                       IT.repeat(1),)),10)))
# [2, 2, 2, 10, 20, 30, 'foo', 'bar', 1, 1]

print(list(flatten([[1,2,[3,4]]])))
# [1, 2, 3, 4]

seq = ([[chr(i),chr(i-32)] for i in range(ord('a'), ord('z')+1)] + list(range(0,9)))
print(list(flatten(seq)))
# ['a', 'A', 'b', 'B', 'c', 'C', 'd', 'D', 'e', 'E', 'f', 'F', 'g', 'G', 'h', 'H',
# 'i', 'I', 'j', 'J', 'k', 'K', 'l', 'L', 'm', 'M', 'n', 'N', 'o', 'O', 'p', 'P',
# 'q', 'Q', 'r', 'R', 's', 'S', 't', 'T', 'u', 'U', 'v', 'V', 'w', 'W', 'x', 'X',
# 'y', 'Y', 'z', 'Z', 0, 1, 2, 3, 4, 5, 6, 7, 8]

Embora flattenpossa manipular geradores infinitos, ele não pode manipular aninhamentos infinitos:

def infinitely_nested():
    while True:
        yield IT.chain(infinitely_nested(), IT.repeat(1))

print(list(IT.islice(flatten(infinitely_nested()), 10)))
# hangs
unutbu
fonte
1
algum consenso sobre o uso de ABC Iterable ou ABC Sequence?
wim
sets, dicts, deques, listiterators, generators, Filehandles e classes personalizadas com __iter__definido são todas as instâncias collections.Iterable, mas não collections.Sequence. O resultado do achatamento de um dicté um pouco duvidoso, mas, caso contrário, acho que collections.Iterableé um padrão melhor do que collections.Sequence. É definitivamente o mais liberal.
unutbu
@ wim: Um problema com o uso collections.Iterableé que isso inclui geradores infinitos. Alterei minha resposta para lidar com este caso.
unutbu
1
Isso não parece funcionar para o terceiro e o quarto exemplos. Joga StopIteration. Além disso, parece que while True: first = next(remainder) pode ser substituído por for first in remainder:.
Georgy
@ Georgy isso pode ser corrigido com o encapsulamento do corpo do achatamento em a try-except StopIteration block.
baduker 24/02
12

Aqui está outra resposta que é ainda mais interessante ...

import re

def Flatten(TheList):
    a = str(TheList)
    b,crap = re.subn(r'[\[,\]]', ' ', a)
    c = b.split()
    d = [int(x) for x in c]

    return(d)

Basicamente, ele converte a lista aninhada em uma seqüência de caracteres, usa um regex para remover a sintaxe aninhada e depois converte o resultado novamente em uma lista (nivelada).

argila
fonte
Se você tentar generalizar isso para algo diferente de valores int, será divertido, por exemplo: [['C=64', 'APPLE ]['], ['Amiga', 'Mac', 'ST']]:) Por outro lado, dada uma lista que se contém, ela se sairá um pouco melhor do que as outras respostas, gerando uma exceção em vez de apenas looping até que seja executado sem memória / recursão até esgotar a pilha ...
abarnert
O prompt original era sobre o achatamento de uma lista de números inteiros. Se você apenas alterar a compreensão da lista para d = [x para x em c], deve funcionar bem para sua amostra.
clay
Primeiro, [x for x in c]é apenas uma maneira lenta e detalhada de fazer uma cópia c, então por que você faria isso? Segundo, seu código claramente será convertido 'APPLE ]['em 'APPLE ', porque não lida com aspas, apenas assume que quaisquer colchetes são colchetes de lista.
abarnert
Ha! Do jeito que seu comentário foi formatado no meu computador, eu nem percebi que era o Apple II, como apareceu nos computadores antigos. De qualquer forma, minha resposta para ambas as perguntas é que este exercício - para mim - é apenas um experimento para encontrar uma solução criativa para achatar uma lista. Não tenho certeza se generalizaria para achatar todas as listas existentes.
Clay
Você só precisa arr_str = str(arr)e então [int(s) for s in re.findall(r'\d+', arr_str)]realmente. Veja github.com/jorgeorpinel/flatten_nested_lists/blob/master/…
Jorge Orpinel
10
def flatten(xs):
    res = []
    def loop(ys):
        for i in ys:
            if isinstance(i, list):
                loop(i)
            else:
                res.append(i)
    loop(xs)
    return res
kev
fonte
8

Você pode usar deepflattendo pacote de terceiros iteration_utilities:

>>> from iteration_utilities import deepflatten
>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(deepflatten(L))
[1, 2, 3, 4, 5, 6]

>>> list(deepflatten(L, types=list))  # only flatten "inner" lists
[1, 2, 3, 4, 5, 6]

É um iterador, portanto você precisa iterá-lo (por exemplo, envolvendo-o listou usando-o em um loop). Internamente, ele usa uma abordagem iterativa em vez de uma abordagem recursiva e é escrito como extensão C, para que seja mais rápido que as abordagens python puras:

>>> %timeit list(deepflatten(L))
12.6 µs ± 298 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> %timeit list(deepflatten(L, types=list))
8.7 µs ± 139 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

>>> %timeit list(flatten(L))   # Cristian - Python 3.x approach from https://stackoverflow.com/a/2158532/5393381
86.4 µs ± 4.42 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(flatten(L))   # Josh Lee - https://stackoverflow.com/a/2158522/5393381
107 µs ± 2.99 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

>>> %timeit list(genflat(L, list))  # Alex Martelli - https://stackoverflow.com/a/2159079/5393381
23.1 µs ± 710 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Eu sou o autor da iteration_utilitiesbiblioteca.

MSeifert
fonte
7

Foi divertido tentar criar uma função que pudesse achatar uma lista irregular no Python, mas é claro que é para isso que serve o Python (para tornar a programação divertida). O gerador a seguir funciona bastante bem com algumas ressalvas:

def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable

Ele vai achatar tipos de dados que você talvez queira ficar sozinho (como bytearray, bytese strobjetos). Além disso, o código se baseia no fato de que solicitar um iterador de um não-iterável gera a TypeError.

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> def flatten(iterable):
    try:
        for item in iterable:
            yield from flatten(item)
    except TypeError:
        yield iterable


>>> list(flatten(L))
[1, 2, 3, 4, 5, 6]
>>>

Editar:

Não concordo com a implementação anterior. O problema é que você não deve conseguir achatar algo que não é iterável. É confuso e dá a impressão errada do argumento.

>>> list(flatten(123))
[123]
>>>

O gerador a seguir é quase o mesmo que o primeiro, mas não tem o problema de tentar achatar um objeto não iterável. Ele falha como seria de esperar quando um argumento inapropriado é dado a ele.

def flatten(iterable):
    for item in iterable:
        try:
            yield from flatten(item)
        except TypeError:
            yield item

Testar o gerador funciona bem com a lista fornecida. No entanto, o novo código gerará um TypeErrorquando um objeto não iterável é fornecido a ele. Exemplos são mostrados abaixo do novo comportamento.

>>> L = [[[1, 2, 3], [4, 5]], 6]
>>> list(flatten(L))
[1, 2, 3, 4, 5, 6]
>>> list(flatten(123))
Traceback (most recent call last):
  File "<pyshell#32>", line 1, in <module>
    list(flatten(123))
  File "<pyshell#27>", line 2, in flatten
    for item in iterable:
TypeError: 'int' object is not iterable
>>>
Noctis Skytower
fonte
5

Embora uma resposta elegante e muito pitônica tenha sido selecionada, eu apresentaria minha solução apenas para a revisão:

def flat(l):
    ret = []
    for i in l:
        if isinstance(i, list) or isinstance(i, tuple):
            ret.extend(flat(i))
        else:
            ret.append(i)
    return ret

Por favor, diga o quão bom ou ruim é esse código?

Xolve
fonte
1
Use isinstance(i, (tuple, list)). Inicializar variáveis vazias é uma bandeira para eu olhar a estruturas alternativas de código, tipicamente compreensões, geradores, recursividade, etc.
dansalmo
3
return type(l)(ret)você também receberá o mesmo tipo de contêiner que foi passado. :)
dash-tom-bang
@ dash-tom-bang Você pode explicar o que isso significa em detalhes?
Xolve
1
Se você passar uma lista, provavelmente desejará uma lista de volta. Se você passar uma tupla, provavelmente desejará uma volta. Se você passar por uma confusão dos dois, terá o que quer que fosse a coisa externa.
traço-tom-bang
4

Eu prefiro respostas simples. Sem geradores. Sem limites de recursão ou recursão. Apenas iteração:

def flatten(TheList):
    listIsNested = True

    while listIsNested:                 #outer loop
        keepChecking = False
        Temp = []

        for element in TheList:         #inner loop
            if isinstance(element,list):
                Temp.extend(element)
                keepChecking = True
            else:
                Temp.append(element)

        listIsNested = keepChecking     #determine if outer loop exits
        TheList = Temp[:]

    return TheList

Isso funciona com duas listas: um loop for interno e um loop while externo.

O loop for interno percorre a lista. Se ele encontrar um elemento de lista, (1) usa list.extend () para nivelar a parte um nível de aninhamento e (2) alterna keepChecking para True. keepchecking é usado para controlar o loop while externo. Se o loop externo for definido como true, ele acionará o loop interno para outra passagem.

Esses passes continuam acontecendo até que nenhuma lista aninhada seja encontrada. Quando uma passagem finalmente ocorre onde nenhuma é encontrada, keepChecking nunca é acionado como true, o que significa que listIsNested permanece falso e o loop while externo é encerrado.

A lista nivelada é então retornada.

Execução de teste

flatten([1,2,3,4,[100,200,300,[1000,2000,3000]]])

[1, 2, 3, 4, 100, 200, 300, 1000, 2000, 3000]

argila
fonte
Eu também gosto de simples. Nesse caso, porém, você repete a lista quantas vezes houver aninhamentos ou níveis. Pode ficar caro.
telliott99
@ telliott99: Você está certo se suas listas são realmente grandes e / ou aninhadas em grandes profundidades. No entanto, se esse não for o caso, a solução mais simples funcionará da mesma forma, e sem a mágica profunda de algumas das outras respostas. Há um lugar para a compreensão de geradores recursivos em vários estágios, mas não estou convencido de que deve ser o local onde você olha primeiro. (Eu acho que você sabe onde eu cair no "quanto pior, melhor" debate.)
argila
@ telliott99: Ou, de outra forma, você não precisará "tentar Grok" minha solução. Se o desempenho não é um gargalo, o que mais importa para você como programador?
Clay
Soluções mais simples têm menos lógica. A recursão é uma construção de programação bastante fundamental com a qual qualquer um que se considera um programador deve se sentir completamente à vontade. Os geradores são muito parecidos com o Python Way e (junto com as compreensões) são algo que qualquer programador profissional de Python deve entender instantaneamente.
traço-tom-bang
1
Eu concordo com a recursão. Quando escrevi minha resposta, o python ainda quebrou a recursão em 1000 ciclos. Eles mudaram isso? Quanto a ser um programador profissional de python, não sou. Além disso, imagino que muitas pessoas programadas em python não o fazem em tempo integral.
argila
4

Aqui está uma função simples que nivela listas de profundidade arbitrária. Sem recursão, para evitar o estouro da pilha.

from copy import deepcopy

def flatten_list(nested_list):
    """Flatten an arbitrarily nested list, without recursion (to avoid
    stack overflows). Returns a new list, the original list is unchanged.

    >> list(flatten_list([1, 2, 3, [4], [], [[[[[[[[[5]]]]]]]]]]))
    [1, 2, 3, 4, 5]
    >> list(flatten_list([[1, 2], 3]))
    [1, 2, 3]

    """
    nested_list = deepcopy(nested_list)

    while nested_list:
        sublist = nested_list.pop(0)

        if isinstance(sublist, list):
            nested_list = sublist + nested_list
        else:
            yield sublist
Wilfred Hughes
fonte
Sim! Muito parecido com o meu código em github.com/jorgeorpinel/flatten_nested_lists/blob/master/…
Jorge Orpinel
3

Estou surpreso que ninguém tenha pensado nisso. Maldita recursão, não recebo as respostas recursivas que as pessoas avançadas aqui fizeram. de qualquer maneira aqui está a minha tentativa sobre isso. ressalva é que é muito específico para o caso de uso do OP

import re

L = [[[1, 2, 3], [4, 5]], 6]
flattened_list = re.sub("[\[\]]", "", str(L)).replace(" ", "").split(",")
new_list = list(map(int, flattened_list))
print(new_list)

resultado:

[1, 2, 3, 4, 5, 6]
Sião
fonte
3

Eu não passei por todas as respostas já disponíveis aqui, mas aqui está uma lista que eu inventei, tomando emprestado da maneira do lisp de processar o primeiro e o resto da lista

def flatten(l): return flatten(l[0]) + (flatten(l[1:]) if len(l) > 1 else []) if type(l) is list else [l]

aqui está um caso simples e um não tão simples -

>>> flatten([1,[2,3],4])
[1, 2, 3, 4]

>>> flatten([1, [2, 3], 4, [5, [6, {'name': 'some_name', 'age':30}, 7]], [8, 9, [10, [11, [12, [13, {'some', 'set'}, 14, [15, 'some_string'], 16], 17, 18], 19], 20], 21, 22, [23, 24], 25], 26, 27, 28, 29, 30])
[1, 2, 3, 4, 5, 6, {'age': 30, 'name': 'some_name'}, 7, 8, 9, 10, 11, 12, 13, set(['set', 'some']), 14, 15, 'some_string', 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
>>> 
Shreyas
fonte
Não é um forro. Não importa o quanto você tente encaixá-lo em um, def foo():é uma linha separada. Além disso, isso é muito ilegível.
cs95
Decifrei o código de uma linha e refatorei mais. (a edição está pendente de revisão por pares enquanto escrevo isso) Esse método específico me pareceu muito legível, embora o código original precise de alguma refatoração.
Emilio M Bumachar 5/02
3

Ao tentar responder a essa pergunta, você realmente precisa fornecer as limitações do código que propõe como solução. Se fosse apenas sobre performances, eu não me importaria muito, mas a maioria dos códigos propostos como solução (incluindo a resposta aceita) falha ao achatar qualquer lista com profundidade maior que 1000.

Quando digo a maioria dos códigos quero dizer todos os códigos que usam qualquer forma de recursão (ou chamam uma função de biblioteca padrão que é recursiva). Todos esses códigos falham porque, para cada chamada recursiva feita, a pilha (chamada) cresce em uma unidade e a pilha de chamadas python (padrão) tem um tamanho de 1000.

Se você não estiver muito familiarizado com a pilha de chamadas, talvez o seguinte ajude (caso contrário, você pode simplesmente rolar para a Implementação ).

Tamanho da pilha de chamadas e programação recursiva (analogia do calabouço)

Encontrar o tesouro e sair

Imagine que você entra em uma enorme masmorra com salas numeradas , procurando um tesouro. Você não conhece o lugar, mas tem algumas indicações sobre como encontrar o tesouro. Cada indicação é um enigma (a dificuldade varia, mas você não pode prever o quão difícil será). Você decide pensar um pouco sobre uma estratégia para economizar tempo, faz duas observações:

  1. É difícil (longo) encontrar o tesouro, pois você terá que resolver enigmas (potencialmente difíceis) para chegar lá.
  2. Uma vez que o tesouro encontrado, retornar à entrada pode ser fácil, basta usar o mesmo caminho na outra direção (embora isso precise de um pouco de memória para recuperar o seu caminho).

Ao entrar na masmorra, você nota um pequeno caderno aqui. Você decide usá-lo para anotar todas as salas que sair depois de resolver um enigma (ao entrar em uma nova sala), dessa forma você poderá voltar para a entrada. Essa é uma ideia genial, você nem gasta um centavo implementando sua estratégia.

Você entra na masmorra, resolvendo com grande sucesso os primeiros 1001 enigmas, mas aí vem algo que você não havia planejado, não havia mais espaço no caderno emprestado. Você decide abandonar sua missão, pois prefere não ter o tesouro do que se perder para sempre dentro da masmorra (que parece realmente inteligente).

Executando um programa recursivo

Basicamente, é exatamente a mesma coisa que encontrar o tesouro. A masmorra é a memória do computador , seu objetivo agora não é encontrar um tesouro, mas calcular algumas funções (encontre f (x) para um dado x ). As indicações são simplesmente sub-rotinas que o ajudarão a resolver f (x) . Sua estratégia é a mesma da pilha de chamadas , o notebook é a pilha, as salas são os endereços de retorno das funções:

x = ["over here", "am", "I"]
y = sorted(x) # You're about to enter a room named `sorted`, note down the current room address here so you can return back: 0x4004f4 (that room address looks weird)
# Seems like you went back from your quest using the return address 0x4004f4
# Let's see what you've collected 
print(' '.join(y))

O problema que você encontrou no calabouço será o mesmo aqui, a pilha de chamadas tem um tamanho finito (aqui 1000) e, portanto, se você digitar muitas funções sem retornar, preencherá a pilha de chamadas e ocorrerá um erro que parecerá como "Caro aventureiro, sinto muito, mas seu notebook está cheio" :RecursionError: maximum recursion depth exceeded . Observe que você não precisa de recursão para preencher a pilha de chamadas, mas é muito improvável que um programa não recursivo chame 1000 funções sem nunca retornar. É importante entender também que, quando você retorna de uma função, a pilha de chamadas é liberada do endereço usado (daí o nome "pilha", o endereço de retorno é inserido antes de inserir uma função e retirado ao retornar). No caso especial de uma recursão simples (uma funçãofque se autodenomina repetidamente - você entrará frepetidamente até que o cálculo esteja concluído (até que o tesouro seja encontrado) e retornará de faté voltar ao local para o qual você ligou f. A pilha de chamadas nunca será liberada de nada até o final, onde será liberada de todos os endereços de retorno, um após o outro.

Como evitar esse problema?

Na verdade, isso é bem simples: "não use recursão se você não souber o quão profundo ela pode ser". Isso nem sempre é verdade, pois em alguns casos, a recursão da chamada de cauda pode ser otimizada (TCO) . Mas em python, esse não é o caso, e mesmo a função recursiva "bem escrita" não otimiza o uso da pilha. Há um post interessante de Guido sobre esta questão: Eliminação da recursão da cauda .

Existe uma técnica que você pode usar para tornar iterativa qualquer função recursiva, essa técnica que poderíamos chamar de trazer seu próprio notebook . Por exemplo, em nosso caso específico, simplesmente estamos explorando uma lista, entrar em uma sala é equivalente a entrar em uma sub-lista; a pergunta que você deve fazer é: como posso voltar de uma lista para sua lista de pais? A resposta não é tão complexa, repita o seguinte até que stackesteja vazio:

  1. empurre a lista atual addresse indexem um stackao entrar em uma nova sub-lista (note que um endereço de lista + índice é também um endereço, portanto, apenas usar exatamente a mesma técnica usada pela pilha de chamadas);
  2. toda vez que um item é encontrado, yieldele (ou adiciona-o a uma lista);
  3. quando uma lista for totalmente explorada, volte à lista de pais usando o stack retorno address(e index) .

Observe também que isso é equivalente a um DFS em uma árvore em que alguns nós são sublistas A = [1, 2]e outros são itens simples: 0, 1, 2, 3, 4(para L = [0, [1,2], 3, 4]). A árvore fica assim:

                    L
                    |
           -------------------
           |     |     |     |
           0   --A--   3     4
               |   |
               1   2

A pré-ordem de travessia do DFS é: L, 0, A, 1, 2, 3, 4. Lembre-se de que, para implementar um DFS iterativo, você também "precisa" de uma pilha. A implementação que propus antes resulta em ter os seguintes estados (para stacke flat_list):

init.:  stack=[(L, 0)]
**0**:  stack=[(L, 0)],         flat_list=[0]
**A**:  stack=[(L, 1), (A, 0)], flat_list=[0]
**1**:  stack=[(L, 1), (A, 0)], flat_list=[0, 1]
**2**:  stack=[(L, 1), (A, 1)], flat_list=[0, 1, 2]
**3**:  stack=[(L, 2)],         flat_list=[0, 1, 2, 3]
**3**:  stack=[(L, 3)],         flat_list=[0, 1, 2, 3, 4]
return: stack=[],               flat_list=[0, 1, 2, 3, 4]

Neste exemplo, o tamanho máximo da pilha é 2, porque a lista de entrada (e, portanto, a árvore) possui profundidade 2.

Implementação

Para a implementação, em python, você pode simplificar um pouco usando iteradores em vez de listas simples. As referências aos (sub) iteradores serão usadas para armazenar os endereços de retorno das sublistas (em vez de ter o endereço da lista e o índice). Essa não é uma grande diferença, mas acho que é mais legível (e também um pouco mais rápida):

def flatten(iterable):
    return list(items_from(iterable))

def items_from(iterable):
    cursor_stack = [iter(iterable)]
    while cursor_stack:
        sub_iterable = cursor_stack[-1]
        try:
            item = next(sub_iterable)
        except StopIteration:   # post-order
            cursor_stack.pop()
            continue
        if is_list_like(item):  # pre-order
            cursor_stack.append(iter(item))
        elif item is not None:
            yield item          # in-order

def is_list_like(item):
    return isinstance(item, list)

Além disso, observe que em is_list_likeI have isinstance(item, list), que poderia ser alterado para lidar com mais tipos de entrada, aqui eu só queria ter a versão mais simples em que (iterável) seja apenas uma lista. Mas você também pode fazer isso:

def is_list_like(item):
    try:
        iter(item)
        return not isinstance(item, str)  # strings are not lists (hmm...) 
    except TypeError:
        return False

Isso considera as strings como "itens simples" e, portanto flatten_iter([["test", "a"], "b]), retornará ["test", "a", "b"]e não ["t", "e", "s", "t", "a", "b"]. Observe que, nesse caso, iter(item)é chamado duas vezes em cada item, vamos fingir que é um exercício para o leitor tornar esse limpador mais limpo.

Testes e observações sobre outras implementações

No final, lembre-se de que você não pode imprimir uma lista infinitamente aninhada Lusando, print(L)porque internamente ela usará chamadas recursivas para __repr__( RecursionError: maximum recursion depth exceeded while getting the repr of an object). Pelo mesmo motivo, as soluções de flattenenvolvimento strfalharão com a mesma mensagem de erro.

Se você precisar testar sua solução, poderá usar esta função para gerar uma lista aninhada simples:

def build_deep_list(depth):
    """Returns a list of the form $l_{depth} = [depth-1, l_{depth-1}]$
    with $depth > 1$ and $l_0 = [0]$.
    """
    sub_list = [0]
    for d in range(1, depth):
        sub_list = [d, sub_list]
    return sub_list

O que fornece: build_deep_list(5)>>> [4, [3, [2, [1, [0]]]]].

cglacet
fonte
2

Aqui está a compiler.ast.flattenimplementação na 2.7.5:

def flatten(seq):
    l = []
    for elt in seq:
        t = type(elt)
        if t is tuple or t is list:
            for elt2 in flatten(elt):
                l.append(elt2)
        else:
            l.append(elt)
    return l

Existem métodos melhores e mais rápidos (se você chegou aqui, já os viu)

Observe também:

Preterido desde a versão 2.6: O pacote do compilador foi removido no Python 3.

pradyunsg
fonte
2

totalmente hacky, mas acho que funcionaria (dependendo do seu data_type)

flat_list = ast.literal_eval("[%s]"%re.sub("[\[\]]","",str(the_list)))
Joran Beasley
fonte
2

Basta usar uma funcybiblioteca: pip install funcy

import funcy


funcy.flatten([[[[1, 1], 1], 2], 3]) # returns generator
funcy.lflatten([[[[1, 1], 1], 2], 3]) # returns list
ADR
fonte
1
FYI: ele usa solução recursiva: ligação para a fonte
Georgy
1

Aqui está outra abordagem py2, não tenho certeza se é a mais rápida, a mais elegante nem a mais segura ...

from collections import Iterable
from itertools import imap, repeat, chain


def flat(seqs, ignore=(int, long, float, basestring)):
    return repeat(seqs, 1) if any(imap(isinstance, repeat(seqs), ignore)) or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

Ele pode ignorar qualquer tipo específico (ou derivado) desejado, retorna um iterador, para que você possa convertê-lo em qualquer contêiner específico, como lista, tupla, ditado ou simplesmente consumi-lo para reduzir o consumo de memória, para melhor ou pior ele pode manipular objetos não-iteráveis ​​iniciais, como int ...

Observe que a maior parte do trabalho pesado é feita em C, já que, até onde eu sei, é implementado o itertec, por isso, embora seja recursivo, o AFAIK não é limitado pela profundidade da recursão do python, pois as chamadas de função estão acontecendo em C, embora isso não significa que você esteja limitado pela memória, especialmente no OS X, onde o tamanho da pilha tem um limite rígido a partir de hoje (OS X Mavericks) ...

existe uma abordagem um pouco mais rápida, mas um método menos portátil, use-o somente se você puder assumir que os elementos base da entrada podem ser explicitamente determinados, caso contrário, você obterá uma recursão infinita e o OS X com seu tamanho limitado de pilha será lançar uma falha de segmentação rapidamente ...

def flat(seqs, ignore={int, long, float, str, unicode}):
    return repeat(seqs, 1) if type(seqs) in ignore or not isinstance(seqs, Iterable) else chain.from_iterable(imap(flat, seqs))

aqui estamos usando conjuntos para verificar o tipo, para que O (1) vs O (número de tipos) verifique se um elemento deve ou não ser ignorado, embora, é claro, qualquer valor com o tipo derivado dos tipos ignorados declarados falhe , é por isso que está usando str, unicodeentão use-o com cuidado ...

testes:

import random

def test_flat(test_size=2000):
    def increase_depth(value, depth=1):
        for func in xrange(depth):
            value = repeat(value, 1)
        return value

    def random_sub_chaining(nested_values):
        for values in nested_values:
            yield chain((values,), chain.from_iterable(imap(next, repeat(nested_values, random.randint(1, 10)))))

    expected_values = zip(xrange(test_size), imap(str, xrange(test_size)))
    nested_values = random_sub_chaining((increase_depth(value, depth) for depth, value in enumerate(expected_values)))
    assert not any(imap(cmp, chain.from_iterable(expected_values), flat(chain(((),), nested_values, ((),)))))

>>> test_flat()
>>> list(flat([[[1, 2, 3], [4, 5]], 6]))
[1, 2, 3, 4, 5, 6]
>>>  

$ uname -a
Darwin Samys-MacBook-Pro.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64
$ python --version
Python 2.7.5
Samy Vilar
fonte
1

Sem usar nenhuma biblioteca:

def flat(l):
    def _flat(l, r):    
        if type(l) is not list:
            r.append(l)
        else:
            for i in l:
                r = r + flat(i)
        return r
    return _flat(l, [])



# example
test = [[1], [[2]], [3], [['a','b','c'] , [['z','x','y']], ['d','f','g']], 4]    
print flat(test) # prints [1, 2, 3, 'a', 'b', 'c', 'z', 'x', 'y', 'd', 'f', 'g', 4]
alfasin
fonte
1

Usando itertools.chain:

import itertools
from collections import Iterable

def list_flatten(lst):
    flat_lst = []
    for item in itertools.chain(lst):
        if isinstance(item, Iterable):
            item = list_flatten(item)
            flat_lst.extend(item)
        else:
            flat_lst.append(item)
    return flat_lst

Ou sem encadeamento:

def flatten(q, final):
    if not q:
        return
    if isinstance(q, list):
        if not isinstance(q[0], list):
            final.append(q[0])
        else:
            flatten(q[0], final)
        flatten(q[1:], final)
    else:
        final.append(q)
Saksham Varma
fonte
1

Eu usei recursivo para resolver lista aninhada com qualquer profundidade

def combine_nlist(nlist,init=0,combiner=lambda x,y: x+y):
    '''
    apply function: combiner to a nested list element by element(treated as flatten list)
    '''
    current_value=init
    for each_item in nlist:
        if isinstance(each_item,list):
            current_value =combine_nlist(each_item,current_value,combiner)
        else:
            current_value = combiner(current_value,each_item)
    return current_value

Então, depois de definir a função combine_nlist, é fácil usar esta função do flatting. Ou você pode combiná-lo em uma função. Eu gosto da minha solução porque ela pode ser aplicada a qualquer lista aninhada.

def flatten_nlist(nlist):
    return combine_nlist(nlist,[],lambda x,y:x+[y])

resultado

In [379]: flatten_nlist([1,2,3,[4,5],[6],[[[7],8],9],10])
Out[379]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Velho novo
fonte
"lista aninhada com qualquer profundidade" não é verdadeira. Apenas tente você verá: current_value = combiner(current_value,each_item) RecursionError: maximum recursion depth exceeded
cglacet
hmmm estou tentando achatar a lista com mais de 1000 camadas?
Oldyoung
Obviamente, esse é o ponto principal da discussão sobre soluções recursivas vs. iterativas. Se você souber com antecedência que o número de camadas é menor que 1000, a solução mais simples funcionará. Quando você diz "qualquer profundidade" esta lista com profundidade> 1000. inclui
cglacet
1

A maneira mais fácil é usar a biblioteca de metamorfose usando pip install morph.

O código é:

import morph

list = [[[1, 2, 3], [4, 5]], 6]
flattened_list = morph.flatten(list)  # returns [1, 2, 3, 4, 5, 6]
YPCrumble
fonte
1

Estou ciente de que já existem muitas respostas impressionantes, mas eu gostaria de adicionar uma resposta que use o método de programação funcional para resolver a questão. Nesta resposta, uso dupla recursão:

def flatten_list(seq):
    if not seq:
        return []
    elif isinstance(seq[0],list):
        return (flatten_list(seq[0])+flatten_list(seq[1:]))
    else:
        return [seq[0]]+flatten_list(seq[1:])

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

resultado:

[1, 2, 3, 4, 5, 6, 7]
Leo wahyd
fonte
1

Não tenho certeza se isso é necessariamente mais rápido ou mais eficaz, mas é isso que eu faço:

def flatten(lst):
    return eval('[' + str(lst).replace('[', '').replace(']', '') + ']')

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

A flattenfunção aqui transforma a lista em uma cadeia de caracteres, retira todos os colchetes, anexa colchetes de volta às extremidades e a transforma novamente em uma lista.

Embora, se você soubesse que teria colchetes na sua lista em cadeias de caracteres, [[1, 2], "[3, 4] and [5]"]teria que fazer outra coisa.

diligar
fonte
Isso não tem vantagem sobre a solução simples, pois isso não processa listas profundas, ou seja, "RecursionError: profundidade máxima de recursão excedida ao obter a reprovação de um objeto".
Cglacet
1

Este é um implemento simples de achatar em python2

flatten=lambda l: reduce(lambda x,y:x+y,map(flatten,l),[]) if isinstance(l,list) else [l]

test=[[1,2,3,[3,4,5],[6,7,[8,9,[10,[11,[12,13,14]]]]]],]
print flatten(test)

#output [1, 2, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
Statham
fonte
1

Isso achatará uma lista ou dicionário (ou lista de listas ou dicionários de dicionários etc.). Ele assume que os valores são cadeias e cria uma cadeia que concatena cada item com um argumento separador. Se você quiser, poderá usar o separador para dividir o resultado em um objeto de lista posteriormente. Ele usa recursão se o próximo valor for uma lista ou uma sequência. Use o argumento key para saber se você deseja as chaves ou os valores (defina key como false) do objeto de dicionário.

def flatten_obj(n_obj, key=True, my_sep=''):
    my_string = ''
    if type(n_obj) == list:
        for val in n_obj:
            my_sep_setter = my_sep if my_string != '' else ''
            if type(val) == list or type(val) == dict:
                my_string += my_sep_setter + flatten_obj(val, key, my_sep)
            else:
                my_string += my_sep_setter + val
    elif type(n_obj) == dict:
        for k, v in n_obj.items():
            my_sep_setter = my_sep if my_string != '' else ''
            d_val = k if key else v
            if type(v) == list or type(v) == dict:
                my_string += my_sep_setter + flatten_obj(v, key, my_sep)
            else:
                my_string += my_sep_setter + d_val
    elif type(n_obj) == str:
        my_sep_setter = my_sep if my_string != '' else ''
        my_string += my_sep_setter + n_obj
        return my_string
    return my_string

print(flatten_obj(['just', 'a', ['test', 'to', 'try'], 'right', 'now', ['or', 'later', 'today'],
                [{'dictionary_test': 'test'}, {'dictionary_test_two': 'later_today'}, 'my power is 9000']], my_sep=', ')

rendimentos:

just, a, test, to, try, right, now, or, later, today, dictionary_test, dictionary_test_two, my power is 9000
Matt Farguson
fonte
0

Se você gosta de recursão, isso pode ser uma solução interessante para você:

def f(E):
    if E==[]: 
        return []
    elif type(E) != list: 
        return [E]
    else:
        a = f(E[0])
        b = f(E[1:])
        a.extend(b)
        return a

Na verdade, adaptei isso a partir de algum código prático do esquema que havia escrito há algum tempo.

Aproveitar!

inspectorG4dget
fonte
0

Eu sou novo em python e venho de um fundo lisp. Isto é o que eu vim com (confira os nomes de var para lulz):

def flatten(lst):
    if lst:
        car,*cdr=lst
        if isinstance(car,(list,tuple)):
            if cdr: return flatten(car) + flatten(cdr)
            return flatten(car)
        if cdr: return [car] + flatten(cdr)
        return [car]

Parece funcionar. Teste:

flatten((1,2,3,(4,5,6,(7,8,(((1,2)))))))

retorna:

[1, 2, 3, 4, 5, 6, 7, 8, 1, 2]
Michael Puckett
fonte