Wörterbücher auf unterschiedlichen Inhalt vergleichen, das geht doch eleganter?

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
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo zusammen,

ich rätsle schon einige Zeit an einer eleganten Lösung.
Ich habe zwei Wörterbücher, der Schlüssel ist ein Gegenstand und der Wert ist die Menge des Gegenstands. Jetzt hätte ich gern nur die Information in wie weit sich die Wörterbücher unterscheiden. Also wenn ein Gegenstand nur in einem Wörterbuch drin ist, dann soll der mit zugehöriger Menge ausgegeben werden und wenn die gleichen mit unterschiedlicher Menge enthalten sind, dann soll der Gegenstand mit der Differenzmenge ausgegeben werden.

Mein Beispielcode:

Code: Alles auswählen

MASTER = {'10': 6, '30': 2, '40': 4}
TO_COMPARE = {'10': 5, '30': 2}


def main():
    parts_different_quantity = set(MASTER.items() ^ TO_COMPARE.items())
    print(parts_different_quantity)


if __name__ == '__main__':
    main()
Die Ausgabe ist:

Code: Alles auswählen

{('40', 4), ('10', 5), ('10', 6)}
Das Ergebnis habe ich an sich auch erwartet. Nur wie geht es weiter. Das ist so schön elegant, jetzt verschachtelte 'for'-Schleifen zu schreiben und ein neues Wörterbuch zu erstellen mit der "manuell" berechneten Differenz, würde die Schönheit ja schon irgendwie versauen. :)

Geht das irgendwie eleganter?

Vielen Dank und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Das geht, was es wirklich das ist, was du meinst:

Code: Alles auswählen

d1 = dict(zip("abcde", range(10, 15)))
d2 = dict(zip("cdefg", range(20, 25)))


# sammel alle key-value Paare, die beide dicts nicht gemeinsam haben:
combined = d1 | d2
diff = {key: combined[key] for key in d1.keys() ^ d2.keys()}
# plus der gemeinsamen keys mit der Differenz der Werte:
diff.update({key: abs(d1[key] - d2[key]) for key in d1.keys() & d2.keys()})
print(diff)

 {'a': 10, 'g': 24, 'f': 23, 'b': 11, 'c': 8, 'e': 8, 'd': 8}

narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Ist das nicht eigentlich `collections.Counter`?
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Wenn es um das Befüllen der Dictionaries geht, kann das eine Option sein. Warum aber für die Auswertung die Differenz der Werte der Schnittmenge beider Dictionaries in Verbindung mit den weiteren Inhalten sinnvoll ist, erschließt sich mir nicht. Aber vielleicht gibt es dafür einen guten Grund.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Super, vielen Dank!

Das sieht schon schöner aus. Die Wörterbücher sind eigentlich eingelesene Listen. In den Listen steht schon die Anzahl der Gegenstände und weil das zusammengehört speichere ich die beim einlesen gleich als Wörterbuch. Alle Listen sollen das gleiche beinhalten wie 'MASTER' und wenn sie das nicht tun, dann will ich wissen, was fehlt und wie oft es fehlt. Deswegen die Auswertung der Differenz. Sollte das geschickter gehen, dann immer gern. Ich könnte mir vorstellen das Pandas da einiges bietet, aber ich wollte auch mit den Bitwisen Operatoren vertrauter werden, weil ich denke, dass das fundamentale Grundlagen sind(?) und das wäre mir persönlich erst mal wichtiger.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Code: Alles auswählen

In [6]: a = Counter({'10': 6, '30': 2, '40': 4})

In [7]: b = Counter({'10': 5, '30': 2})

In [8]: a - b
Out[8]: Counter({'40': 4, '10': 1})

In [9]: difference = a - b

In [10]: difference["30"]
Out[10]: 0
?
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Das darf ja nicht wahr sein. Die Differenz der Counter, ist alles was ich brauche!
Danke, da besteht der Code ja eigentlich nur noch aus Daten einlesen und Ergebnis speichern :D

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

narpfel hat geschrieben: Freitag 17. November 2023, 21:15 ?
Interessant, dass auf `collections.Counter` die Subtraktion implementiert ist. Das wusste ich noch nicht. Warum hast du das bei deinem ersten Hinweis nicht direkt gezeigt?
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

@kbr: Hab ich doch?
https://docs.python.org/3/library/collections.html#counter-objects hat geschrieben:Addition and subtraction combine counters by adding or subtracting the counts of corresponding elements.
Sogar noch mit Beispiel in der Doku.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Das meinte ich nicht. Du hättest beispielsweise direkt ein konkretes Beispiel zeigen können, statt andere raten zu lassen, was genau in der Dokumentation du gemeint hast. In etwa so:

Code: Alles auswählen

import collections

d1 = collections.Counter(dict(zip("abcde", range(10, 15))))
d2 = collections.Counter(dict(zip("cdefg", range(20, 25))))

for diff in d1 - d2, d2 - d1:
    for key in "abcdefg":
        print(key, diff[key])

Dann hätte sich auch schnell erkennen lassen, dass weder "d1 - d2" noch "d2 - d1" dem von @Dennis89 gewünschten Ergebnis entsprechen – wie ich auch erst an obigen Beispiel festgestellt habe.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich hatte eigentlich erst vor nächste Woche daran weiter zu arbeiten, aber jetzt habe ich mir die 'Counter'-Lösung auch noch genauer angeschaut. Ja das stimmt, die Gegenstände, die in der zweiten Liste gar nicht vorkommen, werden nicht mit ausgegeben. Die brauche ich natürlich auch. Die Version mit den Bitweisen Operatoren von @kbr berücksichtigen das.


Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
narpfel
User
Beiträge: 645
Registriert: Freitag 20. Oktober 2017, 16:10

Code: Alles auswählen

In [4]: (d1 - d2) | (d2 - d1)
Out[4]: Counter({'g': 24, 'f': 23, 'b': 11, 'a': 10, 'c': 8, 'd': 8, 'e': 8})
? Oder sollen die Werte aus `d2 - d1` negativ sein?
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Ahja, da hätte man eigentlich drauf kommen können. Dankeschön.

Das mit den negativen Werte wäre vielleicht sogar sehr gut, dann kann ich daran unterscheiden aus welcher Liste die Differenz kommt.
Ich dachte dass das irgendwie mit '~' gehen könnte, aber das darf ich so nicht auf ein Counter-Objekt anwendne.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Ok, so geht es also auch:

Code: Alles auswählen

d1 = collections.Counter(dict(zip("abcde", range(10, 15))))
d2 = collections.Counter(dict(zip("cdefg", range(20, 25))))
diff = dict(d1 - d2 | d2 - d1)
Interessant ist, das man dem Coode, wie so oft, nicht direkt ansieht, wie schnell er ausgeführt wird:

Code: Alles auswählen

%timeit diff = dict(d1 - d2 | d2 - d1)

3.69 µs ± 13.6 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
Nutzt man ein dict statt dem Counter wird der Code deutlich schneller:

Code: Alles auswählen

d1 = dict(zip("abcde", range(1, 6)))
d2 = dict(zip("bcefg", range(10, 16)))

%%timeit
combined = d1 | d2
diff = {key: combined[key] for key in d1.keys() ^ d2.keys()}
diff.update({key: abs(d1[key] - d2[key]) for key in d1.keys() & d2.keys()})

1.16 µs ± 12.3 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Für die meisten Anwendungen dürfte dies aber unter Mikrooptimierung fallen und daher unwichtig sein.

Negative Differenzwerte ergeben sich, so wie ich das gegenwärtig sehe, nur mit der zweiten (unteren) Variante (ohne abs() dann).
Sirius3
User
Beiträge: 17757
Registriert: Sonntag 21. Oktober 2012, 17:20

Am einfachsten wäre, das in einem einzigen Dictcomprehension-Ausdruck zu schreiben:

Code: Alles auswählen

{key: d1.get(key, 0) - d2.get(key, 0) for key in d1.keys() | d2.keys()}
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Wenn es um Einfachheit geht, so geht es sogar noch etwas einfacher:

Code: Alles auswählen

{key: d1.get(key, 0) - d2.get(key, 0) for key in d1 | d2}
Leider ist diese Variante zu einfach: bei keys die sich in d2 aber nicht in d1 befinden, ergeben sich negative Werte, was nicht gewünscht ist. Das müsste dann später noch korrigiert werden.

Edit: aber so geht es:

Code: Alles auswählen

{key: d1.get(key, 0) - d2.get(key, 0) if key in d1 else d2.get(key, 0)  for key in d1 | d2}
Edit 2: mit nur einem "get()" geht es auch, die beiden anderen sind nicht erforderlich:

Code: Alles auswählen

{key: d1[key] - d2.get(key, 0) if key in d1 else d2[key] for key in d1 | d2}
Zuletzt geändert von kbr am Samstag 18. November 2023, 17:03, insgesamt 1-mal geändert.
Benutzeravatar
Dennis89
User
Beiträge: 1156
Registriert: Freitag 11. Dezember 2020, 15:13

Ich weis nicht ob sonst noch jemand Code nach dem Aussehen beurteilt, aber die Dictcomprehension sieht auch elegant aus.
Vielen Dank für die weiteren Beispiele.

Die Werte der Keys, die in d2 aber nicht in d1 sind, dürfen gerne negativ sein. Dann kann ich daran erkennen, wo die Werte herkommen. Ich muss aber erst noch nachfragen, wie die Werte aufbereitet werden sollen.
Aber dank euch habe ich ja für alle Eventualitäten Lösungen, vielen Dank. (Damit will ich das aber nicht abbrechen, falls es noch mehr sinnvolle geben sollte)


Grüße
Dennis

P.S. das mit der Zeit ist interessant. Wenn ich das Abarbeiten meiner 'main'-Funktion messen will, nehme ich dann 'time.process_time' und berechne die Differenz zwischen Start und Ende oder gibts da was besseres? timeit sei ja nur für kurze Codestücke gedacht.
"When I got the music, I got a place to go" [Rancid, 1993]
Antworten