Hilfe bei dieser Python Übung

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
mauzcatdog
User
Beiträge: 5
Registriert: Montag 15. Februar 2016, 16:41

Hallo Leute!

Ich beschäftige mich nun schon einige Zeit mit Python ( rein aus privater Interesse) und hab mir deshalb auch ein Buch gekauft. In diesem Buch gibt es eigentlich tolle Aufgaben aber nicht zu allen Aufgaben gibt es auch Lösungen. Ich glaube es liegt daran, das diese besagten Aufgaben eigentlich leicht zu lösen sein sollten aber ich hänge jetzt seit fast 2 Stunden und hab wirklich keine Idee. Ich hoffe das sich hier jemand findet der mir helfen kann. Also die Aufgabenstellung lautet:

Schreiben Sie eine Funktion mit dem Namen verschachtelte_summe, die eine verschachtelte Liste von Integer-Werten erwartet und die Elemente aller verschachtelten Listen summiert.

Ok...also ich hab das ganze jetzt so gelöst:

Code: Alles auswählen

[Codebox=python file=Unbenannt.py]
def summe(t):
     i=0
     for x in t:
        
         if type(x) == list:
             t.extend(x)
             t.pop(i)
             summe(t)
        
         else:
             i = i + 1
     return sum(t)
            
            
print summe([1,2,[34,76,45,2],3,90,73,[11,[22,33],12,4],4,5])[/Codebox]
Aber die Methode Pop() wurde in dem Buch erst nach dieser Aufgabe erklärt also muss diese Aufgabe auch ohne sie zu lösen sein aber wie??
Auch verstehe ich nicht ganz wieso es so wie ich es gelöst habe überhaupt funktioniert. Habe es mir im Debugger angesehen, aus irgendeinem Grund wird sobald die Funktion das letzte mal aufgerufen wird meine Liste (t) in jeder darunter liegende Funktion bzw. Stapel auch geändert?!
Das i benutze ich (denke es wird euch aber auffallen) nur, um für meinen Pop() befehl die Position zu ermitteln.
Ich hoffe es findet sich jemand der sich die zeit nimmt mir zu helfen. Wie gesagt es ist nichts für die Schule, es ist wirklich aus rein privater Interesse.
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

Das kann nicht gutgehen, weil du die Liste t, durch die du in der for-Schleife durchgehst, in selbiger Schleife mit extend und pop veränderst. Außerdem macht die Zeile summe(t) effektiv nichts, weil sie die Funktion zwar aufruft, aber nichts mit dem Rückgabewert macht.

Hier eine mögliche Lösung:

Code: Alles auswählen

def geschachtelte_summe(liste):
    return sum((geschachtelte_summe(x) if isinstance(x, list) else x) for x in liste)
    
print geschachtelte_summe([1,5,[1,2,3,],8])
print geschachtelte_summe([1,3,3,7])
print geschachtelte_summe([[[[[1]]],2]])
Allerdings ist das auch nicht ganz optimal, weil man der Funktion auch iterierbare Objekte übergeben könnte, die keine Listen sind (zB Tupel oder Sets).
PS: Die angebotene Summe ist beachtlich.
Bob Billsworth
User
Beiträge: 11
Registriert: Freitag 12. Juni 2015, 00:01

Ich möchte auch mitmachen :D :

Code: Alles auswählen

def v_s(liste):
    summe = 0
    for itgr in liste:
        if isinstance(itgr,int):
            summe += itgr
        else:
            summe += v_s(itgr)
    return summe

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

print(v_s(l))
BlackJack

@Bob Billsworth: Die Namen sind nicht wirklich gut. Bei `itgr` muss ich passen, aber auch die anderen Abkürzungen machen den Code nicht gerade leicht verständlich.
BlackJack

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from collections import Iterable


def nested_sum(value):
    return sum(map(nested_sum, value)) if isinstance(value, Iterable) else value


def main():
    print(nested_sum([1, 2, 3, 4, [1, 2, 3, [1, 2, 3], 4, 5], 5, 6]))


if __name__ == '__main__':
    main()
mauzcatdog
User
Beiträge: 5
Registriert: Montag 15. Februar 2016, 16:41

Ok jetzt habe ich zweimal was geschrieben und abgesendet und zweimal hat es nicht funktioniert ein drittes mal mach ich mir nicht nochmal die mühe so einen roman zu schreiben sry xD. Also in kurzform: Vielen Dank für eure vielen Beispiele. den isinstance befehl kannte ich nicht ich hab einfach überlegt wie ich rausfinden kann ob das eine liste ist und bin so auf mein if type(x) == list gekommen. War mir aber fast klar das es da etwas anderes gibt ;). Also Bob kann ich noch folgen, bei Üpsilon und Jack tu ich mir schwer weil ich es nicht gewohnt bin Code so zu schreiben bzw. zu lesen. Obwohl es glaub ich kaum präziser und knackiger geht als die beiden es getan haben eigentlich genial. Ich hab eigentlich generel noch große Probleme mit rekursiven aufrufen...ich muss zugeben das verwirrt mich doch sehr, hoffe das wird mit etwas übung besser. Ich hab noch nicht die richtige herangehensweise für solche Probleme glaub ich.

Was ich nur gar nicht verstehe ist: Wieso funktioniert mein Code trotzdem? Ich will da nicht drauf herumreiten weil eure funktionen alle mehr Sinn machen keine Fragen nur ich versteh nicht wieso es trotzdem klappt. Wenn ich die Funktion bei meinem Beispiel vier mal aufrufe dann rufe ich sie eigentlich mit vier verschiedenen listen auf (da ich ja die funktion erst dann nochmal aufrufe nachdem ich sie verändert habe mit extend und pop)
das mache ich solange (das war auch der gedanke dahinter) bis es keine listen mehr innerhalb meiner liste gibt...so...jetzt müsste ich ja wenn ich also von der letzten funktion wieder zurückgehe alle anderen funktionen passieren die aufgerufen wurden, in denen ja eigentlich das t (die liste) immer verschieden ist....wieso ist aber wenn ich mit meinem listen auflösen fertig bin (also in der letzten aufgereufenen funktion stehe) die liste, alsot, in JEDER Funktion gleich?!?!?! Wie kann das sein??
Üpsilon
User
Beiträge: 222
Registriert: Samstag 15. September 2012, 19:23

Das liegt daran, dass die Liste t in jedem Funktionsaufruf die *selbe* (und nicht nur die gleiche) Liste ist. Das heißt: Alle Funktionsaufrufe teilen sich ein t, und dementsprechend ändert es sich auch überall.

Schau mal, nimm deine Funktion, die du in deinem ersten Beitrag gepostet hast, und das hier:

Code: Alles auswählen

liste = [1,2,[34,76,45,2],3,90,73,[11,[22,33],12,4],4,5]
print summe(liste)
print liste
Und Überraschung! liste hat sich geändert. Das ist allerdings ein Effekt, auf den man sich nicht verlassen kann und auch wenn man das könnte, sollte man das nicht tun, weil solche globale Zustände dazu führen, dass einzelne Programmteile stark voneinander abhängen.

€: Im letzten Satz von meinem ersten Beitrag in diesem Thread meinte ich "können wollte" anstatt "könnte".

Gute Nacht.
PS: Die angebotene Summe ist beachtlich.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Problem, das ich immer mit solchen Aufgaben habe, ist das, dass sie nie im realen Programmieralltag vorkommen sollten, weil wenn man eine verschachtelte Liste hat, über die man eine Summe bilden will, schon beim Erstellen dieser Liste einen Designfehler gemacht hat.

Code: Alles auswählen

from numbers import Number

def nested_sum(iterable):
    result = 0
    stack = [iterable]
    while stack:
        for element in stack.pop():
            if isinstance(element, Number):
                result += element
            else:
                stack.append(element)
    return result
mauzcatdog
User
Beiträge: 5
Registriert: Montag 15. Februar 2016, 16:41

Da hast du sicher Recht Sirius, ich denke aber es ist genau das was es ist: eine Übung. Weil es ist viel schwieriger etwas selber zu machen als es sich einfach nur anzuschauen. Zum Beispiel kann ich fast alles auf Englisch verstehen (außer es sind wirklich einschlägige wissenschaftliche Fachgespräche mit vielen fachspezifischen Fremdwörtern ;) ) aber selber sprechen...uff da muss ich schon lange überlegen weil ich es einfach nie mache.

@Üpsilon also ich versteh schon was du mir sagen willst aber wie darf ich mir das vorstellen? ich dachte eine Funktion besitzt nur lokale variablen ist also das t (die liste) nicht in jeder Funktion eine eigene lokale variable die halt einfach nur den selben Namen hat? Ich glaub ich muss zurück auf Start und mir mal anschauen wie da manche Dinge referenziert werden. Eigentlich ist Python eine einfache Sprache es muss also an mir liegen ....kann man zu dumm sein für Python -.- ?
Seitdem du mir das gesagt hast hab ich immer Angst das ich sowas wieder mache...Was sagst du zu folgendem:
Die Aufgabe ist eine Funktion, die eine geschachtelte Liste mit strings entgegen nimmt und eine geschachtelte Liste mit strings zurückgibt in der jeder string groß geschrieben ist.
Meine Lösung:

Code: Alles auswählen

def verschachtelt_gross(t):
    neu=[]
    for x in t:
        if isinstance(x, list):
            neu.append(verschachtelt_gross(x))
        else:
            neu.append(x.upper())
            
    return neu


print verschachtelt_gross(['wie',['geht',['es','dir'],'denn'],'heute'])
BlackJack

Man sieht an den verschiedenen Lösungen auch einen Zielkonflikt wenn man versucht das allgemein zu lösen: Man muss zwischen iterierbaren und addierbaren Werten unterscheiden, aber dummerweise gibt es so einige Typen die *beides* sind. Was ist dann bei welchem Typ zu bevorzugen? Wie entscheidet man das für allgemeine Typen die man beim Programmieren der Funktion noch gar nicht kennt? Schliesst man wie ich alle iterierbaren Typen von der Addition aus? Damit würde beispielsweise eine addierbare `Point`-Klasse nicht addiert die von einem `namedtuple()` abgeleitet ist. Oder entscheidet man wie Sirius3 daran ob es sich um eine ”Zahl” handelt ob addiert wird? Das schliesst genannte `Point`-Klasse auch erst einmal aus, solange man sie nicht bei `Number` als virtuelle Unterklasse registriert, wobei man da dann natürlich aufpassen muss welche Folgen das auf anderen Code haben kann und ob man wirklich alles/genug in der `Point`-Klasse tut, damit die mit anderen Zahltypen gut zusammenspielt.

Vielleicht wäre es bei einer solchen Aufgabe auch gut sie tatsächlich beim Wort zu nehmen und eine verschachtelte Liste mit ganzen Zahlen zu *erwarten* und bei allem anderen zu motzen:

Code: Alles auswählen

def sum_nested_integer_list(values):
    result = 0
    for value in values:
        if isinstance(value, int):
            result += value
        elif isinstance(value, list):
            result += sum_nested_integer_list(value)
        else:
            raise TypeError(
                'value {0!r} ({1}) not of type `int` or `list`'.format(
                    value, type(value)
                )
            )
    return result
Eine ähnliche, leicht komplexere Aufgabe findet sich übrigens hier: http://adventofcode.com/day/12 :-)

@mauzcatdog: Jeder aktive Aufruf hat zwar seinen eigenen Namen `t`, aber da kann ja immer das *selbe* Objekt dran gebunden sein. Bei einfachen Zuweisungen oder Aufrufen kopiert Python *niemals* Werte, es werden immer die Objekte selbst übergeben. Und wenn Du in einer Funktion oder Methode etwas machst was dieses Objekt verändert, dann sieht man das natürlich auch beim Aufrufer weil es eben das selbe Objekt ist.

Dein Zeichenkettenlisten-Beispiel ist korrekt weil hier jeweils *neue* Listen erzeugt werden und keine bestehenden verändert werden. Das ist auch das sicherere Vorgehen, neue Objekte zu erstellen, statt bestehende zu verändern. Wenn eine Funktion solche Effekte auf die Eingabedaten hat die der Leser nicht erwarten würde, dann sollte man das dokumentieren.
mauzcatdog
User
Beiträge: 5
Registriert: Montag 15. Februar 2016, 16:41

Ok also ich referenzier quasi immer das gleiche Objekt? Ist das so ähnlich wie ich bei der Sprache C mit einem pointer eine Speicheradresse übergeben kann und den Inhalt, obwohl ich in einer anderen Funktion bin, verändern kann?

Also wenn ich etwas in einer Funktion neu erzeuge, wie z.B. diese Liste...dann kann es mir nicht passieren das obwohl ich in jeder Funktion eine Liste habe die "neu" heißt diese Liste bei einer Änderung in den anderen Funktion auch geändert wird? (Ich mein ganz offensichtlich ist es so sonst würde das Beispiel ja nicht funktionieren). Das heißt sobald ich etwas neu erzeuge in einer Funktion, gilt das nur und auch wirklich nur für diese Funktion?! kann ich das so auf den Punkt bringen?
BlackJack

@mauzcatdog: Ja das ist so ähnlich wie Zeiger in C. Allerdings gibt es in Python keine Zeiger als (Python-)Datentyp, darum sollte man das vielleicht besser Referenzen nennen. Denn im Gegensatz zu C bekommst Du nie den Zeiger selbst zu fassen um damit etwas zu machen. Du kannst nicht entscheiden ob Du dereferenzieren willst oder nicht, das wird immer gemacht. Die Übergabestrategie wird auch „call by object sharing“ genannt, weil „call by reference“ bei vielen Programmieren die falsche Erwartung weckt da würden auf Python-Ebene Zeiger übergeben die sich wie C-Zeiger nutzen lassen.

Wenn in einer Funktion ein neues Objekt erzeugt wird, dann ist das im Normalfall nur in dieser Funktion bekannt und kann nur dort verändert werden. Wobei man im Hinterkopf behalten sollte, dass natürlich durch Aufrufe von Funktionen oder Methoden mit dem Objekt als Argument, das Objekt natürlich auch ”nach aussen” transportiert und dort verändert werden könnte. Normalerweise sollte das nicht ”überraschend” passieren. Aber verschiedene Leute haben unterschiedliche Vorstellungen davon was ”überraschend” ist und was nicht, und es gibt natürlich auch Programmierer denen das Problem nicht bewusst ist, oder denen es ”egal” ist.

Das hier wäre IMHO ein Beispiel wo unklar ist was man zu erwarten hat:

Code: Alles auswählen

def main():
    products = ['Chips', 'Chocolate Bar']
    vending_machine = VendingMachine(products)
    vending_machine.add_product('Cheese')
    print products
Ich wäre weder überrascht wenn `products` unverändert geblieben ist, noch wenn es am Ende auch Käse enthalten würde.
mauzcatdog
User
Beiträge: 5
Registriert: Montag 15. Februar 2016, 16:41

Ich merke schon wieso es so wenige gute Programmierer gibt...etwas schnell zusammenschustern ist ja eigentlich nicht sonderlich schwierig bzw. ist ja alles irgendwie lösbar. Aber es so zu lösen das daraus eine allgemein gültige Funktion entsteht die noch dazu kurz und präzise ist, ist anscheinend gar nicht so einfach und erfordert doch eine anständige Kenntnis der Sprache und natürlich muss man über eine vernünftige Lösung auch länger nachdenken als bei einer "schnellen".

Auf jedenfall vielen Dank für die tolle Unterstützung, hat mich als Anfänger wieder ein bisschen vorwärts gebracht!
Antworten