Python Dictionaries (dict + frozendict)     (C) 2020-2026 T.Birnthaler OSTC GmbH
=======================================

Doku --> docs.python.org/3/library/stdtypes.html#dict
     --> docs.python.org/3/library/stdtypes.html#frozendict

Dictionaries (dict, frozendict) (auch Map, Hash, Assoziatives Array, Zuordnung,
Verzeichnis) speichern PAARE/ITEMS aus KEY + VALUE und damit eine "Beziehung"
zwischen zwei Datenwerten (nur Beziehung in Richtung KEY --> VALUE).

Ein Dictionary entspricht einer 2-spaltigen Datenbank-Tabelle mit festen
Spaltennamen KEY + VALUE und enthält als Datensätze KEY-VALUE-PAARE. Die Spalte
KEY ist UNIQUE und INDIZIERT, d.h. der Zugriff über diese Spalte ist unabhängig
von der Anzahl der Datensätze + sehr schnell. Die Spalte VALUE ist NON-UNIQUE
und nicht INDIZIERT, d.h. der Zugriff über diese Spalte ist abhängig von der
Anzahl der Datensätze + relativ langsam.

Das Dictionary liegt im Speicher und "stirbt" am Ende des Programmes, daher ist
es NICHT PERSISTENT, es gibt nur einen Zugriffspfad (keine MULTI-USER
Fähigkeit) und Zugriffe darauf sind WESENTLICH SCHNELLER als auf echte
Datenbanken (keine SQL-Statements erzeugen, versenden, parsen und beantworten,
keine NW-Verbindung, kein Protokoll-Overhead, keine Plattenzugriffe, ...).

Die Datensätze sind UN-GEORDNET (seit Python 3.7 bleibt die Einfüge-Reihenfolge
erhalten) und UN-NUMERIERT. Die DATENTYPEN der beiden Spalten sind nicht
festgelegt und können daher beliebig gemischt sein. Im KEY können aber nur
IM-MUTABLE Datentypen (besser HASHABLE) Datentypen gespeichert werden, im VALUE
sind alle Python-Datentypen erlaubt. Die Spalte VALUE kann per
CONTAINER-Datentyp tuple, list, set, dict, ... auch MEHRSPALTIG und sogar
HIERARCHISCH sein.

  d1 = {}    oder   d1 = dict()                     # Leeres Dictionary
  d2 = {"abc": 123, True: 4.0, (1,2,3): None}       # 3 Paare/Items
  d3 = {"a": 1, "b": 2, "c": 1, "d": 3}             # 4 Paare/Items
  d4 = dict(a=1, b=2, c=1, d=3)                     # Alternative Schreibweise
  d5 = dict([("a",1), ("b",2), ("c",1), ("d",3)])   # Alternative Schreibweise

* KEYS müssen IM-MUTABLE Objekte bzw. eigentlich HASHABLE Objekte sein
  (NoneType, bool, int, float, complex, str, tuple, frozenset, bytes).

* KEYS sind UNIQUE (d.h. der gleiche Key kann nur 1x vorkommen).

* Als VALUE sind beliebige PYTHON-OBJEKTE erlaubt.

* VALUES dürfen NON-UNIQUE sein (d.h. der gleiche Value darf mehrfach vorkommen).

* Dictionaries sind bezüglich KEY/VALUE UN-GEORDNET (seit Python 3.7 bleibt die
  Einfüge-Reihenfolge erhalten). Will man Zugriff in spezieller Reihenfolge,
  muss man beim Zugriff MANUELL sortieren.

* Die ZUGRIFFSGESCHWINDIGKEIT auf einen bestimmten KEY (und damit seinen VALUE)
  ist UN-ABHÄNGIG von der Dictionary-Größe immer gleich SCHNELL (RANDOM ACCESS).

* Die ZUGRIFFSGESCHWINDIGKEIT auf einen bestimmten VALUE ist ABHÄNGIG von der
  Dictionary-Größe und immer LANGSAM (da sie das Durchsuchen des gesamten
  Dictionarys erfordert).

Folgende Operation werden von dict und frozendict unterstützt (K ist ein Key =
im-mutable Objekt, V und DFLT sind Values = beliebige Objekte, D, D2 und E sind
Dictionaries, ITBL ist ein Iterable, L ist eine Liste):

ACHTUNG: Nur LESENDE Operationen, das Dictionary D bleibt UNVERÄNDERT!

+------------------------+-----------------------------------------------------+
| for K in D: ...        | Alle Keys K von D durchlaufen (ungeordnet)          |
| for K in D.keys(): ... | Alle Keys K von D durchlaufen (ungeordnet)          |
| for V in D.values():   | Alle Values V von D durchlaufen (ungeordnet)        |
| for (K,V) in D.items() | Alle Keys+Values K+V von D durchlaufen (ungeordnet) |
+------------------------+-----------------------------------------------------+
| D == D2                | Dictionaries gleich (alle Key-Value Paare)?         |
| D != D2                | Dictionaries un-gleich (ein Key oder Value)?        |
+------------------------+-----------------------------------------------------+
| K in D                 | Key K in D enthalten?                               |
| K not in D             | Key K in D nicht enthalten?                         |
+------------------------+-----------------------------------------------------+
| len(D)                 | Anzahl Keys/Values/Paare/Items                      |
+------------------------+-----------------------------------------------------+
| V = D[K]               | Value von Key K holen (KeyError falls Key fehlt)    |
| V = D.get(K, DFLT)     | Value von Key K holen (oder DFLT, kein KeyError)    |
+------------------------+-----------------------------------------------------+
| L = D.keys()           | Liste aller Keys (ungeordnet)                       |
| L = D.values()         | Liste aller Values (ungeordnet)                     |
| L = D.items()          | Liste aller (Key,Value)-Paare/Items (ungeordnet)    |
+------------------------+-----------------------------------------------------+
| L = list(D)            | Abkürzung für D.keys()                              |
| L = iter(D)            | Abkürzung für iter(D.keys())                        |
| L = reversed(D)        | Abkürzung für reversed(D.keys())                    | PY3.8
+------------------------+-----------------------------------------------------+
| D2 = D.copy()          | Shallow/Flache Kopie                                |
+------------------------+-----------------------------------------------------+
| E = D | D2             | D2 zu D hinzufüg, D2-Val überschreibt D bei gl. Key | PY3.9
+------------------------+-----------------------------------------------------+

ACHTUNG: SCHREIBENDE Operationen sind nur für dict möglich (nicht frozendict)
         das Dictionary D wird VERÄNDERT (bleibt aber das gleiche Objekt)!

ACHTUNG: Die Operation |= ist auch auf frozendict anwendbar,
         erzeugen aber einen NEUEN frozendict!

+-----------------------+------------------------------------------------------+
| D[K] = V              | Value V für vorhandenen Key K (er)setzen             |
|                       | Key K mit Value V hinzufügen (falls NICHT vorhand.)  |
| D.setdefault(K, DFLT) | Value zu Key K auf DFLT setzen (falls NICHT vorhand.)|
|                       | + Value von Key zurückgeben                          |
+-----------------------+------------------------------------------------------+
| del D[K]              | Key K + Value löschen (oder KeyError falls Key fehlt)|
| V = D.pop(K, DFLT)    | Value V von Key K holen (KeyError/DFLT) + Key löschen|
| (K,V) = D.popitem()   | Paar/Item (K,V) holen + entf. (KeyError falls D leer)|
+-----------------------+------------------------------------------------------+
| D = dict.fromkeys(I)  | Dict D mit Keys aus Iterable I + Wert None erzeugen  |
| D = dict.fromkeys(I,V)| Dict D mit Keys aus Iterable I + Wert V erzeugen     |
| D.update(D2|ITER)     | D2/ITER zu D hinzufügen (überschreibt D bei gl. Key) |
| D.clear()             | Dict D leeren (alle Keys + Values entfernen)         |
+-----------------------+------------------------------------------------------+
| D |= D2               | D2 zu D hinzufügen, D2-Val überschreibt D bei gl. Key| PY3.9
+-----------------------+------------------------------------------------------+
ITER = Iterable mit PAAREN als Elemente (z.B. Liste von 2-er Tupeln)

HINWEIS: Zugriff auf ein Dictionary mit einem NICHT vorhandenen Key
         löst die Exception "KeyError" aus.

HINWEIS: Bei Funktion "fromkeys" muss "dict." als Präfix verwendet werden, da
noch kein Dictionary-Objekt vorhanden ist, auf das "fromkeys" angewendet werden
kann (sondern durch "fromkeys" erst erzeugt wird).

HINWEIS: Seit Python 3.7 entspricht die Key-Reihenfolge im Dictionary der
EINFÜGE-REIHENFOLGE und bleibt bei Operationen auf dem Dictionary erhalten.
Vorher war die Reihenfolge der Keys ZUFÄLLIG und konnte sich jederzeit ändern.

HINWEIS: Die Keys "True/False" in Dictionaries werden mit den Keys 1/0 (int),
1.0/0.0 (float) und 1+0j/0+0j (complex) IDENTIFIZIERT, da sie den gleichen
HASH-Wert haben (d.h. jeder der folgenden Keys kann nur 1x in einem Dictionary
vorkommen):

  True  <--> 1 <--> 1.0 <--> 1+0j <--> 1+0.0j <--> 1.0+0j <--> 1.0+0.0j
  False <--> 0 <--> 0.0 <--> 0+0j <--> 0+0.0j <--> 0.0+0j <--> 0.0+0.0j

Vor PY3.7 war das ein ZUFÄLLIGER Key + Wert, seit PY3.7 ist es der ERSTE im
Dictionary vorkommende Key mit dem LETZTEN diesem Key zugeordneten Wert:

  D = {True: "bool", 1: "int", 1.0: "float", 1+0j: "complex"}  # --> {True: 'complex'}
  D = {1: "int", True: "bool", 1.0: "float", 1+0j: "complex"}  # --> {1: 'complex'}
  D = {0+0j: "complex", 0: "int", False: "bool", 0.0: "float"} # --> {0+0j: 'float'}
  D = {0.0: "float", 0+0j: "complex", 0: "int", False: "bool"} # --> {0.0: 'bool'}