Perché Python non ha una funzione "appiattisci" per le liste?


39

Erlang e Ruby hanno entrambi funzioni per appiattire le matrici. Sembra uno strumento così semplice e utile da aggiungere a una lingua. Si potrebbe fare questo:

>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> mess.flatten()
[1, 2, 3, 4, 5, 6]

O anche:

>>> import itertools
>>> mess = [[1, [2]], 3, [[[4, 5]], 6]]
>>> list(itertools.flatten(mess))
[1, 2, 3, 4, 5, 6]

Invece, in Python, si deve affrontare il problema di scrivere una funzione per appiattire le matrici da zero. Mi sembra sciocco, appiattire gli array è una cosa così comune da fare. È come dover scrivere una funzione personalizzata per concatenare due array.

L'ho cercato su Google inutilmente, quindi chiedo qui; c'è un motivo particolare per cui un linguaggio maturo come Python 3, che viene fornito con centinaia di migliaia di batterie incluse, non fornisce un metodo semplice per appiattire le matrici? L'idea di includere una tale funzione è stata discussa e respinta ad un certo punto?


2
@detly: ultimamente mi è capitato di perdere l'appiattimento quando ho usato diverse query per recuperare dati da fonti diverse. Ogni query restituisce un elenco di dizionari, quindi alla fine ho un elenco di elenchi di dizionari da trasformare in un elenco di dizionari. Ho usato un loop + extendma appiattire sarebbe stato molto più elegante. Tuttavia, mi ferisco se questo schema è abbastanza comune da giustificare l'appiattimento nella libreria standard.
Giorgio,

4
"Voglio dire, immagina se inserisci un bug nel tuo codice che modifica inavvertitamente la struttura dei tuoi dati. Flatten funzionerà comunque, ma produrrà risultati completamente sbagliati.": Questo è uno dei motivi per cui mi piacciono i linguaggi digitati staticamente. ;-)
Giorgio,


2
@BryanOakley Vedi anche commenti precedenti (anche se non per elenchi multi-livello, l'appiattimento in generale è comune)
Izkata,

3
È incorporato in Mathemaica e lo uso ampiamente.
Per Alexandersson, il

Risposte:


34

Le proposte per una flattenfunzione da aggiungere alla libreria standard appaiono di volta in volta nelle mailing list di python-dev e python-ideas . Gli sviluppatori Python di solito rispondono con i seguenti punti:

  1. Un appiattimento a un livello (che trasforma un iterabile di iterabili in un unico iterabile) è una banale espressione di una riga (x for y in z for x in y)e in ogni caso è già nella libreria standard sotto il nome itertools.chain.from_iterable.

  2. Quali sono i casi d'uso per un appiattimento multi-livello per scopi generici? Sono davvero abbastanza convincenti da aggiungere la funzione alla libreria standard?

  3. In che modo un appiattimento multilivello per uso generale dovrebbe decidere quando appiattire e quando lasciare da solo? Potresti pensare che una regola come "appiattire tutto ciò che supporta l'interfaccia iterabile" funzionerebbe, ma ciò porterebbe a un ciclo infinito per flatten('a').

Vedi ad esempio Raymond Hettinger :

È stato discusso fino alla nausea su comp.lang.python. Alla gente sembra piacere scrivere le proprie versioni di appiattire più che trovare casi d'uso legittimi che non hanno già soluzioni banali.

Un appiattitore per uso generale ha bisogno di un modo per dire cosa è atomico e cosa può essere ulteriormente suddiviso. Inoltre, non è ovvio come l'algoritmo debba essere esteso per coprire gli input con strutture di dati ad albero con dati sui nodi e sui fogli (preordine, postordine, attraversamento interno, ecc.)


Giusto per essere espliciti, questo significa che la funzione di un livello flattenpuò essere definita come lambda z: [x for y in z for x in y].
Christopher Martin,

1
"Un appiattimento generico ha bisogno di un modo per dire cosa è atomico e cosa può essere ulteriormente suddiviso.": Sembra un problema che può essere risolto usando OOP: ogni oggetto potrebbe avere un flattenmetodo. L'implementazione di questo metodo dovrebbe ricorrere in modo ricorsivo flattenal suo sottocomponente, se l'oggetto è un composito. Sfortunatamente, AFAIK non tutti i valori sono un oggetto in Python. In Ruby dovrebbe funzionare comunque.
Giorgio,

1
un aiuto appiattito per un appiattimento di un livello piuttosto che un continuo "for in for in" è già un caso abbastanza buono IMO. facilmente leggibile
dtc

2
@Giorgio Python evita questi metodi. I protocolli sono preferiti e trovo che siano molto più fluidi con cui lavorare rispetto a un design OOP poiché spesso non è nemmeno necessario implementare molto.
jpmc26,

8

Viene fornito con un tale metodo ma non lo chiama appiattire. Si chiama " catena ". Restituisce un iteratore che sarà quindi necessario utilizzare la funzione list () per trasformarlo in un elenco. Se non si desidera utilizzare un *, è possibile utilizzare la seconda versione "from_iterator". Funziona allo stesso modo in Python 3. Non funzionerà se l'input dell'elenco non è un elenco di elenchi.

[[1], [2, 3], [3, 4, 5]] #yes
[1, 2, [5, 6]] #no

C'era una volta un metodo flatten nel modulo compiler.ast ma questo era deprecato in 2.6 e quindi rimosso in 3.0. La ricorsione della profondità arbitraria, necessaria per elenchi arbitrariamente nidificati, non funziona bene con la massima profondità di ricorsione conservativa di Python. Il ragionamento per la rimozione del compilatore era in gran parte dovuto al fatto che era un disastro . Il compilatore è stato trasformato in ast ma l'appiattimento è stato lasciato alle spalle.

La profondità arbitraria può essere raggiunta con le matrici del numpy e l'appiattimento di quella libreria.


La chain.from_iteratorfunzione, come hai detto, può essere utilizzata solo per appiattire elenchi bidimensionali. Un actualy funzione di appiattire, che accetta qualsiasi quantità di liste annidate e restituisce una lista unidimensionale, sarebbe ancora massicciamente utile in molti casi (almeno a mio parere)
Hubro

2
@Hubro: "in molti casi" - puoi nominarne sei?
Gareth Rees,

1
@GarethRees: ho fatto alcuni esempi qui: programmers.stackexchange.com/questions/254279/…
Hubro

Vorrei anche arrivare al punto di sostenere che se questo altro linguaggio in effetti fornisce una tale funzionalità per appiattire un elenco nel modo molto semplice descritto, questo è uno degli argomenti più convincenti a supporto dell'aggiunta di quella semplice abilità a Python.
Bobort,

Restituisce un iteratore o un generatore?
jpmc26,

-1

... forse perché non è così difficile scriverne uno tu

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

... e poi appiattisci tutto quello che vuoi :)

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

8
asker è consapevole di ciò: "in Python, si deve affrontare il problema di scrivere una funzione per appiattire le matrici da zero". Questo non tenta nemmeno di rispondere alla domanda posta: "Questo mi sembra sciocco, appiattire gli array è una cosa così comune da fare. È come dover scrivere una funzione personalizzata per concatenare due array".
moscerino

1
Fuori tema ... Ma super cool :-) !!
SeF,

questa risposta è come dire a OP che non è un buon sviluppatore perché non sapeva come codificare la funzione da solo. Ti suggerisco di modificare l'inizio della tua risposta perché questo è un codice utile per coloro che inciampano nella domanda, anche se fuori tema
Federico Bonelli,
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.