Appiattire un elenco irregolare di elenchi


440

Sì, so che questo argomento è stato trattato in precedenza ( qui , qui , qui , qui ), ma per quanto ne so, tutte le soluzioni, tranne una, falliscono in un elenco come questo:

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

Dove si trova l'output desiderato

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

O forse ancora meglio, un iteratore. L'unica soluzione che ho visto che funziona per un annidamento arbitrario si trova in questa domanda :

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)

Questo è il modello migliore? Ho trascurato qualcosa? Alcun problema?


16
Il fatto che ci siano così tante risposte e così tante azioni su questa domanda suggerisce davvero che questa dovrebbe essere una funzione integrata da qualche parte, giusto? È particolarmente grave il fatto che compiler.ast sia stato rimosso da Python 3.0
Mittenchops il

3
Direi che ciò di cui Python ha davvero bisogno è una ricorsione ininterrotta piuttosto che un altro incorporato.
argilla,

2
@Mittenchops: totalmente in disaccordo, il fatto che le persone che lavorano con API ovviamente cattive / strutture di dati eccessivamente complicate (solo una nota: lists intese per essere omogenee) non significa che sia un errore di Python e abbiamo bisogno di un built-in per tale compito
Azat Ibrakov

1
Se puoi permetterti di aggiungere un pacchetto al tuo progetto, suppongo che la soluzione more_itertools.collapse lo farà meglio. Da questa risposta: stackoverflow.com/a/40938883/3844376
viddik13

Risposte:


382

L'uso delle funzioni del generatore può semplificare la lettura del tuo esempio e probabilmente migliorare le prestazioni.

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

Ho usato la Iterable ABC aggiunta nella 2.6.

Python 3

In Python 3, basestringnon esiste più, ma puoi usare una tupla di stre bytesottenere lo stesso effetto lì.

L' yield fromoperatore restituisce un articolo da un generatore uno alla volta. Questa sintassi per la delega a un sottogeneratore è stata aggiunta in 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

6
Di tutti i suggerimenti in questa pagina, questo è l'unico che ha appiattito questo elenco l = ([[chr(i),chr(i-32)] for i in xrange(ord('a'), ord('z')+1)] + range(0,9))in un attimo quando l'ho fatto list(flatten(l)). Tutti gli altri, inizierebbero a lavorare e prenderebbero per sempre!
nemesisfixx,

7
Questo appiattisce anche i dizionari. Forse vuoi usare collections.Sequenceinvece di collections.Iteratable?
Jos

1
Questo non funziona con cose che inizialmente non sono elenchi, ad es for i in flatten(42): print (i). Questo potrebbe essere risolto spostando il isinstance-test e la clausola else fuori dal for el-loop. (Quindi potresti lanciarci qualsiasi cosa, e ne farebbe una lista appiattita)
RolKau,

6
Per Python 3.7, l'utilizzo collections.Iterableè deprecato. Usa collections.abc.Iterableinvece.
dawg,

5
In effetti, la ricorsione non è mai necessaria. In questo caso specifico l'uso della ricorsione non è la soluzione migliore in quanto si bloccherà su elenchi profondamente annidati (profondità> 1000). Ma se non miri ad avere qualcosa di sicuro, allora sì le funzioni ricorsive sono migliori in quanto sono molto più facili da leggere / scrivere.
cglacet,

50

La mia soluzione:

import collections


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

Un po 'più conciso, ma praticamente lo stesso.


5
Puoi farlo senza importare nulla se solo try: iter(x)per verificare se è iterabile ... Ma non credo che dover importare un modulo stdlib sia un aspetto negativo che vale la pena evitare.
abarnert,

8
Vale la pena notare che questa soluzione funziona solo se tutti gli articoli sono di tipoint
alfasin

1
Potrebbe renderlo più conciso, def flatten(x): return [a for i in x for a in flatten(i)] if isinstance(x, collections.Iterable) else [x]ma la leggibilità potrebbe essere soggettiva qui.
Zero,

4
questo non funziona con le stringhe perché anche le stringhe sono iterabili. Sostituisci la condizione conif isinstance(x, collections.Iterable) and not isinstance(x, basestring)
aandis il

36

Generatore che utilizza la ricorsione e la tipizzazione anatra (aggiornato per 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]

1
Grazie, funziona for i in flatten(item): yield i
benissimo

list (flatten ([['X'], 'Y'])) non riesce sulla variante 2.X
sten

@ user1019129 vedi il mio commento sopra il tuo
dansalmo

sì, fallisce con il ciclo .. penso che una stringa sia anche un "array" -di-caratteri
sten

35

Versione del generatore della soluzione non ricorsiva di @ unutbu, come richiesto da @Andrew in un commento:

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

Versione leggermente semplificata di questo generatore:

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)

è un attraversamento pre-ordine dell'albero formato dalle liste nidificate. vengono restituite solo le foglie. Si noti che questa implementazione consumerà la struttura dei dati originale, nel bene o nel male. Potrebbe essere divertente scriverne uno che preservi l'albero originale, ma che non debba copiare le voci dell'elenco.
Andrew Wagner,

6
Penso che sia necessario testare le stringhe, ad esempio aggiungere "e non isinstance (l [0], basestring)" come nella soluzione di Cristian. Altrimenti ottieni un ciclo infinito attorno a l [0: 1] = l [0]
c-urchin

Questo è un buon esempio di creazione di un generatore, ma come menziona c-urchin, l'algoritmo stesso fallisce quando la sequenza contiene stringhe.
Daniel 'Dang' Griffith,

28

Ecco la mia versione funzionale di appiattimento ricorsivo che gestisce sia le tuple che gli elenchi e consente di inserire qualsiasi mix di argomenti posizionali. Restituisce un generatore che produce l'intera sequenza in ordine, arg di 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]

1
grande soluzione, tuttavia sarebbe molto utile se si è aggiunto qualche commento per descrivere ciò che e, a, nfare riferimento a
Kristof Pal

2
@WolfgangKuehne: Prova argsper n, intermediate(o il più breve mido si potrebbe preferire element) per ae resultper e, in modo da:flatten = lambda *args: (result for mid in args for result in (flatten(*mid) if isinstance(mid, (tuple, list)) else (mid,)))
In pausa fino a nuovo avviso.

Questo è significativamente più veloce di compiler.ast.flatten. Ottimo codice compatto, funziona per qualsiasi tipo di oggetto (credo).
bcdan,

Wow, questa dovrebbe essere la risposta più votata e accettata ... funziona come un fascino!
U10-Forward

27

Questa versione flattenevita il limite di ricorsione di Python (e quindi funziona con iterabili nidificati arbitrariamente profondi). È un generatore in grado di gestire stringhe e iterabili arbitrari (anche infiniti).

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

Ecco alcuni esempi che ne dimostrano l'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]

Sebbene sia in flattengrado di gestire infiniti generatori, non può gestire l'annidamento infinito:

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

print(list(IT.islice(flatten(infinitely_nested()), 10)))
# hangs

1
qualche consenso sull'opportunità di utilizzare ABC Iterable o ABC Sequence?
mercoledì

sets, dicts, deques, listiterators, generators, Filehandle, e classi personalizzate con __iter__definite sono tutte le istanze collections.Iterable, ma noncollections.Sequence . Il risultato dell'appiattimento di a dictè un po 'incerto, ma per il resto, penso che collections.Iterablesia un valore predefinito migliore di collections.Sequence. È sicuramente il più liberale.
unutbu,

@wim: un problema con l'utilizzo collections.Iterableè che questo include infiniti generatori. Ho cambiato la mia risposta gestendo questo caso.
unutbu,

1
Questo non sembra funzionare per il 3o e il 4o esempio. Si getta StopIteration. Inoltre, sembra che while True: first = next(remainder) potrebbe essere sostituito dafor first in remainder: .
Georgy,

@Georgy questo potrebbe essere risolto incapsulando il corpo di appiattire in a try-except StopIteration block.
baduker,

12

Ecco un'altra risposta ancora più 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)

Fondamentalmente, converte l'elenco nidificato in una stringa, utilizza una regex per eliminare la sintassi nidificata, quindi converte il risultato in un elenco (appiattito).


Se provi a generalizzare questo a qualcosa di diverso dai valori int, sarà divertente, ad esempio [['C=64', 'APPLE ]['], ['Amiga', 'Mac', 'ST']]:) :) D'altra parte, dato un elenco che contiene se stesso, farà un po 'meglio delle altre risposte, sollevando un eccezione invece del semplice looping fino a quando non si esaurisce la memoria / ricorre fino a esaurire lo stack ...
abarnert,

Il prompt originale consisteva nell'appiattire un elenco di numeri interi. Se modifichi la comprensione dell'elenco in d = [x per x in c], dovrebbe funzionare bene per il tuo campione.
argilla,

Innanzitutto, [x for x in c]è solo un modo lento e dettagliato per fare una copia c, quindi perché dovresti farlo? In secondo luogo, il codice verrà chiaramente convertito 'APPLE ]['in 'APPLE ', poiché non gestisce le virgolette, presuppone solo che le parentesi siano parentesi di elenco.
Abarnert,

Ha! Il modo in cui il tuo commento è stato formattato sul mio computer, non mi rendevo nemmeno conto che avrebbe dovuto essere Apple II come appariva sui vecchi computer. In ogni caso, la mia risposta ad entrambe le tue domande è che questo esercizio - per me - è semplicemente un esperimento per trovare una soluzione creativa per l'appiattimento di un elenco. Non sono sicuro che lo generalizzerei per appiattire ogni elenco là fuori.
argilla,

Devi solo arr_str = str(arr)e poi [int(s) for s in re.findall(r'\d+', arr_str)]davvero. Vedi 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

8

È possibile utilizzare deepflattendal pacchetto di terze parti 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]

È un iteratore, quindi è necessario iterarlo (ad esempio avvolgendolo listo utilizzandolo in un ciclo). Internamente utilizza un approccio iterativo invece di un approccio ricorsivo ed è scritto come estensione C, quindi può essere più veloce degli approcci di puro pitone:

>>> %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)

Sono l'autore della iteration_utilitiesbiblioteca.


7

È stato divertente provare a creare una funzione che potesse appiattire la lista irregolare in Python, ma ovviamente questo è ciò che Python serve (per rendere divertente la programmazione). Il seguente generatore funziona abbastanza bene con alcuni avvertimenti:

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

Sarà appiattire i tipi di dati che si potrebbe desiderare lasciato da solo (come bytearray, bytese stroggetti). Inoltre, il codice si basa sul fatto che la richiesta di un iteratore da un non iterabile genera 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]
>>>

Modificare:

Non sono d'accordo con l'implementazione precedente. Il problema è che non dovresti essere in grado di appiattire qualcosa che non è iterabile. È confuso e dà l'impressione sbagliata dell'argomento.

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

Il seguente generatore è quasi uguale al primo ma non ha il problema di provare ad appiattire un oggetto non iterabile. Fallisce come ci si aspetterebbe quando gli viene dato un argomento inappropriato.

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

Il test del generatore funziona bene con l'elenco che è stato fornito. Tuttavia, il nuovo codice genererà a TypeErrorquando gli viene assegnato un oggetto non iterabile. Di seguito sono mostrati esempi del nuovo 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
>>>

5

Sebbene sia stata selezionata una risposta elegante e molto pitonica, vorrei presentare la mia soluzione solo per la recensione:

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

Indica quanto è buono o cattivo questo codice?


1
Usa isinstance(i, (tuple, list)). L'inizializzazione di variabili vuote è una bandiera per me guardare a strutture di codice alternative, in genere comprensioni, generatori, ricorsione, ecc.
dansalmo,

3
return type(l)(ret)ti restituirà anche lo stesso tipo di contenitore che è stato passato. :)
dash-tom-bang

@ dash-tom-bang Puoi per favore spiegare cosa significa in dettaglio.
Xolve,

1
Se passi in un elenco, probabilmente vorrai un elenco indietro. Se passi in una tupla, probabilmente vorrai una tupla indietro. Se passi in un miscuglio dei due, otterrai qualunque cosa sia racchiusa all'esterno.
dash-tom-bang,

4

Preferisco risposte semplici. Nessun generatore Nessun limite di ricorsione o ricorsione. Solo iterazione:

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

Funziona con due elenchi: un ciclo interno per e un ciclo while esterno.

Il ciclo for interno scorre la lista. Se trova un elemento list, (1) usa list.extend () per appiattire quel livello di annidamento di una parte e (2) switch keepChecking su True. keepchecking è usato per controllare il ciclo while esterno. Se il loop esterno viene impostato su true, attiva il loop interno per un altro passaggio.

Questi passaggi continuano ad accadere fino a quando non vengono trovati più elenchi nidificati. Quando alla fine si verifica un passaggio in cui non viene trovato nessuno, keepChecking non viene mai portato su true, il che significa che listIsNested rimane falso e il ciclo while esterno viene chiuso.

Viene quindi restituito l'elenco appiattito.

Prova

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

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


Anche a me piace semplice. In questo caso, tuttavia, si passa all'elenco tante volte quante sono nidificazioni o livelli. Potrebbe essere costoso.
telliott99,

@ telliott99: hai ragione se le tue liste sono davvero grandi e / o nidificate a grande profondità. Tuttavia, se non è così, la soluzione più semplice funziona altrettanto bene e senza la magia profonda di alcune delle altre risposte. C'è un posto per la comprensione del generatore ricorsivo in più fasi, ma non sono convinto che dovrebbe essere dove guardi per primo. (Immagino che tu sappia dove cadrò nel dibattito "Peggio è meglio".)
Clay

@ telliott99: O per dirla in altro modo, non dovrai "provare a Grok" la mia soluzione. Se le prestazioni non sono un collo di bottiglia, cosa conta di più per te come programmatore?
argilla

Le soluzioni più semplici hanno meno logica. La ricorsione è un costrutto di programmazione piuttosto fondamentale con cui chiunque si considera un programmatore dovrebbe sentirsi completamente a proprio agio. I generatori sono molto simili a Python Way e (insieme alle comprensioni) sono qualcosa che ogni programmatore professionista di Python dovrebbe ascoltare all'istante.
dash-tom-bang,

1
Sono d'accordo sulla ricorsione. Quando ho scritto la mia risposta, Python ha comunque interrotto la ricorsione a 1000 cicli. Hanno cambiato questo? Per quanto riguarda essere un programmatore pitone professionista, non lo sono. Inoltre, immagino che molte persone che programmano in Python non lo facciano a tempo pieno.
argilla

4

Ecco una semplice funzione che appiattisce elenchi di profondità arbitraria. Nessuna ricorsione, per evitare l'overflow dello stack.

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


3

Sono sorpreso che nessuno ci abbia pensato. Dannazione ricorsiva Non ottengo le risposte ricorsive che le persone avanzate qui hanno fatto. comunque qui è il mio tentativo su questo. un avvertimento è molto specifico per il caso d'uso dell'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)

produzione:

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

3

Non ho esaminato tutte le risposte già disponibili qui, ma qui è una riga che mi è venuta in mente, prendendo in prestito dal modo di lisp di prima ed elaborazione dell'elenco di riposo

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

ecco un caso semplice e uno non così semplice -

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

Non è una fodera. Non importa quanto si tenta di adattarlo a uno, def foo():è una linea separata. Inoltre, questo è molto illeggibile.
cs95,

Ho de-line-ified il codice e ho fatto qualche ulteriore refactoring. (la modifica è in attesa di revisione tra pari mentre scrivo questo) Questo particolare metodo mi è sembrato molto leggibile, sebbene il codice originale necessitasse di qualche refactoring.
Emilio M Bumachar,

3

Quando si cerca di rispondere a una domanda del genere, è necessario fornire le limitazioni del codice proposto come soluzione. Se si trattasse solo di spettacoli, non mi dispiacerebbe troppo, ma la maggior parte dei codici proposti come soluzione (compresa la risposta accettata) non riesce ad appiattire qualsiasi elenco che abbia una profondità maggiore di 1000.

Quando dico la maggior parte dei codici intendo tutti i codici che usano qualsiasi forma di ricorsione (o chiamo una funzione di libreria standard ricorsiva). Tutti questi codici falliscono perché per ciascuna delle chiamate ricorsive effettuate, lo stack (call) cresce di un'unità e lo stack (predefinito) di chiamate python ha una dimensione di 1000.

Se non hai troppa familiarità con lo stack di chiamate, forse ti sarà utile quanto segue (altrimenti puoi semplicemente scorrere fino all'implementazione ).

Dimensione dello stack di chiamata e programmazione ricorsiva (analogia del dungeon)

Trovare il tesoro ed uscire

Immagina di entrare in un enorme sotterraneo con stanze numerate , alla ricerca di un tesoro. Non conosci il posto ma hai alcune indicazioni su come trovare il tesoro. Ogni indicazione è un indovinello (la difficoltà varia, ma non è possibile prevedere quanto saranno difficili). Decidi di pensare un po 'a una strategia per risparmiare tempo, fai due osservazioni:

  1. È difficile (lungo) trovare il tesoro in quanto dovrai risolvere enigmi (potenzialmente difficili) per arrivarci.
  2. Una volta trovato il tesoro, tornare all'ingresso potrebbe essere facile, devi solo usare lo stesso percorso nell'altra direzione (anche se questo ha bisogno di un po 'di memoria per ricordare il tuo percorso).

Quando entri nel sotterraneo, noti un piccolo quaderno qui. Decidi di usarlo per annotare ogni stanza che esci dopo aver risolto un indovinello (quando entri in una nuova stanza), in questo modo sarai in grado di tornare indietro all'ingresso. È un'idea geniale, non dovrai nemmeno spendere un centesimo per attuare la tua strategia.

Entri nel sotterraneo, risolvendo con grande successo i primi 1001 enigmi, ma ecco che arriva qualcosa che non avevi pianificato, non hai più spazio nel quaderno che hai preso in prestito. Decidi di abbandonare la tua ricerca poiché preferisci non avere il tesoro piuttosto che perderti per sempre nel sotterraneo (che sembra davvero intelligente).

Esecuzione di un programma ricorsivo

Fondamentalmente, è esattamente la stessa cosa di trovare il tesoro. Il dungeon è la memoria del computer , il tuo obiettivo ora non è trovare un tesoro ma calcolare alcune funzioni (trova f (x) per una data x ). Le indicazioni sono semplicemente delle routine secondarie che ti aiuteranno a risolvere f (x) . La tua strategia è la stessa della strategia di stack di chiamate , il notebook è lo stack, le stanze sono gli indirizzi di ritorno delle funzioni:

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))

Il problema che hai riscontrato nel dungeon sarà lo stesso qui, lo stack di chiamate ha una dimensione finita (qui 1000) e quindi, se inserisci troppe funzioni senza tornare indietro, riempirai lo stack di chiamate e avrai un errore che sembra come "Caro avventuriero, mi dispiace molto ma il tuo quaderno è pieno" :RecursionError: maximum recursion depth exceeded . Nota che non hai bisogno di ricorsione per riempire lo stack di chiamate, ma è molto improbabile che un programma non ricorsivo chiami 1000 funzioni senza mai tornare. È anche importante capire che una volta tornato da una funzione, lo stack di chiamate viene liberato dall'indirizzo utilizzato (da cui il nome "stack", l'indirizzo di ritorno viene inserito prima di inserire una funzione e estratto al ritorno). Nel caso speciale di una semplice ricorsione (una funzionefche si chiama una volta - ancora e ancora -) entrerai fpiù e più volte fino a quando il calcolo non sarà terminato (fino a quando non sarà trovato il tesoro) e tornerai da fquando torni al luogo in cui hai chiamato fin primo luogo. Lo stack di chiamate non verrà mai liberato da nulla fino alla fine in cui verrà liberato uno dopo l'altro da tutti gli indirizzi di ritorno.

Come evitare questo problema?

In realtà è piuttosto semplice: "non usare la ricorsione se non sai quanto in profondità può andare". Questo non è sempre vero come in alcuni casi, la ricorsione di Tail Call può essere ottimizzata (TCO) . Ma in Python non è così, e anche la funzione ricorsiva "ben scritta" non ottimizzerà l'uso dello stack. C'è un post interessante di Guido su questa domanda: Eliminazione della ricorsione della coda .

Esiste una tecnica che puoi usare per rendere iterativa qualsiasi funzione ricorsiva, questa tecnica che potremmo chiamare portare il tuo taccuino . Ad esempio, nel nostro caso particolare stiamo semplicemente esplorando un elenco, entrare in una stanza equivale a inserire un elenco secondario, la domanda che dovresti porti è come posso tornare da un elenco al suo elenco padre? La risposta non è così complessa, ripetere quanto segue fino a quando non stackè vuota:

  1. spingere l'elenco corrente addresse indexin a stackquando si inserisce un nuovo elenco secondario (si noti che un indirizzo elenco + indice è anche un indirizzo, quindi utilizziamo esattamente la stessa tecnica utilizzata dallo stack di chiamate);
  2. ogni volta che viene trovato un elemento, yield(o aggiungerlo in un elenco);
  3. una volta che un elenco è stato completamente esplorato, tornare all'elenco parent usando il stack tasto return address(e index) .

Si noti inoltre che ciò equivale a un DFS in un albero in cui alcuni nodi sono elenchi secondari A = [1, 2]e altri elementi semplici: 0, 1, 2, 3, 4(per L = [0, [1,2], 3, 4]). L'albero si presenta così:

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

Il preordine di attraversamento DFS è: L, 0, A, 1, 2, 3, 4. Ricorda che per implementare un DFS iterativo devi anche "avere bisogno" di uno stack. L'implementazione che ho proposto prima porta ad avere i seguenti stati (per 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]

In questo esempio, la dimensione massima dello stack è 2, poiché l'elenco di input (e quindi l'albero) ha profondità 2.

Implementazione

Per l'implementazione, in Python puoi semplificare un po 'usando iteratori invece di semplici elenchi. I riferimenti ai (sotto) iteratori verranno utilizzati per memorizzare gli indirizzi di ritorno delle liste secondarie (invece di avere sia l'indirizzo della lista che l'indice). Questa non è una grande differenza, ma penso che sia più leggibile (e anche un po 'più veloce):

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)

Inoltre, nota che in is_list_likeI have isinstance(item, list), che potrebbe essere modificato per gestire più tipi di input, qui volevo solo avere la versione più semplice in cui (iterabile) è solo un elenco. Ma potresti anche farlo:

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

Questo considera le stringhe come "elementi semplici" e quindi flatten_iter([["test", "a"], "b])ritornerà ["test", "a", "b"]e non ["t", "e", "s", "t", "a", "b"]. Si noti che in quel caso, iter(item)viene chiamato due volte su ogni elemento, facciamo finta che sia un esercizio per il lettore rendere questo più pulito.

Test e osservazioni su altre implementazioni

Alla fine, ricorda che non puoi stampare un elenco infinitamente nidificato Lusando print(L)perché internamente utilizzerà le chiamate ricorsive a __repr__( RecursionError: maximum recursion depth exceeded while getting the repr of an object). Per lo stesso motivo, le soluzioni al flattencoinvolgimento strfalliranno con lo stesso messaggio di errore.

Se è necessario testare la soluzione, è possibile utilizzare questa funzione per generare un semplice elenco nidificato:

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

Che dà: build_deep_list(5)>>> [4, [3, [2, [1, [0]]]]].


2

Ecco l' compiler.ast.flattenimplementazione in 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

Esistono metodi migliori e più veloci (se hai raggiunto qui, li hai già visti)

Nota anche:

Obsoleto dalla versione 2.6: il pacchetto del compilatore è stato rimosso in Python 3.


2

totalmente confuso ma penso che funzionerebbe (a seconda del tipo di dati)

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

2

Basta usare una funcylibreria: pip install funcy

import funcy


funcy.flatten([[[[1, 1], 1], 2], 3]) # returns generator
funcy.lflatten([[[[1, 1], 1], 2], 3]) # returns list

1
A proposito: utilizza una soluzione ricorsiva: collegamento alla fonte
Georgy,

1

Ecco un altro approccio py2, non sono sicuro che sia il più veloce o il più elegante né il più sicuro ...

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))

Può ignorare qualsiasi tipo specifico (o derivato) che desideri, restituisce un iteratore, quindi puoi convertirlo in qualsiasi contenitore specifico come elenco, tupla, dettare o semplicemente consumarlo al fine di ridurre il footprint di memoria, nel bene o nel male può gestire oggetti iniziali non iterabili come int ...

Nota che la maggior parte del sollevamento pesante viene eseguita in C, dal momento che per quanto ne so come sono implementati gli itertools, quindi mentre è ricorsivo, AFAIK non è limitato dalla profondità di ricorsione del pitone poiché le chiamate di funzione stanno avvenendo in C, anche se questo non significa che sei limitato dalla memoria, specialmente in OS X dove le sue dimensioni dello stack hanno un limite rigido ad oggi (OS X Mavericks) ...

c'è un approccio leggermente più veloce, ma un metodo meno portatile, usalo solo se puoi presumere che gli elementi base dell'input possano essere determinati esplicitamente altrimenti, otterrai una ricorsione infinita e OS X con le sue dimensioni dello stack limitate, lo farà genera un errore di segmentazione abbastanza 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))

qui stiamo usando set per verificare il tipo, quindi ci vuole O (1) vs O (numero di tipi) per verificare se un elemento deve essere ignorato o meno, anche se ovviamente qualsiasi valore con tipo derivato dei tipi ignorati dichiarati fallirà , ecco perché il suo utilizzo str , unicodequindi usalo con cautela ...

test:

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

1

Senza usare alcuna libreria:

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]

1

Utilizzando 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

O senza incatenamento:

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)

1

Ho usato ricorsivo per risolvere l' elenco annidato con qualsiasi profondità

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

Quindi, dopo aver definito la funzione combina_elenco, è facile usare questa funzione per eseguire l'appiattimento. Oppure puoi combinarlo in un'unica funzione. Mi piace la mia soluzione perché può essere applicata a qualsiasi elenco nidificato.

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

risultato

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]

"elenco nidificato con qualsiasi profondità" non vero. Prova e vedrai: current_value = combiner(current_value,each_item) RecursionError: maximum recursion depth exceeded
cglacet

hmmm stai cercando di appiattire la lista con più di 1000 livelli?
Oldyoung,

Naturalmente, questo è il punto centrale della discussione sulle soluzioni ricorsive vs. iterative. Se sai in anticipo che il numero di livelli è <di 1000, allora la soluzione più semplice funzionerà. Quando dici "qualsiasi profondità", questo include un elenco con profondità> 1000.
cglacet

1

Il modo più semplice è usare la libreria morph usando pip install morph.

Il codice è:

import morph

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

1

Sono consapevole che ci sono già molte risposte fantastiche, ma volevo aggiungere una risposta che utilizza il metodo di programmazione funzionale per risolvere la domanda. In questa risposta utilizzo la doppia ricorsione:

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

produzione:

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

1

Non sono sicuro che questo sia necessariamente più veloce o più efficace, ma questo è quello che faccio:

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

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

La flattenfunzione qui trasforma l'elenco in una stringa, elimina tutte le parentesi quadre, allega le parentesi quadre alle estremità e la trasforma in una lista.

Sebbene, se sapessi che avresti delle parentesi quadre nella tua lista in stringhe, come [[1, 2], "[3, 4] and [5]"], dovresti fare qualcos'altro.


Ciò non ha alcun vantaggio rispetto alla soluzione semplice in quanto non è in grado di elaborare elenchi profondi, ad esempio "RecursionError: superata la profondità massima di ricorsione durante il recupero della rappresentazione di un oggetto".
cglacet,

1

Questo è un semplice strumento di appiattimento su 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]

1

Questo appiattirà un elenco o un dizionario (o un elenco di elenchi o dizionari di dizionari, ecc.). Presuppone che i valori siano stringhe e crea una stringa che concatena ogni elemento con un argomento separatore. Se lo desideri, puoi utilizzare il separatore per dividere il risultato in un oggetto elenco in seguito. Utilizza la ricorsione se il valore successivo è un elenco o una stringa. Utilizzare l'argomento chiave per indicare se si desidera che le chiavi oi valori (impostare la chiave su false) dall'oggetto dizionario.

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=', ')

rendimenti:

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

0

Se ti piace la ricorsione, questa potrebbe essere una soluzione di tuo interesse:

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

In realtà l'ho adattato da un po 'di codice dello schema pratico che avevo scritto qualche tempo fa.

Godere!


0

Sono nuovo di Python e provengo da uno sfondo chiaro. Questo è quello che mi è venuto in mente (controlla i nomi var per 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]

Sembra funzionare. Test:

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

ritorna:

[1, 2, 3, 4, 5, 6, 7, 8, 1, 2]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.