Du gibst Funktionen weiter wie Zahlen und baust Funktionen, die Funktionen
zurückgeben. Damit wandelst du jedes Element einer Liste um (map),
behältst, was passt (filter), und faltest alles zu einem Wert
zusammen (reduce). Zum Schluss schreibst du eine Schleife als
Kette um und triffst eine Aufgabe, bei der die Schleife klarer bleibt.
Eine Zahl kann man in eine Variable legen und einer Funktion mitgeben. Mit
einer Funktion geht das auch. Diese Funktion bekommt eine Funktion f und
einen Wert x und wendet f zweimal an:
def twice(f, x) f.call(f.call(x)) end add_three = ->(x) { x + 3 } puts add_three.call(4) # 7 puts twice(add_three, 10) # 16
->(x) { x + 3 } ist ein Lambda: eine Funktion ohne Namen, die man in
eine Variable legen und weitergeben kann wie eine Zahl. f.call(…) ruft sie
auf. twice weiß nicht, was f tut; es ruft sie nur auf.
def twice(f, x): return f(f(x)) add_three = lambda x: x + 3 print(add_three(4)) # 7 print(twice(add_three, 10)) # 16
lambda x: x + 3 ist ein Lambda: eine Funktion ohne Namen, die man in
eine Variable legen und weitergeben kann wie eine Zahl. Hinter dem
Doppelpunkt steht, was sie zurückgibt. f(…) ruft sie auf, wie jede andere
Funktion. twice weiß nicht, was f tut; es ruft sie nur auf.
function twice(f, x) { return f(f(x)); } const addThree = x => x + 3; console.log(addThree(4)); // 7 console.log(twice(addThree, 10)); // 16
x => x + 3 ist eine Arrow Function, oft auch Lambda genannt: eine
Funktion ohne Namen, die man in eine Variable legen und weitergeben kann wie
eine Zahl. Rechts vom Pfeil steht, was sie zurückgibt; mit zwei Parametern
schreibt man (a, b) => a + b. f(…) ruft sie auf. twice weiß nicht, was
f tut; es ruft sie nur auf.
Was gibt puts twice(->(x) { x * x }, 3) aus?
Was gibt print(twice(lambda x: x * x, 3)) aus?
Was gibt console.log(twice(x => x * x, 3)) aus?
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Es kommt 81 heraus: Der erste Aufruf macht aus 3 die 9, der zweite aus 9
die 81. Die Funktion, die twice bekommt, ist ein Wert wie die 3, und erst
twice entscheidet, wann und wie oft sie läuft.
Das Wort Lambda kommt aus der Mathematik, vom griechischen Buchstaben λ, mit dem Alonzo Church solche Funktionen schrieb.
Alonzo Church (1903–1995), amerikanischer Mathematiker und Logiker, von 1929 bis 1967 an der Universität Princeton. In den 1930er Jahren erfand er den Lambda-Kalkül, eine Schreibweise für Funktionen, die keinen Namen brauchen: Statt f(x) = x + 3 schreibt man λx. x + 3. Darum heißen Funktionen ohne Namen bis heute Lambdas, und in Python und Ruby gibt es dafür das Wort lambda.
John McCarthy übernahm Churchs Schreibweise für Lisp, und von dort kam sie in viele andere Sprachen; Python bekam lambda, map, filter und reduce 1994. Bei Church in Princeton machten Alan Turing und Stephen Kleene ihren Doktor.
Schreib eine Funktion apply_n(f, n, x), die die Funktion f n-mal hintereinander auf x anwendet und das Ergebnis zurückgibt: apply_n(->(x) { x * 2 }, 3, 1) ergibt 8, denn aus 1 wird 2, dann 4, dann 8. Ist n gleich 0, kommt x unverändert zurück. Die Prüfung ruft deine Funktion mit verschiedenen Lambdas auf; eine Schleife ist hier erlaubt.
Von Hand: apply_n(->(x) { x * 2 }, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.
Eine Schleife, die n-mal läuft, n.times do … end; in ihr wird x zu f.call(x).
Schreib eine Funktion apply_n(f, n, x), die die Funktion f n-mal hintereinander auf x anwendet und das Ergebnis zurückgibt: apply_n(lambda x: x * 2, 3, 1) ergibt 8, denn aus 1 wird 2, dann 4, dann 8. Ist n gleich 0, kommt x unverändert zurück. Die Prüfung ruft deine Funktion mit verschiedenen Lambdas auf; eine Schleife ist hier erlaubt.
Von Hand: apply_n(lambda x: x * 2, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.
Eine Schleife, die n-mal läuft, for _ in range(n):; in ihr wird x zu f(x).
Schreib eine Funktion applyN(f, n, x), die die Funktion f n-mal hintereinander auf x anwendet und das Ergebnis zurückgibt: applyN(x => x * 2, 3, 1) ergibt 8, denn aus 1 wird 2, dann 4, dann 8. Ist n gleich 0, kommt x unverändert zurück. Die Prüfung ruft deine Funktion mit verschiedenen Arrow Functions auf; eine Schleife ist hier erlaubt.
Von Hand: applyN(x => x * 2, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.
Eine Schleife, die n-mal läuft, for (let i = 0; i < n; i++); in ihr wird x zu f(x).
Eine Funktion kann auch eine Funktion zurückgeben:
def make_adder(n) ->(x) { x + n } end add_five = make_adder(5) puts add_five.call(10) # 15 puts make_adder(100).call(1) # 101
def make_adder(n): return lambda x: x + n add_five = make_adder(5) print(add_five(10)) # 15 print(make_adder(100)(1)) # 101
function makeAdder(n) { return x => x + n; } const addFive = makeAdder(5); console.log(addFive(10)); // 15 console.log(makeAdder(100)(1)); // 101
Das Lambda merkt sich das n, mit dem es gebaut wurde, auch wenn
make_adder längst fertig ist. Eine Funktion, die sich so Variablen von
draußen merkt, heißt Closure.
Schreib eine Funktion make_multiplier(n), die ein Lambda zurückgibt, das seine Zahl mit n multipliziert: make_multiplier(3).call(7) ergibt 21. Die Prüfung baut so mehrere Lambdas, auch zwei auf einmal, und ruft sie auf.
Was gibt make_multiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit .call(7).
make_adder oben baut so eine Funktion. Hier multipliziert sie, und das n darf im Lambda stehen.
Schreib eine Funktion make_multiplier(n), die ein Lambda zurückgibt, das seine Zahl mit n multipliziert: make_multiplier(3)(7) ergibt 21. Die Prüfung baut so mehrere Lambdas, auch zwei auf einmal, und ruft sie auf.
Was gibt make_multiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit (7).
make_adder oben baut so eine Funktion. Hier multipliziert sie, und das n darf im Lambda stehen.
Schreib eine Funktion makeMultiplier(n), die eine Funktion zurückgibt, die ihre Zahl mit n multipliziert: makeMultiplier(3)(7) ergibt 21. Die Prüfung baut so mehrere Funktionen, auch zwei auf einmal, und ruft sie auf.
Was gibt makeMultiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit (7).
makeAdder oben baut so eine Funktion. Hier multipliziert sie, und das n darf in der Arrow Function stehen.
Die meisten Schleifen über Listen folgen einem von drei Mustern: Sie wandeln
jedes Element um, behalten einige oder rechnen alles zu einem Wert zusammen.
Für jedes Muster gibt es eine Funktion, der man nur noch sagt, was mit einem
Element geschehen soll. Die erste ist map:
numbers = [1, 2, 3] p numbers.map { |x| x * 10 } # [10, 20, 30] p numbers # [1, 2, 3], unverändert words = ['Igel', 'Ameise', 'Wal'] p words.map { |word| word.upcase } # ["IGEL", "AMEISE", "WAL"]
map ruft den Block für jedes Element auf und baut aus den Ergebnissen eine
neue Liste, so lang wie die alte; die alte bleibt, wie sie ist. Ein Block
{ |x| … } ist wie ein Lambda eine Funktion ohne Namen, nur schreibt man ihn
direkt hinter den Aufruf. Ein Lambda aus einer Variablen geht auch, mit &
davor: numbers.map(&add_three).
numbers = [1, 2, 3] print(list(map(lambda x: x * 10, numbers))) # [10, 20, 30] print(numbers) # [1, 2, 3], unverändert words = ['Igel', 'Ameise', 'Wal'] print(list(map(str.upper, words))) # ['IGEL', 'AMEISE', 'WAL']
map(f, numbers) ruft f für jedes Element auf; aus den Ergebnissen
entsteht eine neue Liste, so lang wie die alte, und die alte bleibt, wie sie
ist. map gibt dabei noch keine Liste zurück, sondern ein map-Objekt, das
die Werte erst ausrechnet, wenn jemand sie haben will; list(…) macht eine
Liste daraus. Statt eines Lambdas geht jede Funktion mit Namen, ohne
Klammern dahinter: str.upper ist die Methode upper der Strings.
In Python schreibt man dasselbe oft als List Comprehension:
[x * 10 for x in numbers]. Sie tut dasselbe wie map, und die Prüfungen
nehmen beides an.
const numbers = [1, 2, 3]; console.log(numbers.map(x => x * 10)); // [ 10, 20, 30 ] console.log(numbers); // [ 1, 2, 3 ], unverändert const words = ['Igel', 'Ameise', 'Wal']; console.log(words.map(word => word.toUpperCase())); // [ 'IGEL', 'AMEISE', 'WAL' ]
map ruft die Funktion für jedes Element auf und baut aus den Ergebnissen
eine neue Liste, so lang wie die alte; die alte bleibt, wie sie ist.
Was gibt p [3, 1, 2].map { |x| x > 1 } aus?
Was gibt print(list(map(lambda x: x > 1, [3, 1, 2]))) aus?
Was gibt console.log([3, 1, 2].map(x => x > 1)) aus?
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Es erscheint [true, false, true]: map gibt für jedes Element das
Ergebnis des Blocks, und x > 1 ist true oder false. Die Liste ist so
lang wie vorher. Elemente weglassen kann map nicht; das tut die nächste
Funktion.
Es erscheint [True, False, True]: map gibt für jedes Element das
Ergebnis des Lambdas, und x > 1 ist True oder False. Die Liste ist so
lang wie vorher. Elemente weglassen kann map nicht; das tut die nächste
Funktion.
Es erscheint [ true, false, true ]: map gibt für jedes Element das
Ergebnis der Funktion, und x > 1 ist true oder false. Die Liste ist
so lang wie vorher. Elemente weglassen kann map nicht; das tut die nächste
Funktion.
In den Katas dieses Kapitels und des nächsten tut map oder select die
Arbeit; eine eigene Schleife brauchst du nicht. each, for und times
sind dafür abgeschaltet.
In den Katas dieses Kapitels und des nächsten tut map oder filter die
Arbeit, oder eine List Comprehension; eine eigene Schleife brauchst du
nicht. Abschalten kann die Prüfung eine Schleife mit for nicht, schreib
sie trotzdem nicht.
In den Katas dieses Kapitels und des nächsten tut map oder filter die
Arbeit; eine eigene Schleife brauchst du nicht. forEach ist dafür
abgeschaltet; eine Schleife mit for kann die Prüfung nicht abschalten,
schreib sie trotzdem nicht.
Ein Laden speichert seine Preise ohne Mehrwertsteuer, in Cent. Schreib eine Funktion with_tax(prices), die jeden Preis mit 19 % Mehrwertsteuer zurückgibt, als neue Liste: with_tax([100, 300]) ergibt [119, 357]. Rechne mit ganzen Zahlen, price * 119 / 100; die Preise sind volle Euro, so geht es auf. Mit map, ohne eigene Schleife.
Für einen einzigen Preis: 100 * 119 / 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.
prices.map { |price| … } gibt die neue Liste zurück; in den Block kommt die Rechnung für einen Preis.
Ein Laden speichert seine Preise ohne Mehrwertsteuer, in Cent. Schreib eine Funktion with_tax(prices), die jeden Preis mit 19 % Mehrwertsteuer zurückgibt, als neue Liste: with_tax([100, 300]) ergibt [119, 357]. Rechne mit ganzen Zahlen, price * 119 // 100; die Preise sind volle Euro, so geht es auf. Mit map oder einer List Comprehension, ohne eigene Schleife.
Für einen einzigen Preis: 100 * 119 // 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.
list(map(lambda price: …, prices)) gibt die neue Liste zurück; ohne list(…) ist es noch ein map-Objekt.
Ein Laden speichert seine Preise ohne Mehrwertsteuer, in Cent. Schreib eine Funktion withTax(prices), die jeden Preis mit 19 % Mehrwertsteuer zurückgibt, als neue Liste: withTax([100, 300]) ergibt [119, 357]. Rechne mit ganzen Zahlen, price * 119 / 100; die Preise sind volle Euro, so geht es auf. Mit map, ohne eigene Schleife.
Für einen einzigen Preis: 100 * 119 / 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.
prices.map(price => …) gibt die neue Liste zurück; rechts vom Pfeil steht die Rechnung für einen Preis.
Schreib eine Funktion lengths(words), die zu jedem Wort seine Länge zurückgibt, als neue Liste: lengths(['Igel', 'Wal']) ergibt [4, 3]. Mit map, ohne eigene Schleife.
'Igel'.length ist 4. Was soll mit jedem Wort der Liste geschehen?
words.map { |word| … }: In den Block kommt, was aus einem Wort werden soll.
Schreib eine Funktion lengths(words), die zu jedem Wort seine Länge zurückgibt, als neue Liste: lengths(['Igel', 'Wal']) ergibt [4, 3]. Mit map oder einer List Comprehension, ohne eigene Schleife.
len('Igel') ist 4. Was soll mit jedem Wort der Liste geschehen?
map nimmt auch eine Funktion mit Namen, ohne Klammern dahinter: Welche Funktion gibt die Länge? Und list(…) drumherum.
Schreib eine Funktion lengths(words), die zu jedem Wort seine Länge zurückgibt, als neue Liste: lengths(['Igel', 'Wal']) ergibt [4, 3]. Mit map, ohne eigene Schleife.
'Igel'.length ist 4. Was soll mit jedem Wort der Liste geschehen?
words.map(word => …): Rechts vom Pfeil steht, was aus einem Wort werden soll.
Recursive Functions of Symbolic Expressions and Their Computation by Machine, Part I, John McCarthy, Communications of the ACM, 1960. Der Aufsatz, mit dem Lisp beginnt, schreibt Funktionen mit Churchs λ und hat einen Abschnitt »Functions with Functions as Arguments«. Die erste Funktion darin ist maplist: Sie wendet eine Funktion auf eine Liste an, dann auf den Rest ohne das erste Element und so weiter, und sammelt die Ergebnisse in einer neuen Liste. McCarthy schreibt sie in einer Zeile, rekursiv: frei bei der ACM.
Die zweite Funktion bekommt eine Funktion, die für jedes Element sagt, ob es bleiben soll:
numbers = [4, 7, 10, 3] p numbers.select { |x| x > 5 } # [7, 10] p numbers.reject { |x| x > 5 } # [4, 3]
select behält die Elemente, für die der Block true ergibt, in ihrer
Reihenfolge; reject behält die anderen. filter ist in Ruby ein zweiter
Name für select.
numbers = [4, 7, 10, 3] print(list(filter(lambda x: x > 5, numbers))) # [7, 10] print([x for x in numbers if x > 5]) # [7, 10]
filter behält die Elemente, für die das Lambda True ergibt, in ihrer
Reihenfolge; wie bei map macht list(…) daraus eine Liste. Die zweite
Zeile ist dasselbe als List Comprehension, mit if am Ende.
const numbers = [4, 7, 10, 3]; console.log(numbers.filter(x => x > 5)); // [ 7, 10 ] console.log(numbers.filter(x => x <= 5)); // [ 4, 3 ]
filter behält die Elemente, für die die Funktion true ergibt, in ihrer
Reihenfolge.
Schreib eine Funktion evens(numbers), die nur die geraden Zahlen der Liste behält, in ihrer Reihenfolge: evens([1, 2, 3, 4]) ergibt [2, 4]. Mit select, ohne eigene Schleife.
select fragt jede Zahl: Soll sie bleiben? Der Block gibt dafür true oder false zurück.
x.even? fragt, ob x gerade ist: numbers.select { |x| … }.
Schreib eine Funktion evens(numbers), die nur die geraden Zahlen der Liste behält, in ihrer Reihenfolge: evens([1, 2, 3, 4]) ergibt [2, 4]. Mit filter oder einer List Comprehension, ohne eigene Schleife.
filter fragt jede Zahl: Soll sie bleiben? Das Lambda gibt dafür True oder False zurück.
Gerade ist eine Zahl, wenn beim Teilen durch 2 kein Rest bleibt: x % 2 == 0. Und list(…) drumherum.
Schreib eine Funktion evens(numbers), die nur die geraden Zahlen der Liste behält, in ihrer Reihenfolge: evens([1, 2, 3, 4]) ergibt [2, 4]. Mit filter, ohne eigene Schleife.
filter fragt jede Zahl: Soll sie bleiben? Die Arrow Function gibt dafür true oder false zurück.
Gerade ist eine Zahl, wenn beim Teilen durch 2 kein Rest bleibt: x % 2 === 0.
Schreib eine Funktion with_letter(words, letter), die die Wörter behält, in denen letter vorkommt, in ihrer Reihenfolge: with_letter(['Hund', 'Katze', 'Maus'], 'a') ergibt ['Katze', 'Maus']. Groß und klein zählt: 'A' ist nicht 'a'. Mit select, ohne eigene Schleife.
Der Block bekommt ein Wort und sagt true oder false: Steht letter darin?
Im Block darfst du letter benutzen, obwohl es ein Parameter von with_letter ist: Der Block merkt es sich wie die Closure oben. word.include?(letter) fragt, ob es im Wort steht.
Schreib eine Funktion with_letter(words, letter), die die Wörter behält, in denen letter vorkommt, in ihrer Reihenfolge: with_letter(['Hund', 'Katze', 'Maus'], 'a') ergibt ['Katze', 'Maus']. Groß und klein zählt: 'A' ist nicht 'a'. Mit filter oder einer List Comprehension, ohne eigene Schleife.
Das Lambda bekommt ein Wort und sagt True oder False: Steht letter darin?
Im Lambda darfst du letter benutzen, obwohl es ein Parameter von with_letter ist: Das Lambda merkt es sich wie die Closure oben. letter in word fragt, ob es im Wort steht.
Schreib eine Funktion withLetter(words, letter), die die Wörter behält, in denen letter vorkommt, in ihrer Reihenfolge: withLetter(['Hund', 'Katze', 'Maus'], 'a') ergibt ['Katze', 'Maus']. Groß und klein zählt: 'A' ist nicht 'a'. Mit filter, ohne eigene Schleife.
Die Funktion bekommt ein Wort und sagt true oder false: Steht letter darin?
In der Arrow Function darfst du letter benutzen, obwohl es ein Parameter von withLetter ist: Sie merkt es sich wie die Closure oben. word.includes(letter) fragt, ob es im Wort steht.
Jedes Ergebnis einer Klausur ist ein Paar aus Name und Punkten, von 0 bis 15. Schreib eine Funktion passed(results), die die Namen derer zurückgibt, die bestanden haben, also mindestens 5 Punkte, in ihrer Reihenfolge: passed([['Ada', 12], ['Alan', 4]]) ergibt ['Ada']. Erst select, dann map, ohne eigene Schleife.
Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.
Ein Block darf ein Paar gleich in zwei Namen auspacken: { |name, points| … }.
Jedes Ergebnis einer Klausur ist ein Paar aus Name und Punkten, von 0 bis 15. Schreib eine Funktion passed(results), die die Namen derer zurückgibt, die bestanden haben, also mindestens 5 Punkte, in ihrer Reihenfolge: passed([['Ada', 12], ['Alan', 4]]) ergibt ['Ada']. Erst filter, dann map, oder eine List Comprehension, ohne eigene Schleife.
Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.
Ein Paar result hat den Namen in result[0] und die Punkte in result[1]. In einer List Comprehension kann man es gleich auspacken: for name, points in results.
Jedes Ergebnis einer Klausur ist ein Paar aus Name und Punkten, von 0 bis 15. Schreib eine Funktion passed(results), die die Namen derer zurückgibt, die bestanden haben, also mindestens 5 Punkte, in ihrer Reihenfolge: passed([['Ada', 12], ['Alan', 4]]) ergibt ['Ada']. Erst filter, dann map, ohne eigene Schleife.
Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.
Eine Arrow Function darf ein Paar gleich in zwei Namen auspacken: ([name, points]) => ….
Die dritte Funktion rechnet eine Liste zu einem einzigen Wert zusammen. Sie bekommt einen Startwert und eine Funktion mit zwei Parametern: dem Ergebnis bis hierher und dem nächsten Element.
numbers = [3, 4, 5] p numbers.reduce(0) { |sum, x| sum + x } # 12 p numbers.reduce { |sum, x| sum + x } # 12
reduce(0) fängt mit 0 an. Was der Block zurückgibt, ist beim nächsten
Element das Ergebnis bis hierher, und nach dem letzten Element das Ergebnis.
Ohne Startwert fängt reduce mit dem ersten Element an. inject ist in
Ruby ein zweiter Name für reduce.
from functools import reduce numbers = [3, 4, 5] print(reduce(lambda total, x: total + x, numbers, 0)) # 12 print(reduce(lambda total, x: total + x, numbers)) # 12
reduce(f, numbers, 0) fängt mit 0 an. Was f zurückgibt, ist beim
nächsten Element das Ergebnis bis hierher, und nach dem letzten Element das
Ergebnis. Ohne Startwert fängt reduce mit dem ersten Element an. Seit
Python 3 steht reduce im Modul functools und muss importiert werden.
const numbers = [3, 4, 5]; console.log(numbers.reduce((total, x) => total + x, 0)); // 12 console.log(numbers.reduce((total, x) => total + x)); // 12
Der Startwert ist das zweite Argument, hier 0. Was die Funktion
zurückgibt, ist beim nächsten Element das Ergebnis bis hierher, und nach dem
letzten Element das Ergebnis. Ohne Startwert fängt reduce mit dem ersten
Element an.
Mit dem Startwert 0 läuft es so:
Startwert 0 Element 3 0 + 3 = 3 Element 4 3 + 4 = 7 Element 5 7 + 5 = 12
Was geschieht bei p [].reduce { |sum, x| sum + x }?
Was geschieht bei print(reduce(lambda total, x: total + x, []))?
Was geschieht bei console.log([].reduce((total, x) => total + x))?
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Es erscheint nil: Ohne Startwert nimmt reduce das erste Element, und
eine leere Liste hat keins. Mit reduce(0) kommt 0 heraus, die Summe von
nichts.
Das Programm bricht ab: TypeError: reduce() of empty iterable with no
initial value. Ohne Startwert nimmt reduce das erste Element, und eine
leere Liste hat keins. Mit dem Startwert 0 kommt 0 heraus, die Summe von
nichts.
Das Programm bricht ab: TypeError: Reduce of empty array with no initial
value. Ohne Startwert nimmt reduce das erste Element, und eine leere
Liste hat keins. Mit dem Startwert 0 kommt 0 heraus, die Summe von nichts.
Gib darum einen Startwert an, wo die Liste leer sein kann: den Wert, der
herauskommen soll, wenn sie leer ist. Wer die Katas zur
Rekursion kennt: Dort rechnet total eine Summe genauso,
und der Fall, in dem sie sofort fertig ist, gibt diesen Startwert zurück.
Schreib eine Funktion product(numbers), die alle Zahlen der Liste miteinander multipliziert, mit reduce: product([2, 3, 4]) ergibt 24. Für die leere Liste ergibt sie 1.
Von Hand: ein Startwert, dann mal 2, mal 3, mal 4. Welcher Startwert lässt das Ergebnis 24, und was kommt dann bei der leeren Liste heraus?
numbers.reduce(…) { |result, x| result * x }: Was gehört in die Klammer?
Schreib eine Funktion product(numbers), die alle Zahlen der Liste miteinander multipliziert, mit reduce aus functools: product([2, 3, 4]) ergibt 24. Für die leere Liste ergibt sie 1. Ohne math.prod.
Von Hand: ein Startwert, dann mal 2, mal 3, mal 4. Welcher Startwert lässt das Ergebnis 24, und was kommt dann bei der leeren Liste heraus?
reduce(lambda result, x: result * x, numbers, …): Was gehört als letztes Argument hinein?
Schreib eine Funktion product(numbers), die alle Zahlen der Liste miteinander multipliziert, mit reduce: product([2, 3, 4]) ergibt 24. Für die leere Liste ergibt sie 1.
Von Hand: ein Startwert, dann mal 2, mal 3, mal 4. Welcher Startwert lässt das Ergebnis 24, und was kommt dann bei der leeren Liste heraus?
numbers.reduce((result, x) => result * x, …): Was gehört als letztes Argument hinein?
Schreib eine Funktion longest(words), die das längste Wort zurückgibt, mit reduce: longest(['Igel', 'Ameise', 'Wal']) ergibt 'Ameise'. Sind mehrere gleich lang, gewinnt das erste. Die Liste ist nie leer. Ohne max_by, max und sort.
Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?
Ohne Startwert fängt reduce mit dem ersten Wort an; die Liste ist nie leer, das passt. Im Block vergleichst du zwei Längen und gibst eines der beiden Wörter zurück, nie eine Zahl.
Schreib eine Funktion longest(words), die das längste Wort zurückgibt, mit reduce aus functools: longest(['Igel', 'Ameise', 'Wal']) ergibt 'Ameise'. Sind mehrere gleich lang, gewinnt das erste. Die Liste ist nie leer. Ohne max und sorted.
Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?
Ohne Startwert fängt reduce mit dem ersten Wort an; die Liste ist nie leer, das passt. Ein Lambda kann wählen: a if bedingung else b. Es gibt eines der beiden Wörter zurück, nie eine Zahl.
Schreib eine Funktion longest(words), die das längste Wort zurückgibt, mit reduce: longest(['Igel', 'Ameise', 'Wal']) ergibt 'Ameise'. Sind mehrere gleich lang, gewinnt das erste. Die Liste ist nie leer. Ohne max_by, max und sort.
Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?
Ohne Startwert fängt reduce mit dem ersten Wort an; die Liste ist nie leer, das passt. Eine Arrow Function kann wählen: bedingung ? a : b. Sie gibt eines der beiden Wörter zurück, nie eine Zahl.
Der Startwert muss keine Zahl sein. Ist er ein leerer Hash, {}, trägt
jedes Element etwas darin ein, und das Ergebnis bis hierher ist der Hash mit
allem, was schon eingetragen ist.
Der Startwert muss keine Zahl sein. Ist er ein leeres Dictionary, {},
trägt jedes Element etwas darin ein, und das Ergebnis bis hierher ist das
Dictionary mit allem, was schon eingetragen ist.
Der Startwert muss keine Zahl sein. Ist er ein leeres Objekt, {}, trägt
jedes Element etwas darin ein, und das Ergebnis bis hierher ist das Objekt
mit allem, was schon eingetragen ist.
Auf einer Klassenfahrt schreibt jemand jede Zahlung auf, als Paar aus Name und Betrag in Cent. Schreib eine Funktion totals(payments), die mit reduce zusammenrechnet, wie viel jede Person bezahlt hat, als Hash: totals([['Ada', 300], ['Alan', 200], ['Ada', 150]]) ergibt { 'Ada' => 450, 'Alan' => 200 }. Ohne group_by und tally.
Der Startwert ist ein leerer Hash, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?
Im Block zählst du den Betrag zu dem dazu, was die Person schon hat; sums.fetch(name, 0) ist 0, wenn sie noch nicht darin steht. Das Paar packt der Block gleich aus: |sums, (name, amount)|.
Was der Block zurückgibt, ist beim nächsten Element sums. Das soll der Hash sein:
payments.reduce({}) do |sums, (name, amount)| # hier: den Betrag eintragen sums end
Auf einer Klassenfahrt schreibt jemand jede Zahlung auf, als Paar aus Name und Betrag in Cent. Schreib eine Funktion totals(payments), die mit reduce aus functools zusammenrechnet, wie viel jede Person bezahlt hat, als Dictionary: totals([['Ada', 300], ['Alan', 200], ['Ada', 150]]) ergibt {'Ada': 450, 'Alan': 200}.
Der Startwert ist ein leeres Dictionary, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?
In ein Lambda passt keine Zuweisung. Schreib darum eine kleine Funktion mit def, die sums und eine Zahlung bekommt, den Betrag dazuzählt und sums zurückgibt; sums.get(name, 0) ist 0, wenn die Person noch nicht darin steht. reduce bekommt sie ohne Klammern.
Ein Gerüst:
def add(sums, payment): name, amount = payment # hier: den Betrag eintragen return sums def totals(payments): return reduce(add, payments, {})
Auf einer Klassenfahrt schreibt jemand jede Zahlung auf, als Paar aus Name und Betrag in Cent. Schreib eine Funktion totals(payments), die mit reduce zusammenrechnet, wie viel jede Person bezahlt hat, als Objekt: totals([['Ada', 300], ['Alan', 200], ['Ada', 150]]) ergibt { Ada: 450, Alan: 200 }. Ohne Object.groupBy.
Der Startwert ist ein leeres Objekt, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?
Die Arrow Function zählt den Betrag zu dem dazu, was die Person schon hat; sums[name] || 0 ist 0, wenn sie noch nicht darin steht. Das Paar packt sie gleich aus: (sums, [name, amount]) => ….
Mit mehr als einem Ausdruck braucht die Arrow Function geschweifte Klammern und ein return:
payments.reduce((sums, [name, amount]) => { // hier: den Betrag eintragen return sums; }, {});
map, filter und reduce geben neue Werte zurück, und auf einer neuen
Liste kann gleich die nächste Funktion laufen. So entsteht eine Kette: Was
kosten alle Bestellungen zusammen, von denen jemand mindestens fünf Stück
bestellt hat? Die Preise sind in Cent.
orders = [ { 'item' => 'Heft', 'price' => 120, 'count' => 5 }, { 'item' => 'Stift', 'price' => 80, 'count' => 10 }, { 'item' => 'Lineal', 'price' => 250, 'count' => 1 }, ] total = orders .select { |order| order['count'] >= 5 } .map { |order| order['price'] * order['count'] } .sum puts total # 1400
Von oben nach unten liest sich die Kette wie die Frage: behalte die
Bestellungen mit mindestens fünf Stück, rechne für jede den Preis aus, zähl
zusammen. sum ist dabei ein reduce, das es fertig gibt: dasselbe wie
reduce(0) { |sum, x| sum + x }.
orders = [ {'item': 'Heft', 'price': 120, 'count': 5}, {'item': 'Stift', 'price': 80, 'count': 10}, {'item': 'Lineal', 'price': 250, 'count': 1}, ] big = filter(lambda order: order['count'] >= 5, orders) prices = map(lambda order: order['price'] * order['count'], big) total = sum(prices) print(total) # 1400
Von oben nach unten liest sich das wie die Frage: behalte die Bestellungen
mit mindestens fünf Stück, rechne für jede den Preis aus, zähl zusammen.
sum ist dabei ein reduce, das es fertig gibt. In eine Zeile geschrieben
liest man die Kette in Python von innen nach außen,
sum(map(…, filter(…, orders))); darum schreibt man sie oft als
Comprehension:
total = sum(order['price'] * order['count'] for order in orders if order['count'] >= 5)
const orders = [ { item: 'Heft', price: 120, count: 5 }, { item: 'Stift', price: 80, count: 10 }, { item: 'Lineal', price: 250, count: 1 }, ]; const total = orders .filter(order => order.count >= 5) .map(order => order.price * order.count) .reduce((sum, x) => sum + x, 0); console.log(total); // 1400
Von oben nach unten liest sich die Kette wie die Frage: behalte die Bestellungen mit mindestens fünf Stück, rechne für jede den Preis aus, zähl zusammen.
Jede Ware im Warenkorb ist ein Hash mit Name, Art, Preis in Cent und Stückzahl. Schreib eine Funktion category_total(items, category), die als Kette ausrechnet, was alle Waren einer Art zusammen kosten: behalten, was zur Art gehört, für jede Ware Preis mal Stückzahl, zusammenzählen. Die Prüfung ruft sie unter anderem mit diesem Warenkorb auf:
items = [ { 'name' => 'Heft', 'category' => 'Schreibwaren', 'price' => 120, 'count' => 5 }, { 'name' => 'Stift', 'category' => 'Schreibwaren', 'price' => 80, 'count' => 10 }, { 'name' => 'Apfel', 'category' => 'Obst', 'price' => 50, 'count' => 6 }, ]
category_total(items, 'Obst') ergibt 300, category_total(items, 'Schreibwaren') ergibt 1400. Gibt es keine Ware der Art, ergibt sie 0.
Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?
item['category'] == category fragt, ob eine Ware zur Art gehört. Danach macht map aus jeder Ware eine Zahl, und sum zählt die Zahlen zusammen.
Jede Ware im Warenkorb ist ein Dictionary mit Name, Art, Preis in Cent und Stückzahl. Schreib eine Funktion category_total(items, category), die als Kette ausrechnet, was alle Waren einer Art zusammen kosten: behalten, was zur Art gehört, für jede Ware Preis mal Stückzahl, zusammenzählen. Die Prüfung ruft sie unter anderem mit diesem Warenkorb auf:
items = [ {'name': 'Heft', 'category': 'Schreibwaren', 'price': 120, 'count': 5}, {'name': 'Stift', 'category': 'Schreibwaren', 'price': 80, 'count': 10}, {'name': 'Apfel', 'category': 'Obst', 'price': 50, 'count': 6}, ]
category_total(items, 'Obst') ergibt 300, category_total(items, 'Schreibwaren') ergibt 1400. Gibt es keine Ware der Art, ergibt sie 0.
Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?
item['category'] == category fragt, ob eine Ware zur Art gehört. Danach macht map aus jeder Ware eine Zahl, und sum zählt die Zahlen zusammen.
Jede Ware im Warenkorb ist ein Objekt mit Name, Art, Preis in Cent und Stückzahl. Schreib eine Funktion categoryTotal(items, category), die als Kette ausrechnet, was alle Waren einer Art zusammen kosten: behalten, was zur Art gehört, für jede Ware Preis mal Stückzahl, zusammenzählen. Die Prüfung ruft sie unter anderem mit diesem Warenkorb auf:
const items = [ { name: 'Heft', category: 'Schreibwaren', price: 120, count: 5 }, { name: 'Stift', category: 'Schreibwaren', price: 80, count: 10 }, { name: 'Apfel', category: 'Obst', price: 50, count: 6 }, ];
categoryTotal(items, 'Obst') ergibt 300, categoryTotal(items, 'Schreibwaren') ergibt 1400. Gibt es keine Ware der Art, ergibt sie 0.
Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?
item.category === category fragt, ob eine Ware zur Art gehört. Danach macht map aus jeder Ware eine Zahl, und reduce mit dem Startwert 0 zählt die Zahlen zusammen.
Diese Funktion zählt die Quadrate der ungeraden Zahlen einer Liste zusammen, mit einer Schleife:
def odd_square_sum(numbers) total = 0 numbers.each do |n| total += n * n if n.odd? end total end
odd_square_sum([1, 2, 3]) ergibt 10, denn 1 · 1 + 3 · 3 = 10. Schreib sie als Kette, ohne Schleife: mit select, map und sum. each, for und times sind abgeschaltet.
Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?
Jede der drei Antworten ist ein Schritt der Kette: numbers.select { … }.map { … }.sum.
Diese Funktion zählt die Quadrate der ungeraden Zahlen einer Liste zusammen, mit einer Schleife:
def odd_square_sum(numbers): total = 0 for n in numbers: if n % 2 == 1: total += n * n return total
odd_square_sum([1, 2, 3]) ergibt 10, denn 1 · 1 + 3 · 3 = 10. Schreib sie als Kette, ohne Schleife: mit filter, map und sum, oder mit sum und einer Comprehension. Eine Schleife mit for kann die Prüfung nicht abschalten; die Kata ist erst gelöst, wenn keine mehr dasteht.
Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?
Jede der drei Antworten ist ein Schritt: filter(…), darum map(…), darum sum(…). Von innen nach außen gelesen ist es die Reihenfolge der Schleife.
Diese Funktion zählt die Quadrate der ungeraden Zahlen einer Liste zusammen, mit einer Schleife:
function oddSquareSum(numbers) { let total = 0; numbers.forEach(n => { if (n % 2 !== 0) { total += n * n; } }); return total; }
oddSquareSum([1, 2, 3]) ergibt 10, denn 1 · 1 + 3 · 3 = 10. Schreib sie als Kette, ohne Schleife: mit filter, map und reduce. forEach ist abgeschaltet; eine Schleife mit for kann die Prüfung nicht abschalten, die Kata ist aber erst gelöst, wenn keine mehr dasteht.
Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?
Jede der drei Antworten ist ein Schritt der Kette: numbers.filter(…).map(…).reduce(…, 0).
Nicht jede Schleife wird als Kette besser. map, filter und reduce
gehen immer die ganze Liste durch, und jedes Element wird für sich
behandelt; eine Schleife kann aufhören, sobald sie gefunden hat, was sie
sucht.
Ein Konto steht am Anfang bei balance Cent. Jede Buchung in bookings ist ein Betrag: positiv für Geld, das hereinkommt, negativ für Geld, das hinausgeht. Schreib eine Funktion first_overdraft(balance, bookings), die den Index der ersten Buchung zurückgibt, nach der das Konto im Minus ist, oder -1, wenn es nie ins Minus geht: first_overdraft(100, [-30, -50, -40, 200]) ergibt 2, denn nach den Buchungen steht das Konto bei 70, 20 und −20. Hier ist eine Schleife erlaubt, und sie ist klarer: Sie kann aufhören, sobald sie die Buchung gefunden hat.
Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.
bookings.each_with_index do |amount, i| … end gibt dir jede Buchung mit ihrem Index. Der Stand ändert sich mit jeder Buchung, und return i hört auf, sobald er unter 0 ist; nach der Schleife kommt -1.
Ein Konto steht am Anfang bei balance Cent. Jede Buchung in bookings ist ein Betrag: positiv für Geld, das hereinkommt, negativ für Geld, das hinausgeht. Schreib eine Funktion first_overdraft(balance, bookings), die den Index der ersten Buchung zurückgibt, nach der das Konto im Minus ist, oder -1, wenn es nie ins Minus geht: first_overdraft(100, [-30, -50, -40, 200]) ergibt 2, denn nach den Buchungen steht das Konto bei 70, 20 und −20. Hier ist eine Schleife erlaubt, und sie ist klarer: Sie kann aufhören, sobald sie die Buchung gefunden hat.
Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.
for i, amount in enumerate(bookings): gibt dir jede Buchung mit ihrem Index. Der Stand ändert sich mit jeder Buchung, und return i hört auf, sobald er unter 0 ist; nach der Schleife kommt return -1.
Ein Konto steht am Anfang bei balance Cent. Jede Buchung in bookings ist ein Betrag: positiv für Geld, das hereinkommt, negativ für Geld, das hinausgeht. Schreib eine Funktion firstOverdraft(balance, bookings), die den Index der ersten Buchung zurückgibt, nach der das Konto im Minus ist, oder -1, wenn es nie ins Minus geht: firstOverdraft(100, [-30, -50, -40, 200]) ergibt 2, denn nach den Buchungen steht das Konto bei 70, 20 und −20. Hier ist eine Schleife erlaubt, und sie ist klarer: Sie kann aufhören, sobald sie die Buchung gefunden hat.
Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.
for (let i = 0; i < bookings.length; i++) gibt dir jeden Index. Der Stand ändert sich mit jeder Buchung, und return i; hört auf, sobald er unter 0 ist; nach der Schleife kommt return -1;.
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Mit reduce geht es auch, aber das Ergebnis bis hierher muss zwei Werte
tragen, den Kontostand und den Index, an dem er zum ersten Mal unter 0 fiel,
und es läuft bis zum Ende der Liste, auch wenn längst alles klar ist:
def first_overdraft(balance, bookings) bookings.each_with_index.reduce([balance, -1]) do |(total, found), (amount, i)| total += amount found = i if found == -1 && total < 0 [total, found] end[1] end
from functools import reduce def first_overdraft(balance, bookings): def step(state, pair): total, found = state i, amount = pair total += amount if found == -1 and total < 0: found = i return (total, found) return reduce(step, enumerate(bookings), (balance, -1))[1]
function firstOverdraft(balance, bookings) { return bookings.reduce(([total, found], amount, i) => { total += amount; if (found === -1 && total < 0) { found = i; } return [total, found]; }, [balance, -1])[1]; }
Die Schleife mit einem return mittendrin sagt dasselbe in weniger
Zeilen, und man sieht, wann sie aufhört. Eine Kette passt, wo jedes Element
für sich umgewandelt, behalten oder dazugerechnet wird; wo die Elemente
voneinander abhängen und es auf den Augenblick ankommt, ist eine Schleife
oft klarer.
Ein Extra für Schnelle: Die Seite ist ohne dieses Kapitel vollständig.
Zwei Katas, die nicht mitzählen: zwei Funktionen zu einer verbinden, und
map selbst schreiben.
Schreib eine Funktion compose(f, g), die eine neue Funktion zurückgibt: Sie wendet erst g an und dann f auf das Ergebnis, wie f ∘ g in der Mathematik. compose(->(x) { x + 1 }, ->(x) { x * 10 }).call(5) ergibt 51: erst 5 · 10 = 50, dann 50 + 1. Ruby hat dafür f << g fertig; schreib es hier selbst.
Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?
Wie make_adder gibt compose ein Lambda zurück; darin darfst du f und g aufrufen.
Schreib eine Funktion compose(f, g), die eine neue Funktion zurückgibt: Sie wendet erst g an und dann f auf das Ergebnis, wie f ∘ g in der Mathematik. compose(lambda x: x + 1, lambda x: x * 10)(5) ergibt 51: erst 5 · 10 = 50, dann 50 + 1.
Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?
Wie make_adder gibt compose ein Lambda zurück; darin darfst du f und g aufrufen.
Schreib eine Funktion compose(f, g), die eine neue Funktion zurückgibt: Sie wendet erst g an und dann f auf das Ergebnis, wie f ∘ g in der Mathematik. compose(x => x + 1, x => x * 10)(5) ergibt 51: erst 5 · 10 = 50, dann 50 + 1.
Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?
Wie makeAdder gibt compose eine Arrow Function zurück; darin darfst du f und g aufrufen.
Schreib map selbst: my_map(items, f) gibt eine neue Liste zurück, in der f auf jedes Element angewendet ist, my_map([1, 2, 3], ->(x) { x * 2 }) ergibt [2, 4, 6]. Die Liste, die die Funktion bekommt, bleibt, wie sie ist. Ohne map, mit einer Schleife oder rekursiv wie McCarthys maplist.
Mit einer Schleife: eine leere Liste, in die für jedes Element f.call(item) kommt.
Rekursiv wie McCarthy: Die leere Liste gibt die leere Liste. Sonst ist das Ergebnis f vom ersten Element, und dahinter my_map vom Rest, items[1..].
Schreib map selbst: my_map(items, f) gibt eine neue Liste zurück, in der f auf jedes Element angewendet ist, my_map([1, 2, 3], lambda x: x * 2) ergibt [2, 4, 6]. Die Liste, die die Funktion bekommt, bleibt, wie sie ist. Ohne map, mit einer Schleife oder rekursiv wie McCarthys maplist. Eine List Comprehension wäre auch ein map; die Prüfung kann sie nicht abschalten, schreib die Schleife trotzdem selbst.
Mit einer Schleife: eine leere Liste, in die für jedes Element f(item) kommt.
Rekursiv wie McCarthy: Die leere Liste gibt die leere Liste. Sonst ist das Ergebnis [f(items[0])], und dahinter my_map vom Rest, items[1:].
Schreib map selbst: myMap(items, f) gibt eine neue Liste zurück, in der f auf jedes Element angewendet ist, myMap([1, 2, 3], x => x * 2) ergibt [2, 4, 6]. Die Liste, die die Funktion bekommt, bleibt, wie sie ist. Ohne map, mit einer Schleife oder rekursiv wie McCarthys maplist.
Mit einer Schleife: eine leere Liste, in die für jedes Element f(item) kommt.
Rekursiv wie McCarthy: Die leere Liste gibt die leere Liste. Sonst ist das Ergebnis [f(items[0])], und dahinter myMap vom Rest, items.slice(1).