2013-04-23 6 views
9

Qual è il modo consigliato di appiattire gli elenchi annidati dal deprecation of the compiler package?Sostituzione di Python 3 per la funzione svuotata di compilatore.statten

>>> from compiler.ast import flatten 
>>> flatten(["junk",["nested stuff"],[],[[]]]) 
['junk', 'nested stuff'] 

So che ci sono alcune risposte di overflow dello stack per la lista appiattimento, ma sto sperando per il pythonic, pacchetto standard, "uno, e preferibilmente solo un modo ovvio" per fare questo.

risposta

4

La funzione dichiarata prende un elenco annidato e lo appiattisce in un nuovo elenco.

per appiattire un elenco arbitrario annidato in un nuovo elenco, questo funziona su Python 3 come ci si aspetta:

import collections 
def flatten(x): 
    result = [] 
    for el in x: 
     if isinstance(x, collections.Iterable) and not isinstance(el, str): 
      result.extend(flatten(el)) 
     else: 
      result.append(el) 
    return result 

print(flatten(["junk",["nested stuff"],[],[[]]])) 

Stampe:

['junk', 'nested stuff'] 

Se si desidera un generatore che fa la stessa cosa :

def flat_gen(x): 
    def iselement(e): 
     return not(isinstance(e, collections.Iterable) and not isinstance(e, str)) 
    for el in x: 
     if iselement(el): 
      yield el 
     else: 
      for sub in flat_gen(el): yield sub 

print(list(flat_gen(["junk",["nested stuff"],[],[[[],['deep']]]]))) 
# ['junk', 'nested stuff', 'deep'] 

Per Python 3.3 e versioni successive, utilizzare yield from al posto del loo p:

def flat_gen(x): 
    def iselement(e): 
     return not(isinstance(e, collections.Iterable) and not isinstance(e, str)) 
    for el in x: 
     if iselement(el): 
      yield el 
     else: 
      yield from flat_gen(el) 
+0

Costruire una lista come questa è una soluzione davvero pessima, un generatore sarebbe molto più adatto. –

+0

Direi che "soluzione veramente brutta" è un po 'forte. Se l'elenco annidato è relativamente piccolo, la creazione di un generatore potrebbe essere più lenta. Dipende dalla necessità Ho postato entrambi. – dawg

+0

Potrebbe essere più lento creare un generatore su una piccola lista, ma quando è piccolo, la differenza di fuso orario non ha importanza. Quando è grande, tuttavia, potrebbe benissimo farlo. È preferibile utilizzare il generatore a prescindere, ed è più leggibile. Oltre a ciò, 'hasattr (e," __iter __ ")' è in genere un controllo eseguito meglio utilizzando l'ABC 'Iterable' - è più leggibile in questo modo. –

10

itertools.chainitertools.chain è la soluzione migliore per l'appiattimento di qualsiasi livello univoco nidificato - è altamente efficiente rispetto a qualsiasi soluzione di puro python.

Detto questo, funzionerà su tutti i valori iterabili, quindi è necessario un controllo se si desidera evitare di appiattire le stringhe, ad esempio.

Allo stesso modo, non si appiattisce magicamente su una profondità arbitraria. Detto questo, in genere, non è necessaria una soluzione così generica, ma è preferibile mantenere i dati strutturati in modo da non richiedere appiattimento in questo modo.

Edit: Direi che se si dovesse fare appiattimento arbitraria, questo è il modo migliore:

import collections 

def flatten(iterable): 
    for el in iterable: 
     if isinstance(el, collections.Iterable) and not isinstance(el, str): 
      yield from flatten(el) 
     else: 
      yield el 

ricordarsi di utilizzare basestring in 2.x su str e for subel in flatten(el): yield el invece di yield from flatten(el) pre-3.3 .

Come notato nei commenti, direi che questa è l'opzione nucleare, ed è probabile che causi più problemi di quanti ne risolva. Invece, l'idea migliore è di rendere il tuo output più regolare (l'output che contiene un elemento lo dia ancora come una tupla di un elemento, ad esempio) e fare un appiattimento regolare di un livello in cui viene introdotto, piuttosto che tutto alla fine.

Questo renderà più logico, leggibile e più facile da utilizzare con il codice. Naturalmente, ci sono casi in cui lo ha bisogno di per fare questo tipo di appiattimento (se i dati provengono da qualche parte con cui non si può incasinare, quindi non si ha altra scelta che prenderlo nel formato mal strutturato), in cui caso, questo tipo di soluzione potrebbe essere necessario, ma in generale, è probabilmente una cattiva idea.

+0

Grazie. Questo può essere uno sciocco chiarimento, ma hai una raccomandazione su come collegarlo? '>>> itertools.chain ([[" junk ", [" nested stuff "], [], [[]]]]) ' Solitamente utilizzo list (iterable) per unire ma, se lo faccio in questo caso, finisco con una lista non flat! – Mittenchops

+0

* uniterate *? Non sono veramente sicuro di cosa significhi. 'list (iterable)' è il modo migliore per ottenere una lista da un iterabile arbitrario. –

+0

Usando la lista sopra, ottengo: '>>> lista (itertools.chain ([[" junk ", [" nested stuff "], [], [[]]]]))' '[[' junk ', [' nested stuff '], [], [[]]]] ' versus' flatten() 'che fornisce' [' junk ',' nested stuff '] ' – Mittenchops

1

Non c'è alcun metodo incorporato per una lista con nidificazione arbitraria, ma qualcosa di simile ...

def flatten(l): 
    for i in l: 
     if isinstance(i, (list, tuple)): 
      for ii in flatten(i): 
       yield ii 
     else: 
      yield i 

>>> l = ["junk",["nested stuff"],[],[[]]] 
>>> list(flatten(l)) 
['junk', 'nested stuff'] 

... lavorerà per liste e tuple.Se si desidera supportare qualsiasi oggetto che si potrebbe usare in un'espressione for item in object, allora è probabilmente meglio utilizzare anatra tipizzazione come questo ...

def flatten(l): 
    for i in l: 
     if isinstance(i, (str, bytes)): 
      yield i 
     else: 
      try: 
       for ii in flatten(i): 
        yield ii 
      except TypeError: 
       yield i 

>>> l = ["junk",["nested stuff"],[],[[]]] 
>>> list(flatten(l)) 
['junk', 'nested stuff'] 

... che è un po 'più robusto di controllo isinstance(el, Iterable), dal momento che non farà fronte con alcuni casi, come questo ...

class Range10: 
    def __getitem__(self, index): 
     if index >= 10: 
      raise IndexError 
     return index 

>>> import collections 
>>> r10 = Range10() 
>>> isinstance(r10, collections.Iterable) 
False 
>>> list(Range10()) 
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 
+0

-1 - Il controllo del tipo contro liste e tuple rende questo estremamente inflessibile. –

+0

@Lattyware La domanda era: "Qual è il modo consigliato per appiattire gli elenchi annidati ..." – Aya

+0

@Lattyware Qualche commento sulla seconda opzione? – Aya

1

mio brutto soluzione while-chain, solo per divertimento:

from collections import Iterable 
from itertools import chain 

def flatten3(seq, exclude=(str,)): 
    sub = iter(seq) 
    try: 
     while sub: 
      while True: 
       j = next(sub) 
       if not isinstance(j, Iterable) or isinstance(j, exclude): 
        yield j 
       else: 
        sub = chain(j, sub) 
        break 
    except StopIteration: 
     return 
3

è possibile utilizzare flatten funzione dal funcy libreria:

from funcy import flatten, isa 
flat_list = flatten(your_list) 

È possibile anche specificare esplicitamente i valori da seguire:

# Follow only sets 
flat_list = flatten(your_list, follow=isa(set)) 

dare uno sguardo a its implementation se si desidera un algoritmo.

+0

Mi piace molto la funzionalità. Grazie per il consiglio. – Mittenchops

Problemi correlati