Alle Elemente aus verschachtelten Datentypen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Montag 23. März 2009, 14:33

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)
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Montag 23. März 2009, 14:49

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
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Montag 23. März 2009, 15:12

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.
Benutzeravatar
helduel
User
Beiträge: 300
Registriert: Montag 23. Juli 2007, 14:05
Wohnort: Laupheim

Montag 23. März 2009, 15:17

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
DasIch
User
Beiträge: 2465
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Montag 23. März 2009, 15:51

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
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Montag 23. März 2009, 16:18

@DasIch: Das ist ja keine Rekursion mehr ;)
Aber meine Rekursion ist vielleicht auch komplizierter als nötig, kann schon sein ;)
Benutzeravatar
BlackVivi
User
Beiträge: 762
Registriert: Samstag 9. Dezember 2006, 14:29
Kontaktdaten:

Montag 23. März 2009, 16:19

Nocta hat geschrieben:@DasIch: Das ist ja keine Rekursion mehr ;)
Doch, natürlich o_o Schau nochmal genauer hin.
Benutzeravatar
str1442
User
Beiträge: 520
Registriert: Samstag 31. Mai 2008, 21:13

Montag 23. März 2009, 16:43

@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.
Nocta
User
Beiträge: 290
Registriert: Freitag 22. Juni 2007, 14:13

Montag 23. März 2009, 17:47

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.
Antworten