# Listen - veränderliche Sequenzen

import sys
import re
from pprint import pprint

# Listen aus Modul sys - Modulsuchpfad und Argumenteliste
print(sys.path)
print(sys.argv)
print()
print(len(sys.argv))
pprint(sys.argv)
pprint(sys.path)
print()

# Listen ganzer Zahlen
print(list(range(10)))
print(list(range(-5, 5)))
print(list(range(-5, 5, 2)))
print(list(range(20, 2, -3)))
print()

# leere Liste
l = []         # oder: l = list()

# Listen anfügen
l.extend(list(range(20)))
l += dir()[-2:]         # l = l + dir()[-2:]
print(l)
print()

# Elemente anfügen, einfügen, löschen
l.append(10)         ; print(l)
l.insert(0, 'start') ; print(l)
l.pop()              ; print(l)
l.pop(0)             ; print(l)
del l[2]             ; print(l)
del l[:3]            ; print(l)
del l[8:]            ; print(l)
del l[2:5]           ; print(l)
print()

# ermittle, ob und ggf. wo bestimmte Elemente in der Liste stehen
for x in 'start', 2, 4:
    if x in l:
        print(x, l.index(x))

# entferne Elemente aus der Liste
for x in 'start', 2, 4:
    if x in l:
        l.remove(x)

print(l)
print()

# Konvertierung Tupel <==> Liste
print(tuple(l))
print(list(tuple(l)))
print()

# Unpacking einer Liste
date = '2012-12-11'
j, m, t = date.split('-')

# formatierte Ausgabe
print('%s . %s . %s' % (t, m, j))                          # 11 . 12 . 2012
print('%s . %s . %s' % tuple(reversed(date.split('-'))))   # 11 . 12 . 2012
print('{} . {} . {}'.format(*(reversed(date.split('-'))))) # 11 . 12 . 2012

# Unpacking einer durch Slicing gebildeten Liste
a, b = list(range(10))[-2:]
print(a, b)
print()

# Sortieren

l = ['ccx', 'aaay', 'dz', 'bba', '']
k = l[:] # k bekommt eine (flache) Kopie von l;
         # copy.deepcopy() bietet eine tiefe Kopie
k.sort() # k wird sortiert und somit verändert, l ist davon nicht betroffen
print(l)
print(k)
print()

# sortierte Kopien von l erstellen

print(sorted(l))                       # steigend sortieren
print(list(reversed(sorted(l))))       # fallend sortieren
print(sorted(l, reverse=1))            # sorted kann selber fallend sortieren
print(sorted(l, key=len))              # nach der Länge der Listenelemente sortieren
print(sorted(l, key=lambda x: x[-1:])) # nach dem letzten Element (Zeichen) jedes Listenelementes sortieren;
                                       # Slicing funktioniert auch bei leerem String, x[-1] dagegen nicht
print()

# statt der anonymen lambda-Funktion kann man auch eine normale Funktion
# nutzen, die das letzte Element (Zeichen) eines Strings (bzw. einer Sequenz)
# ermittelt
def lastchar(s):
    return s[-1:]

# damit kann man dann sortieren
print(sorted(l, key=lastchar))
print()

# Listen-Manipulation:

# Slicing
l = list(range(5, 100, 5)) # [5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
print(len(l))              # 19
print(l[:5])               # [5, 10, 15, 20, 25]
print(l[:10:2])            # [5, 15, 25, 35, 45]
print(l[15:])              # [80, 85, 90, 95]
print(l[10::-1])           # [55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5]
print(l[::-1])             # [95, 90, 85, 80, 75, 70, 65, 60, 55, 50, 45, 40, 35, 30, 25, 20, 15, 10, 5]
print(l[10::5])            # [55, 80]
print(l[8:14])             # [45, 50, 55, 60, 65, 70]
print(l[8:14:2])           # [45, 55, 65]
print(l[::3])              # [5, 20, 35, 50, 65, 80, 95]
print(l[::-3])             # [95, 80, 65, 50, 35, 20, 5]
print()

print('Elemente einzeln einfügen')
l[1:3] = list(range(5))    # [5, 0, 1, 2, 3, 4, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
print(l)

print('Liste einfügen')
l[2:4] = [list(range(6))]  # [5, 0, [0, 1, 2, 3, 4, 5], 3, 4, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
print(l)
print(l[2][-1]) # das letzte Element des 3. Elements von l ausgeben
print()

# Listen in Listen
l = [1, 3, ['a', None, False], (9, 8)]
for i in l:
    print(i, type(i))

l[2][2] = True
print(l)

l[2] = [3, 4] # Liste wird als ein Element eingefügt
print(l)
print()

l = [list(range(2)), list(range(3)), list(range(4))]
# verschachtelte Listen durch verschachtelte for-Schleifen ausgeben
for i in range(len(l)):
    for j in range(len(l[i])):
        print('l[%d,%d] = %s' % (i, j, l[i][j]))

print()

# alternativ:
for i, sublist in enumerate(l):
    for j, item in enumerate(sublist):
        print('l[%d,%d] = %s' % (i, j, item))

print()

# List Comprehension: Listenobjekte funktional/deklarativ beschreiben;
# erinnert an die mathematische Notation von Mengen

print([2 * x for x in range(-5, 5) if x > 0])

# "x and y" entspricht "x != 0 and y != 0"
print([(x, y) for x in range(4) for y in range(3) if x and y])

# nur die mit A beginnenden der auf l endenden Strings der Ausgangsliste in die
# neue Liste aufnehmen
print([s for s in ['Anton', 'Berta', 'Caesar', 'Alf', 'Emil'] if re.search('^A|l$', s)])
# ['Anton', 'Alf', 'Emil']

# durch extended slicing jedes 3. Element entnehmen
print([(x, y) for x in range(4) for y in range(3) if x and y][::3])

def nkz(line):
    'Nutzerkennzeichen aus passwd-Zeile herausspalten'
    return line.split(':')[0]

print([nkz(line) for line in open('/etc/passwd')][:5])
print([line.split(':')[0] for line in open('/etc/passwd')][:5])

print()

# Generatorausdrücke
# () statt []
#
# dienen der Beschreibung von Generatoren, die im Rahmen einer Iteration die
# benötigten Elemente der Reihe nach erzeugen; eine List Comprehension erzeugt
# dagegen stets eine komplette Liste aller zu generierenden Elemente

go = (2 * x for x in range(-5, 5) if x > 0) # <generator object <genexpr> at 0x7f69a6229048>
for x in go: print(x)
print()

# analog dem obigen Beispiel zu /etc/passwd; Generator ist bei langen Dateien effizienter
go = (line.split(':')[0] for line in open('/etc/passwd'))
for uid in range(5): print(next(go))
print()

# oder:
for i, uid in zip(range(5), (line.split(':')[0] for line in open('/etc/passwd'))):
    print(uid)
print()

# die Klammerung des Generatorausdrucks kann entfallen, wenn er das einzige
# Funktionsargument ist
print(sum(2 * x for x in range(-5, 5) if x > 0))
print(list(2 * x for x in [y for y in range(-5, 5) if y > 0]))

# Generatorausdruck innerhalb eines Generatorausdrucks
print(sum(2 * x for x in (y for y in range(-5, 5) if y > 0)))

# List Comprehension innerhalb eines Generatorausdrucks
print(sum(2 * x for x in [y for y in range(-5, 5) if y > 0]))

# maximale und minimale UID in der /etc/passwd ermitteln
print(max(int(line.split(':')[2]) for line in open('/etc/passwd')))
print(min(int(line.split(':')[2]) for line in open('/etc/passwd')))

# Skalarprodukt zweier Vektoren
xvec = [10, 20, 30]
yvec = [7, 5, 3]
print(sum(x * y for x, y in zip(xvec, yvec)))
    # list(zip(xvec, yvec)) liefert [(10, 7), (20, 5), (30, 3)]
print()

# eigene Generatorfunktion mit yield

def num_gen(n):
    for x in range(n):
        yield x * x

# for iteriert über dem Generator, der durch den Aufruf num_gen(10) erzeugt wurde
for x in num_gen(10): print(x, end=' ')
print()

# analog mit Generatorausdruck
for x in (x * x for x in range(10)): print(x, end=' ')
print()

# eine Liste mit dem Generator initialisieren
print(list(num_gen(5)))

# zu Fuß
g = num_gen(10) # führt num_gen() bis zum ersten yield aus und liefert einen
                # Generator, der das Iterator-Interface bietet

# Endlosschleife
while 1:        # while True: ...
    try:
        print(next(g), end=' ') # das nächste Element vom Generator anfordern
    except StopIteration as e:
        # die StopIteration behandeln, die ausgeworfen wird, wenn der Generator
        # erschöpft ist
        print(type(e)) # <class 'StopIteration'>
        # Abbruch der Schleife
        break
