@LRK: Noch mal zurück zu Deinem „zu komplex für mich“: Programmieren besteht zum grossen Teil darin Probleme in kleinere Teilprobleme zu zerlegen, solange bis die eben nicht mehr zu komplex sind um sie in einer kleinen Funktion mit ein paar Zeilen zu lösen. Habe gerade am Anfang keine Scheu mehr Funktionen, die ein kleines Teilproblem lösen, zu schreiben als ”notwendig” sind.
Und lass Dich nicht von Komplettlösungen erschlagen, denn die sind in der Regel auch nicht einfach so im Kopf des Programmierers magisch aufgetaucht und am Stück runtergeschrieben worden, sondern sind das Ergebnis von einzelnen Schritten, sowohl bei der Planung, als auch bei der Umsetzung.
Schauen wir uns noch mal das vorherige Problem an wo Du ausgegeben haben wolltest, an welchen Stellen Buchstaben vorkommen die mehr als einmal enthalten sind. Ausgangspunkt war: ``[["A", "B"], ["C", "D"], ["E", "F"], ["C", "E"], ["F", "C"]]`` und das Ziel die Ausgabe:
'C' kommt 3 mal vor: [1][0], [3][0], [4][1].
'E' kommt 2 mal vor: [2][0], [3][1].
'F' kommt 2 mal vor: [2][1], [4][0].
Das war Deine Antwort auf die Frage was denn konkret heraus kommen soll. Und nun muss man dieses Problem in Teilprobleme zerlegen. Zum Beispiel in einen oder mehrere Schritte die zwischen der ursprünglichen Liste und der gewünschten Ausgabe stehen. Man kann sich da von beiden Seiten einer Lösung nähern. Zum Beispiel kann man sich fragen was man denn für *Daten* und welcher Form braucht um die gezeigte Ausgabe zu bekommen. Man hat eine Zeile pro Zeichen das mehrfach vorkommt und in jeder Zeile sind die variablen Teile: Das Zeichen selbst, die Anzahl, und die Pfade/Stellen die zu diesem Zeichen führen. Mal für ein Zeichen als Daten ausgedrückt: ``("C", 3, [(1, 0), (3, 0), (4, 1)])``. Oder für alle drei:
Code: Alles auswählen
result = [
("C", 3, [(1, 0), (3, 0), (4, 1)]),
("E", 2, [(2, 0), (3, 1)]),
("F", 2, [(2, 1), (4, 0)]),
]
Was hier auffällt, ist dass die Anzahl grundsätzlich immmer gleich der Länge der Liste mit den Fundstellen ist, man die also gar nicht extra speichern braucht, weil man sie mit `len()` abfragen kann:
Code: Alles auswählen
result = [
("C", [(1, 0), (3, 0), (4, 1)]),
("E", [(2, 0), (3, 1)]),
("F", [(2, 1), (4, 0)]),
]
Mit diesen Daten kann man sich jetzt daran machen eine Funktion zu schreiben, welche die Ausgabe erstellt. Dabei könnten sich zwei (verschachtelte) Teilprobleme stellen: Wie wandelt man die Pfade von einer Liste mit Tupeln in eine Zeichenkette um. Und dieses Teilproblem lässt sich lösen in dem man eine Funktion schreibt, die ein einzelnes Tupel in eine gewünschte Form als Zeichenkette umwandelt und diese Funktion für jedes Tupel ausführt. Also mal das kleinste Teilproblem: Ein Tupel mit zwei Zahlen in eine Zeichenkette umwandeln, die diese beiden Zahlen in eckigen Klammern enthält:
Code: Alles auswählen
def format_path(path):
return "[{}][{}]".format(*path)
Wichtig: Teillösungen testen, ob sie auch das tun was sie sollen, denn wenn sie noch nicht funktionieren, macht es keinen Sinn sie in einer grösseren Teillösung zu verwenden, denn dort werden sie dann auch nicht funktionieren, und damit auch die grössere Teillösung nicht. Hier ein einfacher Testaufruf:
Wenn man diese Funktion hat, kann man darauf aufbauend eine schreiben, die mehrere Pfade bekommt und die so formatiert und daraus eine Zeichenkette erstellt wo sie mit Kommas getrennt sind:
Code: Alles auswählen
def format_paths(paths):
return ", ".join(map(format_path, paths))
Test:
Code: Alles auswählen
In [34]: format_paths([(1, 0), (3, 0), (4, 1)])
Out[34]: '[1][0], [3][0], [4][1]'
Nun kann man eine Funktion schreiben welche die Ergebnisdatenstruktur ausgibt. Hier mal der gesamte bisherige Code, der die bekannte Ausgabe schreibt:
Code: Alles auswählen
#!/usr/bin/env python3
def format_path(path):
return "[{}][{}]".format(*path)
def format_paths(paths):
return ", ".join(map(format_path, paths))
def print_result(result):
for letter, paths in result:
print(
"{!r} kommt {} mal vor: {}.".format(
letter, len(paths), format_paths(paths)
)
)
def main():
data = [["A", "B"], ["C", "D"], ["E", "F"], ["C", "E"], ["F", "C"]]
print(data)
#
# TODO ...
#
result = [
("C", [(1, 0), (3, 0), (4, 1)]),
("E", [(2, 0), (3, 1)]),
("F", [(2, 1), (4, 0)]),
]
print_result(result)
if __name__ == "__main__":
main()
Ausgabe haben wir schon mal und damit auch sichergestellt, dass das `result` alle Daten enthält und für die Ausgabe praktisch strukturiert ist. Jetzt müssen wir irgendwie von `data` zu `result` kommen. Wir brauchen dazu zu den Buchstaben Pfade. Also könnte man als ersten Zwischenschritt mal eine Liste erstellen die aus (Buchstabe, Pfad)-Paaren für jeden Buchstaben in jeder Unterliste in `data` enthält:
Code: Alles auswählen
def get_paths(data):
result = list()
for i, letters in enumerate(data):
for j, letter in enumerate(letters):
result.append((letter, (i, j)))
return result
Test:
Code: Alles auswählen
In [37]: data = [["A", "B"], ["C", "D"], ["E", "F"], ["C", "E"], ["F", "C"]]
In [38]: get_paths(data)
Out[38]:
[('A', (0, 0)),
('B', (0, 1)),
('C', (1, 0)),
('D', (1, 1)),
('E', (2, 0)),
('F', (2, 1)),
('C', (3, 0)),
('E', (3, 1)),
('F', (4, 0)),
('C', (4, 1))]
Jetzt wollen wir aber nicht jedes Vorkommen von jedem Buchstaben und einen Pfad sondern eine Abbildung von Buchstabe auf Pfade an denen er vorkommt. So etwas was einen Wert auf einen anderen abbildet ist ein Wörterbuch/`dict` in Python. Also schreiben wir eine Funktion die aus den Daten aus dem letzten Zwischenschritt ein solches Wörterbuch erstellt (mit einem normalen `dict` statt eines `collection.defaultdict`):
Code: Alles auswählen
def map_letter_to_paths(paths):
result = dict()
for letter, path in paths:
if letter in result:
result[letter].append(path)
else:
result[letter] = [path]
return result
Test:
Code: Alles auswählen
In [40]: map_letter_to_paths(get_paths(data))
Out[40]:
{'A': [(0, 0)],
'B': [(0, 1)],
'C': [(1, 0), (3, 0), (4, 1)],
'D': [(1, 1)],
'E': [(2, 0), (3, 1)],
'F': [(2, 1), (4, 0)]}
Nun sind wir schon fast am Ziel angekommen – wir brauchen die Schlüssel/Wert-Paare als Liste und ohne die Einträge wo nur ein Pfad im Wert enthalten ist:
Code: Alles auswählen
def select_duplicates(letter_to_paths):
return [
(letter, paths)
for letter, paths in letter_to_paths.items()
if len(paths) > 1
]
Test:
Code: Alles auswählen
In [44]: select_duplicates(map_letter_to_paths(get_paths(data)))
Out[44]:
[('C', [(1, 0), (3, 0), (4, 1)]),
('E', [(2, 0), (3, 1)]),
('F', [(2, 1), (4, 0)])]
Hey, Lücke geschlossen! Das gesamte Programm:
Code: Alles auswählen
#!/usr/bin/env python3
def get_paths(data):
result = list()
for i, letters in enumerate(data):
for j, letter in enumerate(letters):
result.append((letter, (i, j)))
return result
def map_letter_to_paths(paths):
result = dict()
for letter, path in paths:
if letter in result:
result[letter].append(path)
else:
result[letter] = [path]
return result
def select_duplicates(letter_to_paths):
return [
(letter, paths)
for letter, paths in letter_to_paths.items()
if len(paths) > 1
]
def format_path(path):
return "[{}][{}]".format(*path)
def format_paths(paths):
return ", ".join(map(format_path, paths))
def print_result(result):
for letter, paths in result:
print(
"{!r} kommt {} mal vor: {}.".format(
letter, len(paths), format_paths(paths)
)
)
def main():
data = [["A", "B"], ["C", "D"], ["E", "F"], ["C", "E"], ["F", "C"]]
print(data)
result = select_duplicates(map_letter_to_paths(get_paths(data)))
print_result(result)
if __name__ == "__main__":
main()
Im Grunde ist das eine Lösung die man als fertig ansehen könnte. Wenn man da noch etwas dran schrauben will, könnte man zum Beispiel `get_paths()` etwas vereinfachen in dem man eine Generatorfunktion daraus macht und `map_letter_to_paths()` in dem man ein `collections.defaultdict()` verwendet. Und man könnte schauen welche Funktionen sich sinnvoll und einfach konsolidieren lassen. Erste Kandidaten sind Funktionen die nur aus einem einzigen Ausdruck bestehen, weil man die sehr einfach an der Stelle wo sie aufgerufen werden einsetzen kann.