ÜbungBaut auf Katas: Dictionaries auf
icon: /images/icons/lambda.svg kind: uebung duration: 2 Doppelstunden languages: [ruby, python, javascript] idea: Funktionen weitergeben, Listen umwandeln, sieben und zusammenfalten curriculum: "Oberstufe: Vertiefung Applikative Programmierung (funktional: Funktionen als Werte, map, filter, reduce)" topics: [funktional, listen] requires: [kata-dicts]

Katas: Funktionen als Werte

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.

1. Eine Funktion als Wert

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.

Vermutung 1Zweimal quadrierenEinstieg

Was gibt puts twice(->(x) { x * x }, 3) aus?

Vermutung 1Zweimal quadrierenEinstieg

Was gibt print(twice(lambda x: x * x, 3)) aus?

Vermutung 1Zweimal quadrierenEinstieg

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.

Aufgabe 2n-mal anwendenEinstieg

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.

Tipp

Von Hand: apply_n(->(x) { x * 2 }, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.

Nächster Tipp

Eine Schleife, die n-mal läuft, n.times do … end; in ihr wird x zu f.call(x).

Aufgabe 2n-mal anwendenEinstieg

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.

Tipp

Von Hand: apply_n(lambda x: x * 2, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.

Nächster Tipp

Eine Schleife, die n-mal läuft, for _ in range(n):; in ihr wird x zu f(x).

Aufgabe 2n-mal anwendenEinstieg

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.

Tipp

Von Hand: applyN(x => x * 2, 3, 1) rechnet 1, 2, 4, 8. Jeder Aufruf von f bekommt das Ergebnis des vorigen.

Nächster Tipp

Eine Schleife, die n-mal läuft, for (let i = 0; i < n; i++); in ihr wird x zu f(x).

Eine Funktion, die eine Funktion baut

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.

Aufgabe 3Eine Funktion zum Multiplizieren bauenÜbung

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.

Tipp

Was gibt make_multiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit .call(7).

Nächster Tipp

make_adder oben baut so eine Funktion. Hier multipliziert sie, und das n darf im Lambda stehen.

Aufgabe 3Eine Funktion zum Multiplizieren bauenÜbung

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.

Tipp

Was gibt make_multiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit (7).

Nächster Tipp

make_adder oben baut so eine Funktion. Hier multipliziert sie, und das n darf im Lambda stehen.

Aufgabe 3Eine Funktion zum Multiplizieren bauenÜbung

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.

Tipp

Was gibt makeMultiplier(3) zurück? Keine Zahl, sondern eine Funktion, die ihre Zahl erst später bekommt, mit (7).

Nächster Tipp

makeAdder oben baut so eine Funktion. Hier multipliziert sie, und das n darf in der Arrow Function stehen.

2. map: jedes Element umwandeln

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.

Vermutung 4Was macht map daraus?Einstieg

Was gibt p [3, 1, 2].map { |x| x > 1 } aus?

Was macht map daraus?
Vermutung 4Was macht map daraus?Einstieg

Was gibt print(list(map(lambda x: x > 1, [3, 1, 2]))) aus?

Was macht map daraus?
Vermutung 4Was macht map daraus?Einstieg

Was gibt console.log([3, 1, 2].map(x => x > 1)) aus?

Was macht map daraus?

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.

Aufgabe 5Preise mit SteuerEinstieg

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.

Tipp

Für einen einzigen Preis: 100 * 119 / 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.

Nächster Tipp

prices.map { |price| … } gibt die neue Liste zurück; in den Block kommt die Rechnung für einen Preis.

Aufgabe 5Preise mit SteuerEinstieg

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.

Tipp

Für einen einzigen Preis: 100 * 119 // 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.

Nächster Tipp

list(map(lambda price: …, prices)) gibt die neue Liste zurück; ohne list(…) ist es noch ein map-Objekt.

Aufgabe 5Preise mit SteuerEinstieg

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.

Tipp

Für einen einzigen Preis: 100 * 119 / 100 ist 119. Dasselbe soll mit jedem Preis der Liste geschehen.

Nächster Tipp

prices.map(price => …) gibt die neue Liste zurück; rechts vom Pfeil steht die Rechnung für einen Preis.

Aufgabe 6WortlängenEinstieg

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.

Tipp

'Igel'.length ist 4. Was soll mit jedem Wort der Liste geschehen?

Nächster Tipp

words.map { |word| … }: In den Block kommt, was aus einem Wort werden soll.

Aufgabe 6WortlängenEinstieg

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.

Tipp

len('Igel') ist 4. Was soll mit jedem Wort der Liste geschehen?

Nächster Tipp

map nimmt auch eine Funktion mit Namen, ohne Klammern dahinter: Welche Funktion gibt die Länge? Und list(…) drumherum.

Aufgabe 6WortlängenEinstieg

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.

Tipp

'Igel'.length ist 4. Was soll mit jedem Wort der Liste geschehen?

Nächster Tipp

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.

3. filter: behalten, was passt

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.

Aufgabe 7Nur die geraden ZahlenEinstieg

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.

Tipp

select fragt jede Zahl: Soll sie bleiben? Der Block gibt dafür true oder false zurück.

Nächster Tipp

x.even? fragt, ob x gerade ist: numbers.select { |x| … }.

Aufgabe 7Nur die geraden ZahlenEinstieg

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.

Tipp

filter fragt jede Zahl: Soll sie bleiben? Das Lambda gibt dafür True oder False zurück.

Nächster Tipp

Gerade ist eine Zahl, wenn beim Teilen durch 2 kein Rest bleibt: x % 2 == 0. Und list(…) drumherum.

Aufgabe 7Nur die geraden ZahlenEinstieg

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.

Tipp

filter fragt jede Zahl: Soll sie bleiben? Die Arrow Function gibt dafür true oder false zurück.

Nächster Tipp

Gerade ist eine Zahl, wenn beim Teilen durch 2 kein Rest bleibt: x % 2 === 0.

Aufgabe 8Wörter mit einem BuchstabenÜbung

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.

Tipp

Der Block bekommt ein Wort und sagt true oder false: Steht letter darin?

Nächster Tipp

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.

Aufgabe 8Wörter mit einem BuchstabenÜbung

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.

Tipp

Das Lambda bekommt ein Wort und sagt True oder False: Steht letter darin?

Nächster Tipp

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.

Aufgabe 8Wörter mit einem BuchstabenÜbung

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.

Tipp

Die Funktion bekommt ein Wort und sagt true oder false: Steht letter darin?

Nächster Tipp

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.

Aufgabe 9BestandenÜbung

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.

Tipp

Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.

Nächster Tipp

Ein Block darf ein Paar gleich in zwei Namen auspacken: { |name, points| … }.

Aufgabe 9BestandenÜbung

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.

Tipp

Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.

Nächster Tipp

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.

Aufgabe 9BestandenÜbung

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.

Tipp

Zwei Schritte: Erst behalten, wer bestanden hat; dann aus jedem übrigen Paar den Namen machen.

Nächster Tipp

Eine Arrow Function darf ein Paar gleich in zwei Namen auspacken: ([name, points]) => ….

4. reduce: alles zu einem Wert

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
Vermutung 10Die leere Liste ohne StartwertEinstieg

Was geschieht bei p [].reduce { |sum, x| sum + x }?

Die leere Liste ohne Startwert
Vermutung 10Die leere Liste ohne StartwertEinstieg

Was geschieht bei print(reduce(lambda total, x: total + x, []))?

Die leere Liste ohne Startwert
Vermutung 10Die leere Liste ohne StartwertEinstieg

Was geschieht bei console.log([].reduce((total, x) => total + x))?

Die leere Liste ohne Startwert

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.

Aufgabe 11Das ProduktEinstieg

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.

Tipp

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?

Nächster Tipp

numbers.reduce(…) { |result, x| result * x }: Was gehört in die Klammer?

Aufgabe 11Das ProduktEinstieg

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.

Tipp

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?

Nächster Tipp

reduce(lambda result, x: result * x, numbers, …): Was gehört als letztes Argument hinein?

Aufgabe 11Das ProduktEinstieg

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.

Tipp

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?

Nächster Tipp

numbers.reduce((result, x) => result * x, …): Was gehört als letztes Argument hinein?

Aufgabe 12Das längste WortÜbung

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.

Tipp

Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?

Nächster Tipp

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.

Aufgabe 12Das längste WortÜbung

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.

Tipp

Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?

Nächster Tipp

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.

Aufgabe 12Das längste WortÜbung

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.

Tipp

Das Ergebnis bis hierher ist das längste Wort bis hierher. Was soll mit ihm geschehen, wenn das nächste Wort kommt?

Nächster Tipp

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.

Aufgabe 13Wer hat wie viel bezahlt?Knobelei

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.

Tipp

Der Startwert ist ein leerer Hash, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?

Nächster Tipp

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)|.

Nächster Tipp

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
Aufgabe 13Wer hat wie viel bezahlt?Knobelei

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}.

Tipp

Der Startwert ist ein leeres Dictionary, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?

Nächster Tipp

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.

Nächster Tipp

Ein Gerüst:

def add(sums, payment):
    name, amount = payment
    # hier: den Betrag eintragen
    return sums

def totals(payments):
    return reduce(add, payments, {})
Aufgabe 13Wer hat wie viel bezahlt?Knobelei

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.

Tipp

Der Startwert ist ein leeres Objekt, {}. Was soll bei der ersten Zahlung, ['Ada', 300], in ihm stehen, und was danach bei ['Alan', 200]?

Nächster Tipp

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]) => ….

Nächster Tipp

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;
}, {});

5. Ketten

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.

Ausprobieren: Vertausch in der Kette die ersten beiden Schritte, so dass zuerst umgewandelt und dann behalten wird. Lies die Fehlermeldung: Was ist eine Bestellung nach dem Umwandeln geworden, und warum kann der zweite Schritt nicht mehr nach der Stückzahl fragen?
Aufgabe 14Was eine Art Ware kostetKnobelei

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.

Tipp

Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?

Nächster Tipp

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.

Aufgabe 14Was eine Art Ware kostetKnobelei

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.

Tipp

Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?

Nächster Tipp

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.

Aufgabe 14Was eine Art Ware kostetKnobelei

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.

Tipp

Drei Schritte, wie bei den Bestellungen oben: Welche Waren bleiben? Was wird aus jeder? Wie kommt aus der Liste eine Zahl?

Nächster Tipp

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.

Eine Schleife umschreiben

Aufgabe 15Eine Schleife als KetteÜbung

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.

Tipp

Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?

Nächster Tipp

Jede der drei Antworten ist ein Schritt der Kette: numbers.select { … }.map { … }.sum.

Aufgabe 15Eine Schleife als KetteÜbung

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.

Tipp

Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?

Nächster Tipp

Jede der drei Antworten ist ein Schritt: filter(…), darum map(…), darum sum(…). Von innen nach außen gelesen ist es die Reihenfolge der Schleife.

Aufgabe 15Eine Schleife als KetteÜbung

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.

Tipp

Lies die Schleife als Frage: Welche Zahlen zählen? Was wird aus jeder? Was geschieht am Ende mit allen?

Nächster Tipp

Jede der drei Antworten ist ein Schritt der Kette: numbers.filter(…).map(…).reduce(…, 0).

Wo die Schleife klarer bleibt

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.

Aufgabe 16Zum ersten Mal im MinusKnobelei

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.

Tipp

Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.

Nächster Tipp

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.

Aufgabe 16Zum ersten Mal im MinusKnobelei

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.

Tipp

Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.

Nächster Tipp

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.

Aufgabe 16Zum ersten Mal im MinusKnobelei

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.

Tipp

Von Hand: Rechne den Kontostand nach jeder Buchung aus und schau, wann er zum ersten Mal unter 0 ist.

Nächster Tipp

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.

6. Extras Extra

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.

Aufgabe 17Zwei Funktionen zu einerKnobeleiExtra

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.

Tipp

Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?

Nächster Tipp

Wie make_adder gibt compose ein Lambda zurück; darin darfst du f und g aufrufen.

Aufgabe 17Zwei Funktionen zu einerKnobeleiExtra

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.

Tipp

Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?

Nächster Tipp

Wie make_adder gibt compose ein Lambda zurück; darin darfst du f und g aufrufen.

Aufgabe 17Zwei Funktionen zu einerKnobeleiExtra

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.

Tipp

Was gibt compose zurück? Eine Funktion, die ein x bekommt. Was rechnet sie mit diesem x, Schritt für Schritt?

Nächster Tipp

Wie makeAdder gibt compose eine Arrow Function zurück; darin darfst du f und g aufrufen.

Aufgabe 18map selbst schreibenKnobeleiExtra

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.

Tipp

Mit einer Schleife: eine leere Liste, in die für jedes Element f.call(item) kommt.

Nächster Tipp

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..].

Aufgabe 18map selbst schreibenKnobeleiExtra

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.

Tipp

Mit einer Schleife: eine leere Liste, in die für jedes Element f(item) kommt.

Nächster Tipp

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:].

Aufgabe 18map selbst schreibenKnobeleiExtra

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.

Tipp

Mit einer Schleife: eine leere Liste, in die für jedes Element f(item) kommt.

Nächster Tipp

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).