Seite 1 von 1

Alle Elemente aus verschachtelten Datentypen

Verfasst: Montag 23. März 2009, 14:33
von Nocta
Hi, angeregt durch diesen Thread habe ich mal eine rekursive Funktion geschrieben, die alle Elemente einer Liste zurückgibt (ohne Verschachtelungen).

Code: Alles auswählen

def get_all_items(oldlist, items = []):
    newlist = []
    for i in oldlist:
        if type(i) == list and len(i) > 0:
            for j in i:
                newlist.append(j)
        else:
            items.append(i)
    if len(newlist) > 0:
        return get_all_items(newlist, items)
    else:
        return items
Klappt wunderbar, wenn ich das Beispielsweise so aufrufe:

Code: Alles auswählen

>>> print get_all_items([1, 2, [3, 4, [5, 6, []], 7], 8, 9])
>>> [1, 2, 8, 9, 3, 4, 7, 5, 6, []]
Aber aus Generator Expressions oder so kann man ja auch noch einzelne Elemente extrahieren, also habe ich mal versucht eine allgemeinere Funktion zu schreiben, die wie in der Überschrift angedeutet alle verschachtelten Datenstrukturen (kann man das so nennen?) auflöst und mir nur die Elemente zurückgibt.

Code: Alles auswählen

def get_all_items(olditer, items = []):
    newiter = []
    for i in olditer:
        if hasattr(i, '__iter__'):
            for j in i:
                newiter.append(j)
        else:
            items.append(i)
    if len(newiter) > 0:
        return get_all_items(newiter, items)
    else:
        return items

>>> print get_all_items([1, 2, [3, 4, [5, 6, [], (x for x in range(9))], 7], 8, 9])
>>> [1, 2, 8, 9, 3, 4, 7, 5, 6, 0, 1, 2, 3, 4, 5, 6, 7, 8]
Klappt wunderbar, nur sagt er mir, wenn ich ein Dictionary, Tuple oder sonstiges ohne append-Methode reinpacke, kommt folgendes folgendes:

Code: Alles auswählen

items.append(i)
AttributeError: 'tuple' object has no attribute 'append'
Ich kann mir nur nicht erklären, wieso newiter oder items (nur auf diese Objekte wende ich append() an) plötzlich den Typ dict oder tuple haben.
Natürlich ist auch abgesehen von der Lösung meines Problems wie immer auch Kritik an anderen Punkten erwünscht.

Das ist übrigens nur Spielerei, ich brauche so eine Funktion eigentlich nicht (sonst würde ich die wahrscheinlich zur Sicherheit auch eher iterativ schreiben)

Verfasst: Montag 23. März 2009, 14:49
von helduel
Moin,

ich bekomme diesen Fehler nicht. Kannst du den mal nachstellen?

Deine Funktion stellt die Elemente eines Iterables innerhalb eines Iterables ans Ende. Ist das gewollt?

"def get_all_items(...)" wird nur einmal ausgeführt! D.h. dein items-Argument zeigt immer auf dieselbe Liste, wenn keine explizit angegeben ist.

Code: Alles auswählen

>>> get_all_items([1, 2, 3])
[1, 2, 3]
>>> get_all_items(['a', 'b', 'c'])
[1, 2, 3, 'a', 'b', 'c']
Gruß,
Manuel

Verfasst: Montag 23. März 2009, 15:12
von Nocta
helduel hat geschrieben:ich bekomme diesen Fehler nicht. Kannst du den mal nachstellen?
Oh, war anscheinend ein Schreibfehler. Ich habe die Liste zugemacht und das Tupel nicht in die Liste geschrieben sondern als Argument (also ``items``) übergeben. Klar, dass dann die Fehlermeldung kommt.
helduel hat geschrieben:Deine Funktion stellt die Elemente eines Iterables innerhalb eines Iterables ans Ende. Ist das gewollt?
Wie gesagt habe ich keinen wirklichen Nutzen für die Funktion.
Daher war die Reihenfolge nicht gewollt, weil sie mich bis hierhin noch gar nicht interessiert hat ;) Aber du hast schon recht, es wäre natürlich irgendwo schöner, wenn die Reihenfolge stimmt.
Edit: Aber das könnte eventuell kompliziert werden, weil ich ja einfach die Reihenfolge übernehme, die die Rekursion erstellt und die Iterables innerhalb eines Iterables werden natürlich nach den nicht-Iterables gestellt, weil diese eine Rekursionseben tiefer liegen. Naja ich muss mal drüber nachdenken, vielleicht geht das ja durch einen ganz einfachen Trick.
helduel hat geschrieben:"def get_all_items(...)" wird nur einmal ausgeführt! D.h. dein items-Argument zeigt immer auf dieselbe Liste, wenn keine explizit angegeben ist.
Hm, der Defaultwert erstellt also kein neues Objekt (außer beim 1. Mal)?
Das finde ich irgendwie komisch, ich dachte man könnte das gleichsetzen mit get_all_items(bla, []). Kann ich das irgendwie ändern/umgehen? Es ist ja irgendwo blöd, wenn man immer eine leere Liste übergeben müsste.

Verfasst: Montag 23. März 2009, 15:17
von helduel
Nocta hat geschrieben:Hm, der Defaultwert erstellt also kein neues Objekt (außer beim 1. Mal)?
Genau. Bzw., zur "Compile-Zeit".
Nocta hat geschrieben:Das finde ich irgendwie komisch, ich dachte man könnte das gleichsetzen mit get_all_items(bla, []). Kann ich das irgendwie ändern/umgehen? Es ist ja irgendwo blöd, wenn man immer eine leere Liste übergeben müsste.
Du kannst als Default None setzen und in der Funktion prüfen, ob items None ist und ggf. dann eine leere Liste erzeugen.

Gruß,
Manuel

Verfasst: Montag 23. März 2009, 15:51
von DasIch
Wieso so kompliziert?

Code: Alles auswählen

In [1]: from numbers import Number

In [2]: def flatten(seq, excs=(basestring, Number)):
   ...:     for item in seq:
   ...:         if isinstance(item, excs):
   ...:             yield item
   ...:         else:
   ...:             for flattened in flatten(item, excs):
   ...:                 yield flattened

Verfasst: Montag 23. März 2009, 16:18
von Nocta
@DasIch: Das ist ja keine Rekursion mehr ;)
Aber meine Rekursion ist vielleicht auch komplizierter als nötig, kann schon sein ;)

Verfasst: Montag 23. März 2009, 16:19
von BlackVivi
Nocta hat geschrieben:@DasIch: Das ist ja keine Rekursion mehr ;)
Doch, natürlich o_o Schau nochmal genauer hin.

Verfasst: Montag 23. März 2009, 16:43
von str1442
@Nocta:

isinstance() ist statt type(x) == bla (was man übringens besser als type(x) is bla schreiben könnte, da man davon ausgehen kann, das sich der Type / die Klasse nicht so schnell ändert.) der öfter genutze und iirc auch empfohlene Weg.

list.extend nutzen anstatt einzeln durch die Liste zu iterieren, oder einfach Liste 2 zu liste 1 addieren.

Und items als Standardwert eine Liste zu geben ist meist schlecht, da Funktionsparameter nicht zum Scope der Funktion gehören und deswegen nur ein einziges Mal erzeugt werden. Wenn du die Funktion zweimal anwendest wirst du sehen, das dein items den Müll vom letzten Aufruf noch beinhaltet.

Verfasst: Montag 23. März 2009, 17:47
von Nocta
Doch, natürlich o_o Schau nochmal genauer hin.
Oh, ja okay.
Stimmt :)
Okay, das mit isinstance() werde ich dann demnächst auch benutzen.

Code: Alles auswählen

Und items als Standardwert eine Liste zu geben ist meist schlecht, da Funktionsparameter nicht zum Scope der Funktion gehören und deswegen nur ein einziges Mal erzeugt werden. Wenn du die Funktion zweimal anwendest wirst du sehen, das dein items den Müll vom letzten Aufruf noch beinhaltet.
Joa das haben wir in dem Thread ja schon festgestellt und hab ich mit 'None' geändert. Damit geht's dann komischerweise wieder.