Python: Lambdas, Comprehensions


Einführung

Python ist quasi der de facto Standard, um Programme im Bereich KI und Data Science zu schreiben. In meinem Blogartikel "Künstliche Intelligenz - Python erlernen" habe ich für alle die Kenntnisse in einer anderen objektorientierten Sprache haben, versucht anhand objektorientierter Konzepte einen Python Schnellkurs zu geben. Python hat aber auch viele spezifische Eigenschaften, wie es mit Daten und Datenstrukturen umgeht. Daher fand ich es ein Muss einen Artikel zu diesen Thema zu verfassen. Das Resultat ist im Artikel "Künstliche Intelligenz:Python - Listen, Tupeln, Reguläre Ausdrücke und mehr" zu finden. Mit diesen zwei Artikeln habe ich quasi die objektorientierten Konzepte der Sprache abgehandelt (natürlich nicht vertieft). Wer sich dann intensiv mit Python auseinandersetzt muss sich grundlegend noch Kenntnisse aneignen über wichtige Details, die beispielsweise bei der Klassen-Mehrfachvererbung in Python auftauchen. 

Dieser Artikel hier, führt zwei moderne Konzepte ein, die in einigen anderen objektorientierten Sprachen teilweise auch realisiert sind, aber eigentlich mit den Paradigmen von Objektorientierung nichts zu tun haben. Ich spreche von Lambda Ausdrücken und von einem sehr Python-spezifischen, aber äusserst nützlichen Konzept, nämlich den Comprehensions, wo quasi die Vorschrift wie eine Datenstruktur zu konstruieren ist, in einer sehr kompakten Form angegeben wird. 

Lambda Ausdrücke halten seit längerem Einzug in die Welt der objektorientierten Sprache und kommen ursprünglich aus funktionalen Programmiersprachen wie beispielsweise LISP. Lambda Ausdrücke sind anonymisierte Funktionen, die beliebig viele Anzahl von Parametern enthalten können. Im Allgemeinen werden sie durch Verweise angesprochen. Lambda Ausdrücke wurden vom Python Erfinder van Rossum nur widerwillig in die Sprache aufgenommen, weil identische Resultate mit den Comprehensions auf einfachere Weise erzielt werden können.

Lambdas

Ein Lambda Ausdruck hat folgende Syntax:

Lambda <Argumenten Liste> : <Ausdruck>

In folgenden Beispielen wird von Lambda Ausdrücken Gebrauch gemacht. Allerdings ist anzumerken, dass wenn die Beispiele in PyCharm CE ausgeführt werden, teilweise Warnmeldungen kommen. Das weil eben die Lambda Ausdrücke nur widerwillig in Python integriert wurden.

x = lambda a : a + 10
print(x(5)) 
# 15
x = lambda a, b : a * b
print(x(5, 6))
# 30
def evaluator(f):
    return lambda x, y: f(x,y)


def multiplicate(x, y):
    return x * y


def add(x, y):
    return x + y


evaluate = evaluator(multiplicate)
print(evaluate(2, 5))
# 10
evaluate = evaluator(add)
print(evaluate(2, 5))
# 7
Oben deutlich ersichtlich, wie sich Lambda's für Transformationen eignen. Unten ein geläufigeres Beispiel für die Verwendung von Lambdas als anonyme Funktion.

def apply(f, list1, list2):
    result = []
    if len(list1) != len(list2):
        return result
    i = 0
    for el1 in list1:
        result.append(f(el1, list2[i]))
        i += 1
    return result


list1 = [1, 2, 3, 4, 5, 6, 7]
list2 = [1, 3, 4, 5, 8, 9, 10]
applier = apply((lambda x, y: x+y), list1, list2)
print(applier)

applier = apply((lambda x, y: x*y), list1, list2)
print(applier)
# [2, 5, 7, 9, 13, 15, 17]
# [1, 6, 12, 20, 40, 54, 70]

Map

Eine Map definiert eine Abbildung von einem iterierbaren Objekt auf ein anderes. Mit einer Map, kann obiges Beispiel wesentlich kompakter geschrieben werden:

adder = list(map(lambda x, y: x+y, list1, list2))
print(adder)

Selbst mit bestehenden Funktionen von Python funktioniert map:

print(list(map(len, ["Eins", "Zwei", "Drei", "Sechs"])))
# [4, 4, 4, 5]

Filter

Mit Filter werden Daten gefiltert, das heisst Bedingungen legen fest, ob das iterierbare Objekt im Resultat landet oder nicht. Auch hier einige Beispiele:


import math as m

def prim(ls):
    while ls[0] < 2:
        del ls[0]
    newrange = range(2, int(m.sqrt(max(ls))+1))
    n = 0
    for i in newrange:
        while ls[n] <= i:
            n = n+1
        filterit = filter(lambda x: x % i > 0, ls[n:len(ls)])
        templs = list(ls[0:n])
        templs.extend(list(filterit))
        ls = templs
        n = ls[0]
    return ls

list1 = range(150)
print(prim(list1))

Obiges Beispiel verwendet einen filter-Ausdruck und zwar werden alle Elemente in einer Subliste geprüft, ob sie Modulo i dividierbar sind. Die gesamte Funktion hat zum Zweck, alle Primzahlen in einem bestimmten Range zurückzugeben.

Filter akzeptieren prinzipiell alle Funktionen, die für ein iterables Objekt eine Boolschen Wert zurückgeben:


def findEvens(x):
    if x % 2 == 0:
        return True
    return False


list2 = list(range(21))
print(list(filter(findEvens, list2)))
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Reduce

Während Map eine Abbildung auf eine gleichmächtige Menge vornimmt und Filter spezifische Elemente selektiert und eine reduzierte Liste zurückgibt, gibt Reduce nur ein Element zurück. Reduce nimmt eine Funktion entgegen und berechnet einen spezifischen Wert aus den Elementen des iterierbaren Objekts. Dabei wird die Funktion zuerst auf die ersten beiden Werte angewendet und dann das Resultat auf den dritten Wert und das neue Resultat auf den vierten Wert usf. Damit Reduce verwendet werden kann, muss das Modul functools importiert werden.

Nachfolgend ein Beispiel zur Verwendung von Reduce:


from functools import reduce


def add(x, y):
    return x + y


print(reduce(add, range(0, 11)))
# 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55

# oder als Einzeiler
print(reduce(lambda x,y: x+y, range(0, 11)))

Comprehensions

Comprehensions sind abstrakte Listen. Dabei wird in eckigen Klammern angegeben wie die Liste algorithmisch konstruiert werden soll. Dank der Mächtigkeit von Comprehensions kann auf die Verwendung von Lambda Ausdrücken verzichtet werden. Auch die Funktionen Map, Reduce und Filter können sehr einfach durch Comprehensions ersetzt werden.

Die allgemeine Syntax ist folgende:

[Ausdruck von 1 oder mehreren For-Anweisungen und eventuell If-Bedingungen]

Obschon meines Erachtens gerade die Reduce Funktion nicht so sehr trivial mit List Comprehensions ersetzt werden kann, scheint es offenbar möglich zu sein. Obiges Beispiel einer Addition von den Elementen einer Liste ist jedenfalls ohne die Verwendung der Funktion sum nicht möglich.

Auch hier einige Beispiel dazu:

Heraussuchen aller Pythagoräischen Zahlentripel:

print([(x, y, z) for x in range(1, 27) for y in range(x, 27) for z in range(y, 27) if x*x + y*y == z*z])
# [(3, 4, 5), (5, 12, 13), (6, 8, 10), (7, 24, 25), (8, 15, 17), (9, 12, 15), (10, 24, 26), (12, 16, 20), (15, 20, 25)]

Map ist ganz einfach zu realisieren, wie folgendes Beispiel zeigt. Allerdings bitte nicht verwenden, um effektiv Primzahlen zu berechnen. Die Idee dahinter ist, alle Vielfache einer Primzahl aus der Liste zu entfernen:


def findPrimes(list):
    list2 = list.copy()
    notPrimes = [[]]
    for i in range(2, int(m.sqrt(len(list))) + 1):
        notPrimes.append([x*i for x in list])
    i = 0
    result = set(list2)
    while i < len(notPrimes):
        result = result.difference(set(notPrimes[i]))
        i += 1
    return result


print(list(findPrimes(list(range(2, 100)))))

Einige Reduktionsfunktionen sind direkter Bestandteil von Python: sum, max, min.

Ich wünsche nun viel Vergnügen beim Training!

Keine Kommentare:

Kommentar veröffentlichen