Unterliste auf doppelte Einträge Prüfen

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.
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

Vielen Dank für die lieben Worte. Habe überlegt ob ich mich beruflich umorientieren sollte und wollte evtl. etwas im IT Bereich machen. (Der Zeit Lagerarbeiter) Da mir Kollegen aus dem Büro und Lager sagen ich solle etwas mit PC machen, da ich mich gut auskenne. Naja kleinere Scripte in VB und Excel gemacht habe und mich mit der ein oder anderen Software gut auskenne. Ich weiß das die Jungs und Mädels hier viel Erfahrung haben. Mein Vater meinte früher immer zu mir "man muss denn Anfang von etwas kennen um das neue zu verstehen" ich denke beim Programmieren ist es ähnlich. Zuerst wissen wie man es früher gemacht hat ist nicht verkehrt um zu verstehen warum es heute anderst ist. Ich finde es toll wie andere hier einem helfen auch wenn ich es nicht immer gleich verstehe. Aber ich nehme mir Zeit um nach zu schauen welcher Baustein, eines Codes was macht. Ein Freund von mir ist Programmierer und er meint auch das es das richtige für mich ist und er auch nicht alles versteht und vieles nachlesen muss.
Danke nochmals.
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

Derzeit sieht es durch die Hilfe von _blackjack_ so aus.

data = [['A', 'B'], ['C', 'D'], ['E', 'F'], ['G', 'C'], ['F', 'H']]

Ergebnis: [['A', 'B'], ['C', 'D'], ['E', 'F'], ['G'], ['H']]

Ich möchte aber eigentlich dass das Ergebniss so aussieht.

Ergebnis: [['A', 'B'], ['C', 'D'], ['E', 'F'], ['F', 'H']]

Das dass ganze Object gelöscht wird und ich die Möglichkeit habe nur nach doppelten werten in Data[x][1] zu suchen und dessen Object gelöscht wird.

"for letters in data:" soll und wird durchlaufen
Aber
"for letter in letters:" soll nur die zweite Position durchlaufen. [1] Und wenn etwas doppelt ist in "Letters" gelöscht werden. [x] löschen.

Soll in Liste bleiben
Data[4][0] Darf nur doppelt vorkommen, wenn Data[4][1] nicht doppelt vorkommt.

Darf nicht in Liste bleiben
Data[3][0] kommt zwar nicht doppelt vor, aber Data[3][1] kommt doppelt vor. Deswegen soll Data[3] gelöscht werden.


Es fällt mir wahnsinnig schwer mich aus zu drücken.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn in letters nur Element [1] interessant ist, dann brauchst Du keine for-Schleife.

Was hast du denn schon versucht. Alles was Du brauchst, sollte Dir ja jetzt bekannt sein.
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

Ich dachte ich muss Letters mit einer for Schleife durchlaufen und wenn letter[1] schon gesehen worden ist Letters löschen.
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LRK: Vielleicht ist hier ein Problem auch das keiner hier weiss was das da überhaupt bedeuten soll und Code der mit irgendwelchen sinnfreien Listen und Indexen herum hampelt nur schwer verständlich ist.

Wenn die inneren Listen immer genau zwei Werte haben und beide Werte etwas anderes bedeuten, also der Index des Elements die Bedeutung festlegt, dann sind die inneren Listen eigentlich keine Listen sondern Tupel. Und wenn die beiden Elemente eine unterschiedliche Bedeutung haben, dann kann man ja auch einfach mal sagen *was* die Bedeuten. Und zwar *im Code*, in dem man mit `collections.namdedtupel()` einen Typ erstellt an dessen Namen die Bedeutung der beiden zusammengefassten Werte als Einheit klar wird, und an den Attributnamen die Bedeutung der beiden Werte. Dann wird es nicht nur leichter solchen Code zu lesen und zu verstehen, sondern auch ihn zu schreiben, weil Fehler viel leichter auffallen – auch dem Programmierer beim schreiben des Codes.

Gute Namen sind wichtig! Das ist keine Kosmetik, sondern wirklich wichtiges Hilfsmittel zum Programmieren und zur Fehlersuche.

Da Du immer von „löschen“ sprichst: Das macht man nicht solange man das vermeiden kann. Habe ich ja auch nicht gemacht. Man baut eine neue Liste auf in der nur das steht was man haben will. Das was man nicht haben will wird nicht gelöscht, sondern einfach nicht in die neue Liste übernommen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

Tut mir leid ich war mir der Dimension nicht bewusst.
Mit "collections.namdedtupel()" tubel kann ich noch nichts anfangen aber Versuche mich natürlich damit zu beschäftigen.

Das mit dem neue Liste erstellen verstehe ich jetzt ja und das man einfach das was man nicht haben möchte einfach weg lässt in der neuen Liste.

Zu meiner Namensgebung der Liste und meiner Vorstellung im Kopf.
Kann ich nur so beschreiben.
Ich habe eine Text Datei mit Firmenname und Internet Adressen die sieht so aus.

Firmenname
Internet-Adresse

...
...

Oft ist der Firmenname gleich und die Internet-Adresse eine andere. Diese Einträge möchte ich behalten.
Aber es kommt sehr oft vor das der Firmenname unterschiedlich ist aber die Internet-Adresse doppelt vor kommt. Diese brauche ich nicht doppelt und wollte deswegen Firmenname und Internet-Adresse in der neuen Liste weg lassen.

Stelle es mir so vor.
Liste[[Firmenname1, Internet-Adresse1],[Firmenname2, Internet-Adresse2],[Firmenname3, Internet-Adresse1],[Firmenname1, Internet-Adresse4]]

Hier wäre "[Firmenname3, Internet-Adresse1]" überflüssig, weil ich die Internet-Adresse1 schon habe.
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@LRK: Also in Worten: Man hat Firmeneinträge und für jeden Eintrag einen Namen und eine URL. Man muss sich die URLs merken die man schon gesehen hat. Und das Ergebnis. Dann schaut man für jede Firma ob die URL schon mal vorkam. Falls nicht, merkt man sich die URL und schreibt den Firmeneintrag ins Ergebnis. Falls nicht, macht man einfach nichts.

Wenn man das mit namenlosen verschachtelten Listen macht und nicht weiss was das eigentlich alles bedeuten soll, ist das beste was man machen kann:

Code: Alles auswählen

    seen = set()
    result = list()
    for pair in data:
        if pair[1] not in seen:
            seen.add(pair)
            result.append(pair)
Wenn man `data`, `pair`, und der ”magischen” 1 für den Indexzugriff sinnvolle Namen gibt, sieht das so aus:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import namedtuple
from pprint import pprint

Company = namedtuple("Company", "name url")


def main():
    companies = list(
        map(
            Company._make,
            [
                ("Firmenname1", "Internet-Adresse1"),
                ("Firmenname2", "Internet-Adresse2"),
                ("Firmenname3", "Internet-Adresse1"),
                ("Firmenname1", "Internet-Adresse4"),
            ],
        )
    )
    pprint(companies)
    print()
    seen_urls = set()
    result = list()
    for company in companies:
        if company.url not in seen_urls:
            seen_urls.add(company.url)
            result.append(company)
    pprint(result)


if __name__ == "__main__":
    main()
Ausgabe:

Code: Alles auswählen

[Company(name='Firmenname1', url='Internet-Adresse1'),
 Company(name='Firmenname2', url='Internet-Adresse2'),
 Company(name='Firmenname3', url='Internet-Adresse1'),
 Company(name='Firmenname1', url='Internet-Adresse4')]

[Company(name='Firmenname1', url='Internet-Adresse1'),
 Company(name='Firmenname2', url='Internet-Adresse2'),
 Company(name='Firmenname1', url='Internet-Adresse4')]
Es ist zwar genau der gleiche Algorithmus und dem Rechner/Python ist das egal ob es nun `companies` oder `data` heisst, `pair` oder `company`, oder ``pair[1]`` oder ``company.url`` - aber dem menschlichen Leser, was den Programmierer ja mit einschliesst, sagen Namen mit Bedeutung halt mehr. Vergleich mal den Code der `result` erstellt in den beiden Varianten mit der Prosabeschreibung im ersten Absatz. Was da näher dran ist.

`more_itertools` ist ja eines der externen Module die ich sehr nützlich finde, da gibt es schon etwas passendes, so dass das eigentliche erzeugen von `result` zum Einzeiler wird:

Code: Alles auswählen

#!/usr/bin/env python3
from collections import namedtuple
from operator import attrgetter
from pprint import pprint

from more_itertools import unique_everseen

Company = namedtuple("Company", "name url")


def main():
    companies = list(
        map(
            Company._make,
            [
                ("Firmenname1", "Internet-Adresse1"),
                ("Firmenname2", "Internet-Adresse2"),
                ("Firmenname3", "Internet-Adresse1"),
                ("Firmenname1", "Internet-Adresse4"),
            ],
        )
    )
    pprint(companies)
    print()
    result = list(unique_everseen(companies, attrgetter("url")))
    pprint(result)


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Benutzeravatar
__blackjack__
User
Beiträge: 14047
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@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:

Code: Alles auswählen

In [33]: format_path((42, 23))
Out[33]: '[42][23]'
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.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

@__blackjack__
Vielen Dank, vielen vielen Dank. Du hast dir wahnsinnig viel Mühe gegeben. Ich habe mir alles durchgelesen und finde es super wie du die einzelnen Schritte beschreibst. Ich verstehe jetzt viel besser was, warum wieso und für was was welcher Code steht. Du bist bestimmt Lehrer an ner Uni oder so. :D

Was bedeutet das " {!r} " in print_result, warum " !r " ?

Was ich noch nachschauen werde ist...
Was macht Join?
mit Mal wird das Wörterbuch erstellt?
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

Nochmal @__blackjack__
Ich danke dir viel Mals, bis ich das alles gegoogelt hätte, gefunden und verstanden hätte währe eine Ewigkeit vergangenen. Das du dir die Arbeit und Mühe gemacht hast alles so ausführlich für mich zu schreiben. Und auch noch die Geduld hattest, einem Hinterweltler wie immer wieder zu helfen. Danke und auch den anderen danke ich viel mals.
Sirius3
User
Beiträge: 18270
Registriert: Sonntag 21. Oktober 2012, 17:20

@LKR: "{!r}" ist ein »conversion flag« bei format, steht alles in er Dokumentation.

Das Mal in ›"[{}][{}]".format(*path)‹ steht für Argument-Entpacken, nämlich dass die einzelnen Elemente von Path als eigene Argumente an format übergeben werden, das ist hier das selbe wie ›"[{}][{}]".format(path[0], path[1])‹.
LRK
User
Beiträge: 38
Registriert: Sonntag 16. Juni 2019, 21:00

@Sirius3
Danke, werde es mir nochmal durchlesen. Das mit Format habe ich verstanden. Habe das wohl mit dem " !r " übersehen. :D
Antworten