Python Sequenzen                            (C) 2019-2025 T.Birnthaler OSTC GmbH
================

Doku --> docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
     --> docs.python.org/3/library/array.html

SEQUENZEN sind eine "GEORDNETE" Folge (Reihe) von ELEMENTEN, d.h. die Elemente
haben eine REIHENFOLGE (erstes, zweites, ..., letztes). Jedem Element wird ein
INDEX zugeordnet (Ganzzahl beginnend mit 0), über den es angesprochen wird. Der
Index läuft AUFSTEIGEND von 0 bis Sequenz-Länge-1
       bzw. ABSTEIGEND von -1 bis -Sequenz-Länge:

  s1 = "Hallo Welt"                  s2 = ""     # String + leerer String
  t1 = ( True, 99, "hallo", 3.14 )   t2 = ()     # Tupel + leerer Tupel
  l1 = [ 123, 456, 789 ]             l2 = []     # Liste + leere Liste

   01234  # <-- POSITIVER Index (0 ... Länge-1)
  "hallo" # Sequenz (str)
      -1  # <-- NEGATIVER Index (-1 ... -Länge) = ABKÜRZUNG für len(S) - 1
     -2   #                                                     len(S) - 2
    -3    #                                                     len(S) - 3
   -4     #                                                     len(S) - 4
  -5      #                                                     len(S) - 5

* Die Zugriffsgeschwindigkeit auf ein ELEMENT über seinen INDEX ist UN-ABHÄNGIG
  von der Sequenz-LÄNGE und vom INDEX immer gleich schnell (RANDOM ACCESS).

* Die Zugriffsgeschwindigkeit auf ein bestimmtes ELEMENT über seinen WERT
  (z.B. welchen Index hat ein bestimmter Wert oder wie häufig kommt ein Wert
  in der Sequenz vor) ist ABHÄNGIG von der Sequenz-LÄNGE und relativ langsam
  (erfordert das Durchsuchen der gesamten Sequenz).

* In vielen anderen Programmiersprachen wird statt SEQUENZ der Begriff ARRAY
  verwendet (den Python in seiner Dokumentation vermeidet). Python verwendet
  in der Dokumentation auch den Begriff ITEM statt ELEMENT.

Wichtige in Python eingebaute Sequenz-Datentypen mit ihren Eigenschaften:

  +-----------+------------+-----------------------------------+----------+
  | Datentyp  | Änderbar   | Elemente                          | Elem.anz |
  +-----------+------------+-----------------------------------+----------+
  | str       | im-mutable | UTF-8 codierte Zeichen (1-4 Byte) | fix      |
  | tuple     | im-mutable | ALLE Python-Objekte               | fix      |
  | list      |    mutable | ALLE Python-Objekte               | variabel |
  +-----------+------------+-----------------------------------+----------+
  | bytes     | im-mutable | Bytes mit Wert 0-255 bzw. 00-FF   | fix      |
  | bytearray |    mutable | Bytes mit Wert 0-255 bzw. 00-FF   | variabel |
  | array     |    mutable | Zahlen/Zeichen mit fester Größe   | variabel |
  +-----------+------------+-----------------------------------+----------+

HINWEIS: "array" ist ein Modul aus der Standard-Bibliothek, das EIN-dimensionale
         Arrays von Ganzzahlen (signed/unsigned char/short/int/long/long long),
         Fließkommazahlen (float/double) und Unicode Zeichen (wchar_t) erlaubt.
         Einsatzzweck von "array": Erlaubt nur Elemente gleichen Typs und
                                   arbeitet speichersparend + effizient.
         --> Besser gleich die Bibliothek "NumPy" verwenden
             (diese unterstützt auch MEHR-dimensionale Arrays = Matrizen)!

Folgende LESENDEN Operationen werden von (fast) ALLEN Sequenz-Datentypen (str,
tuple, list, bytes, bytearray, array, ...) unterstützt (sowohl mutable als auch
im-mutable). S und T sind Sequenzen des gleichen Datentyps, I, J, K, N sind
Ganzzahlen und E ist ein Element, das die Einschränkung für die Elemente der
Sequenz S erfüllt (meist ein beliebiges Objekt).

ACHTUNG: Die Sequenz S bleibt UNVERÄNDERT!

+--------------------+----------------------------------------------------+
| Operation          | Bedeutung                                          |
+--------------------+----------------------------------------------------+
| for E in S: ...    | ALLE Elem. E von S nach Index geordnet durchlaufen |
+--------------------+----------------------------------------------------+
| E in S             | Element E in S enthalten?                          |
| E not in S         | Element E in S nicht enthalten?                    |
+--------------------+----------------------------------------------------+
| S + T              | Verkettung von S und T                             |
| S * N   N * S      | Sequenz S N-mal vervielfachen (N-mal verketten)    |
+--------------------+----------------------------------------------------+
| S == T             | Sequenzen gleich (ALLE Elemente + Reihenfolge)?    | Typ muss gleich sein!
| S != T             | Sequenzen ungleich (EIN Element oder Reihenfolge)? | Typ muss gleich sein!
+--------------------+----------------------------------------------------+
| S < T              | Sequenz S kleiner T?               (hierarchisch!) | Typ muss gleich sein!
| S <= T             | Sequenz S kleiner gleich T?        (hierarchisch!) | Typ muss gleich sein!
| S > T              | Sequenz S größer T?                (hierarchisch!) | Typ muss gleich sein!
| S >= T             | Sequenz S größer gleich T?         (hierarchisch!) | Typ muss gleich sein!
+--------------------+----------------------------------------------------+
| S[I]         Index | Element mit Index I (0 bis Länge-1)                |
| S[I:J]       Slice | Elemente von Index I bis J-1 (mit Schrittweite 1)  | "Verkappte" for-Schleife
| S[I:J:K]     Slice | Elemente von Index I bis J-1 mit Schrittweite K    | "Verkappte" for-Schleife
+--------------------+----------------------------------------------------+
| len(S)             | Länge von S (Anzahl Elemente)                      |
+--------------------+----------------------------------------------------+
| min(S)             | Kleinstes Element in S (gemäß Vergleich "<")       |
| max(S)             | Größtes   Element in S (gemäß Vergleich "<")       |
| sum(S)             | Summe der Elemente in S (müssen Zahlen sein)       |
+--------------------+----------------------------------------------------+
| all(S)             | False falls mind. 1 Element False, sonst/leer True | Seltsam --> wieder vergessen!
| any(S)             | True  falls mind. 1 Element True, sonst/leer False | Seltsam --> wieder vergessen!
+--------------------+----------------------------------------------------+
| S.index(E[,I[,J]]) | Index des 1. Elements E in S (oder "ValueError")   |
|                    | (von Index I bis J-1; Standard: 0 bis len(S)-1)    |
| S.count(E)         | Anzahl Vorkommen von Element E in S                |
+--------------------+----------------------------------------------------+
| reversed(S)        | Elemente in umgekehrter Reihenfolge (Iterator)     |
| sorted(S,key,      | Sortierte Liste der Elemente (gemäß Vergleich "<") |
|          reverse)  | (key=MAPFUNC, reverse=True --> absteigend)         |
+--------------------+----------------------------------------------------+

HINWEIS: Index I, J sowie Schrittweite K dürfen NEGATIV sein (auch gemischt).

HINWEIS: Indexzugriffe werden ÜBERWACHT, d.h. beim Zugriff über den Rand einer
Sequenz hinaus erfolgt eine Exception "IndexError".

HINWEIS: Die Formulierung "gemäß Vergleich <" heißt, dass ALLE Elemente per
Operator "<" miteinander VERGLEICHBAR sind. D.h. entweder alle Elemente sind
ZAHLEN (bool, int, float, complex, Decimal, Fraction) oder alle Elemente haben
den GLEICHEN Datentyp.

HINWEIS: INDEXZUGRIFFE S[I] und SLICING per S[I:J] und S[I:J:K] KOPIEREN ein
Stück (Slice) der Sequenz S und dürfen aus einer beliebigen Kombination von
positiven + negativen Indices bestehen. Slicing stellt letztlich eine SCHLEIFE
dar. Startindex 0/-1, Endindex len(S)/-len(S)-1 und Schrittweite 1 dürfen
weggelassen werden (bei sinnlosen Kombinationen entsteht eine LEERE Sequenz):

  S = "0123456789"   # Sequenz (Element = positiver Index)
     -10987654321    # Negativer Index

  S[:]          # --> '0123456789' = 1:1-Kopie (OK, pythonic)
  S[::]         # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)
  S[::1]        # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)
  S[0:]         # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)
  S[:len(S)]    # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)
  S[0:len(S)]   # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)
  S[0:len(S):1] # --> '0123456789' = 1:1-Kopie (OK, un-pythonic)

Übliche (leicht verständliche) Slices:

  S[::2]        # --> '02468'      = Jedes 2. Element ab 1. Element
  S[1::2]       # -->  '13579'     = Jedes 2. Element ab 2. Element
  S[5:]         # -->      '56789' = Ohne erste 5 Elemente
  S[:5]         # --> '01234'      = Erste 5 Elemente
  S[3:5]        # -->    '34'      = 4. und 5. Element
  S[1:]         # -->  '123456789' = 1. Element weglassen
  S[:-1]        # --> '012345678'  = Letztes Element weglassen
  S[1:-1]       # -->  '12345678'  = 1. + letztes Element weglassen
  S[::-1]       # --> '9876543210' = Alle Elemente in umgek. Reihenfolge

Bei NEGATIVER Index-Grenze ist die entsprechende positive Index-Grenze gemeint.
Dies ist ungewöhnlich (und daher schwer verständlich).

  S[-5:]        # -->      '56789' = Letzte 5 Elemente
  S[:-5]        # --> '01234'      = Erste 5 Elemente
  S[-4:-5]      # --> ''           = Leer
  S[-5:-4]      # -->      '5'     = Element mit Index 5 oder -5
  S[-1:-10]     # --> ''           = Leer
  S[-10:-1]     # --> '012345678'  = Ohne letztes Element

Bei NEGATIVER Schrittweite sind die Slice-Grenzen UMZUDREHEN (größere zuerst,
dann kleinere. Dies ist ungewöhnlich (und daher schwer verständlich).

  S[::-1]       # --> '9876543210' = Alle Elemente in umgek. Reihenfolge
  S[4::-1]      # -->      '43210' = Erste 5 Elemente in umgek. Reihenfolge
  S[:5:-1]      # --> '9876'       = Letzte 4 Elemente in umgek. Reihenfolge
  S[0:5:-1]     # --> ''           = Leer
  S[5:0:-1]     # -->    '54321'   = Ohne erste 5 + letztes Element
  S[:0:-1]      # --> '987654321'  = Ohne 1. Element in umgek. Reihenfolge
  S[-1:0:-1]    # --> '987654321'  = Ohne 1. Element in umgek. Reihenfolge
  S[-1::-1]     # --> '9876543210' = Alle Elemente in umgek. Reihenfolge
  S[-2::-1]     # -->  '876543210' = Ohne letztes Element in umgek. Reihenfolge
  S[-2:0:-1]    # -->  '87654321'  = Ohne 1. + letztes Element in umgek. Reihenfolge
  S[:-6:-1]     # --> '98765'      = Letzte 5 Elemente in umgek. Reihenf.
  S[-1:-6:-1]   # --> '98765'      = Letzte 5 Elemente in umgek. Reihenf.

HINWEIS: Slices berücksichtigen Index-Grenzen automatisch (kein "IndexError"
möglich) und Slices mit LEEREM Index-Bereich liefern ein LEERES Ergebnis:

  S[100:100]    # --> ''           KEIN Element
  S[100:]       # --> ''           KEIN Element
  S[:100]       # --> '0123456789' ALLE Elemente
  S[-100:-100]  # --> ''           KEIN Element
  S[-100:]      # --> '0123456789' ALLE Elemente
  S[:-100]      # --> ''           KEIN Element
  S[100:-100]   # --> ''           KEIN Element
  S[-100:100]   # --> '0123456789' ALLE Elemente

HINWEIS: Funktion slice(STOP) bzw. slice(START, STOP [,STEP]) erzeugt ein
SLICE-OBJEKT, das den entsprechenden Slice-Zugriff auf die Elemente einer
Sequenz durchführt. Es gilt:

  S = "0123456789"           # Sequenz (Element = positiver Index)
  I = 3; J = 7+1; K = 2      # START, STOP, STEP
                             #
  S[slice(I, I+1)]           # -->    '3'       = S[I]
  S[slice(I, J)]             # -->    '34567'   = S[I:J]
  S[slice(I, J, None)]       # -->    '34567'   = S[I:J:]
  S[slice(I, J, K)]          # -->    '357'     = S[I:J:K]
  S[slice(I, None)]          # -->    '3456789' = S[I:]
  S[slice(I, None, None)]    # -->    '3456789' = S[I::]
  S[slice(I, None, K)]       # -->    '3579'    = S[I::K]
  S[slice(J)]                # --> '01234567'   = S[:J]
  S[slice(None, J)]          # --> '01234567'   = S[:J]
  S[slice(None, J, None)]    # --> '01234567'   = S[:J:]
  S[slice(None, J, K)]       # --> '0246'       = S[:J:K]
  S[slice(None)]             # --> '0123456789' = S[:]
  S[slice(None, None)]       # --> '0123456789' = S[:]
  S[slice(None, None, None)] # --> '0123456789' = S[::]
  S[slice(None, None, K)]    # --> '02468'      = S[::K]


Im-mutable Sequenzen
--------------------
Folgende LESENDE Operation wird von im-mutable Sequenzen (str, tuple, bytes)
ZUSÄTZLICH zu obigen unterstützt (S ist eine im-mutable Sequenz):

ACHTUNG: Die Sequenz S bleibt UNVERÄNDERT!

+--------------------+-------------------------------------------------------+
| Operation          | Bedeutung                                             |
+--------------------+-------------------------------------------------------+
| hash(S)            | Hashwert erstellen (eindeutig)                        |
+--------------------+-------------------------------------------------------+


Mutable Sequenzen
-----------------
Folgende SCHREIBENDEN Operationen werden von mutable Sequenzen (list, bytearray)
ZUSÄTZLICH zu obigen Sequenz-Operationen unterstützt (S ist eine mutable
Sequenz, T ist eine Sequenz oder ein Iterator des gleichen Datentyps,
I, J, K, N sind Ganzzahlen und E ist ein Element, das die Einschränkung für die
Elemente der Sequenz S erfüllt (meist ein beliebiges Objekt).

ACHTUNG: Sequenz S wird VERÄNDERT (bleibt aber das gleiche Objekt)!

ACHTUNG: Operationen += und *= sind auch auf String+Tupel anwendbar (im-mutable),
         erzeugen dabei aber "on-the-fly" einen NEUEN String / Tupel!

+---------------------+--------------------------------------------------------+
| Operation           | Bedeutung                                              |
+---------------------+--------------------------------------------------------+
| S[I] = E            | Element mit Index I durch E ersetzen                   |
| S[I:J] = T          | Elemente mit Index I..J-1 durch Iterable T ersetzen    | Slice
| S[I:J:K] = T        | Elemente mit Index I..J-1 in Schrittweite K ersetzen   | Slice
+---------------------+--------------------------------------------------------+
| del S[I]            | Element mit Index I aus Liste entfernen                |
| del S[I:J]          | Elemente mit Index I..J-1 aus Liste entfernen          | Slice
| del S[I:J:K]        | Elemente mit Index I..J-1 in Schrittweite K entfernen  | Slice
+---------------------+--------------------------------------------------------+
| S.append(E) S += [E]| E am Sequenz-Ende anhängen      S[len(S):len(S)] = [E] |
| S.extend(T) S += T  | Elemente von T anhängen           S[len(S):len(S)] = T |
| S.insert(I, E)      | Element E bei Index I einfügen          S[I:I+1] = [E] |
+---------------------+--------------------------------------------------------+
| E = S.pop([I])      | Element bei Index I (STD: -1) entfernen und zurück     |
| S.remove(E)         | Erstes Element mit Wert E entfernen (Suche!)           |
+---------------------+--------------------------------------------------------+
| S *= N              | Sequenz S N-mal vervielfachen + verketten              |
| S.reverse()         | Reihenfolge der Elemente umdrehen                      |
| S.clear()           | Sequenz S leeren (alle Elemente entfernen)    del S[:] |
| S2 = S.copy()       | Shallow/Flache Kopie erzeugen                     S[:] |  TODO
+---------------------+--------------------------------------------------------+

HINWEIS: Index I, J sowie Schrittweite K dürfen NEGATIV sein (auch gemischt).


list-Sequenz
------------
Folgende ändernde Operation wird von Listen zusätzlich zu den allgemeinen
Sequenz-Operationen und den Operationen für mutable Sequenzen unterstützt:

ACHTUNG: Die Liste L wird VERÄNDERT (bleibt aber das gleiche Objekt)!

+--------------------+-------------------------------------------------------+
| Operation          | Bedeutung                                             |
+--------------------+-------------------------------------------------------+
| L.sort(key,reverse)| Elemente aufsteigend sortieren (gemäß Vergleich "<")  |
|                    | (key=MAPFUNC, reverse=True --> absteigend)            |
+--------------------+-------------------------------------------------------+

HINWEIS: Die Formulierung "gemäß Vergleich <" bedingt, dass ALLE Elemente per
Operator "<" miteinander VERGLEICHBAR sind. D.h. entweder alle Elemente sind
ZAHLEN (bool, int, float, complex, Decimal, Fraction) oder alle Elemente haben
den GLEICHEN Datentyp.