Hier sollte zusammen mit Rüdeger Baumann der Einsatz von SQUEAK/SMALLTALK in der Schule erörtert werden. Statt über Smalltalk zu berichten, erscheint hier zunächst ein Artikel über die Entwicklung von JavaScript und seine funktionalen Sprachelemente.
Ist ES6 das neue Smalltalk?


ES6 – Die Javascript-Revolution

Atwood's Law:
any application that can be written in JavaScript,
will eventually be written in JavaScript.
blog.codinghorror.com/the-principle-of-least-power
Tim Berners-Lee's Law: → "The Rule of Least Power"

49 „Programmiersprachen bzw. –systeme, die in der Schule eingesetzt wurden“ hat Bernhard Koerber seiner „kleinen Geschichte der Programmiersprachen in der Schule“ in einer Übersicht aufgezählt. Dabei ist eine Sprache übersehen worden, die bereits mehrfach in LOG IN behandelt wurde, die jeder Schüler zumindest passiv benutzt und die im Ranking der Programmiersprachen, die in Stellenanzeigen in den USA, GB und Australien am meisten gesucht werden und noch vor JAVA auf Platz 1 steht: die Lingua franca des Internets.

Die Tabelleneinträge seien hier nachgeholt:
Name: LiveScript, JavaScript, ECMA-Script3, ES5, ES6/ES2015.
Bedeutung des Namens: Der Name ist eine reine Marketingentscheidung.
Jahr der Einführung: 1996
Entwickler: Brendan Eich.
Merkmale: Prototypbasiertes Objektsystem mit funktionalen Elementen


JavaScript war von Anfang an als funktionale Programmiersprache angelegt. Im April 1995 wurde Brendan Eich von Netscape mit dem Argument angeworben, "den Browser um (das funktionale) Scheme zu erweitern" [Hug, 2012]. "Um alle Zweifler ... zu überzeugen", schreibt Eich, "musste ich innerhalb von zehn Tagen eine Demoversion erstellen Ich arbeitete Tag und Nacht und baute daher ganz unvermeidlich ein paar Designfehler in die Sprache ein (die aus der Evolution von LISP übernommen wurden)".

Registermaschine
Bild 1: → Registermaschine
4 Stifte gleichzeitig
Bild 2: → Turtelgrafik mit drei Stiften
Quicksort funktional
Bild 3: → Quicksort-Demo mit ES6
Für nicht ganz aktuelle Browser: → Quicksort-Demo mit ES5

"JavaScript wird eines Tages untergehen", sagt Bredan Eich (vergl. Seibel, 2010), plädiert aber zugleich für eine ständige Verbesserungen seiner Programmiersprache.

In den letzten 20 Jahren ist Java-Script ständig weiterentwickelt worden. Mit dem in LOG-IN 5 '97 beschriebenen Java-Script haben die aktuellen Versionen ES5 und ES6 nur noch wenig zu tun. ... WEITERES FOLGT ...

Einige Ausdrucksmöglichkeiten von JavaScript sollen anhand dreier Beispielprogramme aus dem Informatikunterricht dargestellt werden.

Registermaschine

Turtlegrafik

Quicksort

Mit HTML5 hat sich das Erstellen einer validen HTML-Seite sehr vereinfacht. Bild 1 zeigt ein mögliches Ergebnis mit Ein- und Ausgabemöglichkeit. Aber es geht noch viel einfacher, wenn man auf HTML ganz verzichtet und einfach die Funktionsdeklaration im <script>-Attribut in eine Browser- oder Node.js-Konsole eingibt.
Die Quicksort-Funktion verwendet nur einen ternären Operator und kommt Anweisungen und Variablen aus. Smalltalk eben.



Vererbung: klassisch oder modern?

JavaScript ungleich Java

"Java verhält sich zu JavaScript wie Ham zu Hamster", steht es in [MEH, 2013]. Anderere Vergleiche im Internet sind "car und carpet" oder "Ei und Eiffelturm". Die meisten der zahlreichen englisch- oder deutschsprachigen Büchern zu JavaScript ...
Array.prototype.mmap = function (fn) {return this.reduce((a,b) => a.concat(fn(b)), []);};

Emoji-Demonstration
Bild 4: Die Funktionsweise von map, filter und reduce demonstriert mit Emojis
von Joey Devilla mit einer Implementation in Swift

Die Array-Methode reduce gibt es seit ECMASript 5.1.
["🍔", "🍕", "🍰"].reduce(
    (akku, x) => akku+"💩 ", '');
// "💩 💩 💩 "

map, reduce, filter

map, reduce, filter (bildeab, reduziere, filtere) sind wichtige Funktionen für das Paradigma der funktionalen Programmierung.
Diese Funktionen können zu Übungszwecken im Unterricht mit dem ternären Vergleichsoperator "[test] ?[ja-Zweig] :[nein-Zweig]" formuliert werden.

{// eine vereinfachte Version von reduce
const reduziere = ([kopf, ...rest], fn, akku) =>
    typeof kopf === 'undefined'
    ? akku
    : reduziere(rest, fn, fn(akku, kopf));

reduziere([2,3,7], (a, b) => a * b, 1); // 42
// bildeab (map) und filtere mittels reduziere

const bildeab = (arr, fn) =>
    reduziere(arr, (a, b) => a.concat(fn(b)), []);

bildeab([5,6,7], (x) => x*x); // [25, 36, 49]

const filtere = (arr, fn) => reduziere(arr,
   (a, b) => fn(b) ? a.concat(b) : a, []);

filtere([5,55,33,4], (x) => x > 10); // [55, 33]

reduziere(["🍔", "🍕", "🍰", '🍟'], (akku, x) =>
     akku+"💩\n", 'Am Ende kommt raus:\n');

//Mit der Pfeilfunktion filtere lässt sich nun quicksort im deklarativen Stil formulieren.

const quicksort = ([kopf, ...rest]) =>
    kopf === undefined // Liste leer?
    ? []               // ja, dann []
    : quicksort(filtere(rest, (a) => a < kopf))
     .concat(kopf,
      quicksort(filtere(rest, (a) => a >= kopf))
    );

let liste = [5,1,3,1];
quicksort(liste); // [1, 1,3, 5]
liste //  [5,1,3,1] bleibt unverändert
}
Plätten (engl. flatten) – eine Reihung umdrehen:
var plaetten =  (reihung) => 
      reihung.reduce((akku, element) => 
        Array.isArray(element) 
        ? akku.concat(plaetten(element)) 
        : akku.concat(element), 
       []);
plaetten([1,2,3,,,6,,[[[5,[7]]]],[9,'ende']]);
// [1, 2, 3, 6, 5, 7, 9, "ende"]

Funktional: Eine Reihung umdrehen

const liste1 = [13, 8, 5, 3, 2, 1, 1];
const liste2 = liste1.reverse();
console.log(liste2, liste1);
// [1, 1, 2, 3, 5, 8, 13], [1, 1, 2, 3, 5, 8, 13]
liste1 ist ein listenähnliches Objekt vom Typ Array mit den ersten sieben Fibonaccizahlen.
Array.prototype.drehUm = function () { return this.length < 2 
    ? this 
    : this.slice(this.length-1).concat(this.slice(0, this.length-1).drehUm())};

let prim = [2, 3, 5, 7, 11];
prim.drehUm(); // [11, 7, 5, 3, 2]
prim // [2, 3, 5, 7, 11]

Fazit – Wozu JavaScript?

"Worin besteht der bildende Wert textbasierter Programmierung, also der Möglichkeit, Syntaxfehler machen zu können, wenn inhaltlich alle informatischen Konzepte, aber auch Standardanwendungsbereiche und -aufgaben mit grafischen Programmierwerkzeugen (wie z.B. BYOB) realisiert werden können? Ist die mit der Textbasierung verbundene Frustrationsrate gerechtfertigt, hat Syntax ihren eigenen Wert?", fragen Modrow, Mönig und Strecker in "Wozu JAVA?" in LOG IN Heft Nr. 168 (2011)


Böttcher, K.: JAVA jetzt – adieu PASCAL. Zur Evolution des Informatikunterrichts. In: LOG IN 17 (1997) Heft 6, S. 38–45

Crockford, D., Das Beste an JavaScript, Köln: O'Reilly, 2008

Hughes-Croucher, T; Wilson, S.: Einführung in Node.js, Köln: O'Reilly, 2012, S. XI

Koerber, B.: Haben Sie noch ELAN? Eine kleine Geschichte der Programmiersprachen in der Schule. In: LOG IN, 35. Jg. (2015), Nr. 181/182, S. 10–28

Seibel, P.: Coders at Work. Bedeutende Programmierer und ihre Erfolgsgeschichten. mitp-Verlag, 2011







Teil 2


ES6 Beispiele

Beispiel 1: Funktionsabschlüsse (Closures)

In der Fabrikfunktion "erzeugePerson" sind zehn Variablen zu unterscheiden, fünf im Funktionsabschluss (vname, name, ort, gibOrt und gibNamen) und fünf Eigenschaften des zurückgegebenen Objekts (vname, name, alter, gibOrt, gibNamen) . Die Variablen name und ort bleiben auch nach dem Beenden der Funktion erzeugePerson in einem Funktionsabschluss (Closure) erhalten und zwar für jedes Personenobjekt getrennt. Das gilt allerdings nur solange wie es die privilegierten Funktionen (gibOrt, gibNamen) gibt , die auf sie zugreifen. Mit der Anweisung "person1.gibNamen = function () {return this.name;};" wäre die Variable name im Funktionsabschluss nicht mehr erreichbar (und würde bei einer Speicherbereinigung entfernt werden), sondern nur die Eigenschaft name.

let erzeugePerson = (vname, name) => {
  let ort = "Buxtehude";
  let gibOrt = () => ort;
  let gibNamen = () => name;
  return {
    "vname": vname,
    "name": name,
    "alter": 0,
    "gibOrt": gibOrt,
    "gibNamen": gibNamen
  };
};

let person1 = erzeugePerson("Grete", "Meier");
let person2 = erzeugePerson("Peter", "Meier");

// einige Tests und ihre Rückgabewerte
console.log(              // Rückgabe:
person1.name,             // 'Meier'
person1.name = "Müller",  // 'Müller'
person1.name,             // 'Müller'
person2.name,             // 'Meier'
person1.gibNamen(),       // 'Meier'
person1.ort = "Berlin",   // 'Berlin'
person1.ort,              // 'Berlin'
person2.ort,              // undefined
person1.gibOrt()          // 'Buxtehude'
);

person1.getName = function () {
    return this.vname + ' ' + this.name;
};

let kind1 = {vname: 'Renate'}

Object.setPrototypeOf(kind1, person1);

kind1.getName();          // 'Renate Müller'

OLOO (objects linked to other objects) statt OOP: Beim Objekt kind1 erkennt man, wie prototypische Verberbung (behavior delegation) ohne Klassen funktioniert. kind1 wird durch ein Literal erzeugt und erbt vom Prototyp person1. Da kind1 keine Methode getName besitzt, wird in der Prototypenkette diese Funktion beim Objekt person1 gefunden. Das this-Objekt von getName ist kind1 und nicht etwa person1. Deshalb ist this.vname "Renate" und this.name ("Müller") wird beim Prototyp gefunden.

Beispiel 2: Pfeilfunktionen und Funktionen als Methoden

Bei den neuen Pfeilfunktionen (wie g) erfolgt die Bindung an das this-Objekt bei der Definition und kann nicht mehr geändert werden, während bei der "normalen" Funktion (wie f) this das Objekt referenziert, von dem aus die Funktion ausgeführt wird.

var person = {};
var ID = 42; //globale Variable
const f = function () {
    return this.ID;
};
const g = () => this.ID; // this === Window

person.ID = Math.random(); // 0.165700
person.gibID = f;
person.getID = g;
person.gibID(); // 0.165700
person.getID(); // 42


Merke:

Eine Pfeilfunktion hat eine kürzere Syntax, besitzt kein eigenes this-Objekt und kann nicht mittels new als Konstruktorfunktion verwendet werden. Verwende Pfeilfunktionen für funktionale Programmierung.

Verwende mit function deklarierte Funktionen als Methoden. Dabei ergibt sich der Wert, der durch this referenziert wird, dynamisch aus dem Aufrufkontext.


Beispiel 3: Verkettung von Funktionen

const erzeugeMult = (faktor) => (x) => faktor * x;
const mwSteuer19 = erzeugeMult(1.19);
mwSteuer19(200); // 238

const erzeugeAdd = (a) => (x) => a +x;
const mitPorto5 = erzeugeAdd(5);
let endPreis = (x) => mitPorto5(mwSteuer19(x));
endPreis(200); // 243

let verkette = (f1, f2) => {return (x) => f1(f2(x))};
// kurz: verkette = (f1, f2) => x => f1(f2(x));

endPreis = verkette(mitPorto5, mwSteuer19);
endPreis(400); // 481

Beispiel 4: Rest-Operator und destrukturierende Zuweisung

Der Rest-Operator (...) erleichtert funktionales Programmieren mit JavaScript außerordentlich. Bei der print-Funktion tritt er zweimal auf. Die Paramterliste wird in der Reihung args gesammelt und die Elemente dieser Reihung werden einzelnd als Parameter in die log-Funktion eingesetzt. Die destrukturierende Zuweisung erspart eine temporäre Variable beim Tausch von x und y und vereinfacht die Aufteilung

const add = (...args) =>
    args.reduce((a, b) => a+b);
add(9, 10, 11, 12); // 42

const print = (...args) =>
    console.log(...args);

let x = 5, y = 8;
[x, y] = [y, x];
print(x, y); // 8 5

let liste = [9, 10, 11];
[kopf, ...rest] = liste;
print(kopf, rest, liste); // 9 [10, 11] [9, 10, 11]


Beispiel 5.1: Vorbelegte Parameter und Endrekursion

Will man endrekursive Funktionen schreiben, dann benötigt man meistens einen oder mehrere Akkumulatoren (hier vorbelegte Paramerter) zum Speichern von Zwischenergebnissen. Endrekursion wird von künftigen Browsern unterstützt werden.

const fakultaet = (n, akku = 1) =>
    n <= 1
    ? akku
    : fakultaet(n - 1, n * akku);

fakultaet(69) // 1.7112245242814127e+98

const fibonacci = (n, f1 = 1, f2 = 1) =>
     n === 1
     ? f1
     : fibonacci(n-1, f2, f1 + f2);
fibonacci(69) // 117669030460994

Problem 25 des Euler Projekts:
"Was ist der Index des ersten Glieds der Fibonacci-Folge, das 1000 Stellen hat?"
fibonacci = (max, n =1, f1 = 1n, f2 = 1n) =>
     f1 >= max
     ? n 
     : fibonacci(max, n+1, f2, f1 + f2);

fibonacci(10n**999n) //4782

let ggT = (a, b) => b === 0 ? a : ggT(b, a % b);

ggT = (a, b) => {
    console.log(a);
    return b == 0 ? a : ggT(b, a % b);
};

ggT(987,610) // ?

ggT = (a, b, akku = `ggT(${a}, ${b}) = `) => {
    return b == 0 
    ? akku + a 
    : ggT(b, a % b, akku + `ggT(${a}, ${b}) = ` );
};

ggT(55, 34) = ggT(34, 21) = ggT(21, 13) = ggT(13, 8) = ggT(8, 5) = ggT(5, 3) = ggT(3, 2) = ggT(2, 1) = 1

Beispiel 5.2: BigInteger

"Welches ist das erste 1000-stellige Folgenglied der Fibonaccigfolge?", fragt Rüdeger Baumann im (letzten) LOG IN-Heft 197/198 (Project Euler 025). In JavaScript benötigt die Lösung in der endrekursiven Fassung nur einen bedingten Operator (?:), der einen Ausdruck zurückgibt und keine Anweisung (Statement) erfordert.

const maxFibonacci = (max, n = 1, f1 = 1n, f2 = 1n) => {
    //console.log(n,  f1)
     return f1 >= max
         ? [n, f1] 
         : maxFibonacci(max, n+1, f2, f1 + f2);
}
maxFibonacci(10n**999n) //=> 4782

const modInverse = (z, n) => {
    // Nur für (positive) BigInteger!
    let ggT = (a, b) => b === 0n ? a : ggT(b, a % b);
    let modInv = (z, n, s = 0n, t = 1n) =>
        (z === 1n)
        ? t
        : modInv(n % z, z, t, s - (n / z) * t);
        
    if (ggT(z, n) !== 1n) {
        console.log("Es gibt keinen modularen Kehrwert!");
        return 0n;
    }
    let kehrwert = modInv(z, n);
    return (kehrwert > 0)
           ? kehrwert
           : kehrwert + n
}

Primzahlenquelle: https://bigprimes.org
1619
1627
387888747401526335715080912311
726433696642102454440335213821
var phi = (primzahl1, primzahl2) => {
    return {
            n: primzahl1 * primzahl2,
            modul: (primzahl1 - 1n) * (primzahl2 - 1n)   
           };
    };

Beispiel 6: Quicksort funktional

Mit den neuen Sprachelementen von ES6 lässt sich Quicksort funktional und damit deklarativ ohne zusätzliche Variablen und ohne Seiteneffekte programmieren.

const quicksort = ([kopf, ...rest]) =>
    kopf === undefined // Liste leer?
    ? []               // ja, dann []
    : [].concat(       // sonst:
        quicksort(rest.filter((a) => a < kopf)),
        kopf,
        quicksort(rest.filter((a) => a >= kopf))
    );

quicksort([5,1,3]); // [1,3,5]

// Zeitkomplexität testen:
let test = function (n) {
    "use strict";
    let i, start, reihung = [];
    for ( i = 0; i < n; i += 1 ) {
        reihung.push(Math.random());
    }
    start = Date.now();
    quicksort(reihung); // reihung.sort()
    return (Date.now() - start) ;
};

test(2E6) / test(1E6) // 2.098208770846201

Das passt zur Kostenfunktion T(n) = n*log(n). Aber wie schnell wird sortiert?

test(1E6) // 4776

1000000 Elemente in knapp 5 s. Zum Verleich wird in der Funktion test2 "reihung.sort()" verwendet. sort ist eine eingebaute Arrayfunktion von JavaScript.

test2(1E6) // 5861

Das Ergebnis kann nicht stimmen und den Grund erkennt man bei folgenden Tests:

[3, 1, 12, 5, 1].sort(); // [1, 1, 12, 3, 5]
[3, 1, 12, 5, 1].sort((a, b) => a - b) // [1, 1, 3, 5, 12]
[3, 1, 12, 5, 1].sort((a, b) => b - a) // [12, 5, 3, 1, 1]

Der Array-Methode sort muss eine Vergleichsfunktion als Parameter übergeben werden, ansonsten wird lexikographisch sortiert. Die anonyme Vergleichstfunktion bestimmt im Beispiel, ob die Zahlen auf- oder absteigend sortiert werden. Ein Funktionsaufruf test3(1E6) mit Sortierung nach Größe ergibt damit z. B. 1406 (ms). Auch eine Reihung von Personen kann auf diese Weise sortiert werden, ohne den sort-Algorithmus neu schreiben zu müssen.

[person1, person2].sort((a, b) => a.alter - b.alter);

Beispiel 7: UPN-Terme

let upn = [[[2, 2, "*"], 3, "+"], [2, 4, "+"], "*"];

const plaette = (liste) => liste.reduce( //       *
        (akku, element) => akku.concat(  //     /   \
            Array.isArray(element) ?     //    +     +
            plaette(element) :           //   / \   / \
             element                     //  *   3 2   4
        ),                               // / \
    [] // Startwert für akku               2   2
);

plaette(upn); // [2, 2, "*", 3, "+", 2, 4, "+", "*"]
plaette(upn).join(" "); // "2 2 * 3 + 2 4 + *"

const berechne = (op1, op2, op) => { // Hilfsfunktion
        if (op == "+") return op2 + op1;
        if (op == "-") return op2 - op1;
        if (op == "*") return op2 * op1;
        if (op == "+") return op2 / op1;
    };

berechne(45, 49, "-"); // 4

var berechneUPN = (upnString) =>
        upnString.split(" ")
        .reduce((stapel, element) =>  // stapel als Akkumulator
            ["+", "-", "*", "/"].includes(element) ?
            stapel.concat([berechne(stapel.pop(),  stapel.pop(), element)]) :
            stapel.concat([+element]) // Typwandlung ergibt Zahl
        , [] // beginne mit leerem Stapel
        )[0];

berechneUPN(plaette(upn).join(" ")); // 42
berechneUPN("49 45 - 3 + 2 4 + *") === 42 // true

Beispiel 8: Baum-Rekursion Einführungsbeispiele

Ein schönes Einführungsbeispiel, das sich mit Rekursion einfacher lösen lässt als mit Schleifen, beschreibt M. Haverbeke in Javascript 2. Auflage
Im Browser können die Beispiele sofort ausprobiert werden. Eine deutsche Übersetzung gibt es im dpunkt.verlag

A 8.1 mal 5 plus 3
Finde für eine gegebene Zahl einen Term, der beginnend mit 1 dadurch gebildet wird, dass wiederholt 5 addiert oder mit 3 multipliziert wird.
Für UPN-Terme sieht eine rekursive Lösung so aus:

function finde(ziel, aktuell = 1, upnTerm ="1") {
    if (aktuell === ziel) {
      return upnTerm;
    } else if (aktuell > ziel) { 
      return false;
    } else {
      return finde(ziel, aktuell + 5, upnTerm + " 5 +") ||
             finde(ziel, aktuell * 3, upnTerm + " 3 *");
    }
  }
  
  finde(54) // "1 5 + 3 * 3 *"

Dieses Programm gibt sich damit zufrieden, überhaupt eine Lösung zu finden. Tatsächlich wird die kürzeste Lösung gefunden, wie die SuS mithilfe der Funktion findeAlle rasch erkennen.

  function findeAlle(ziel, aktuell = 1, upnTerm = `${ziel} = 1`) {
    if (aktuell === ziel) {
      console.log(upnTerm);
    } else if (aktuell > ziel) { 
      return false;
    } else {
      return findeAlle(ziel, aktuell + 5, upnTerm + " 5 +") ||
             findeAlle(ziel, aktuell * 3, upnTerm + " 3 *");
    }
  }
  
findeAlle(42)
//42 = 1 3 * 3 * 5 + 3 *
//42 = 1 3 * 3 * 3 * 5 + 5 + 5 +

Auch die Frage, welche natürlichen Zahlen sich auf diese Weise darstellen lassen, kann mit findeBis leicht erkannt werden.

function findeBis(max) {
    if (max === 1) {return;}
    console.log(max, " = ", finde(max));
    findeBis(max -1);
}

findeBis(100);

Für natürliche Zahlen größer als 22 erkennt man sofort, welche Zahlen sich nicht darstellen lassen.

A 8.2 Wechselgeld zählen

Im Klassiker "Struktur und Interpretation von Computerprogrammen" wird das Problem "Wechselgeld zählen"als Einführungsbeispiel für Baumrekursion gewählt.

Wie viele Möglichkeiten gibt es, 1 DM mit den Münzen [50, 10, 5, 2, 1] zu wechseln?

Es wird gleich "eine einfache Lösung als rekursiver Prozedur" angegeben, die als ES6-Pfeilfunktion wg so lauten könnte:

let z; // für später zum testen

let wg = (betrag, muenzarten) => {
z += 1; // Test: z zählt Funktionsaufrufe

    if (betrag === 0) return 1;
    if (betrag < 0 || muenzarten.length === 0) return 0;
    return wg(betrag, muenzarten.slice(1)) + 
           wg(betrag - muenzarten[0], muenzarten);
};

wg(100, [1,2,5,10,50]); // 2498

Können SuS eine solche Lösung selbsttätig finden? Mit den richtigen Leitfragen mag das gelingen.

Kleiner-Bruder-/ kleine-Schwester-Prinzip

Die Aufgabe löse ich gerne. Ist sie einfach, dann nenne ich die Lösung sofort.
Ansonsten finde ich eine Teilaufgabe, die mein kleiner Bruder/ meine kleine Schwester für mich löst, und kann damit dann eine Lösung meiner Aufgabe angeben.

Auf das Problem "Turm von Hanoi" angewendet, kann die Formulierung so lauten: "die n Scheiben lege ich gerne von Turm a nach c, wenn ein kleiner Mönch n-1 Scheiben von Turm a nach b legt. Dann lege ich meine untere Scheibe von a nach c und ein anderer Mönch transportiert die n-1 Scheinen von Turm b nach Turm c."

Auf das Wechselgeldproblem angewendet, stellen sich die Fragen "Wann kann ich sofort eine Lösung angeben?" und "Welche einfacheren Teilaufgaben gibt es?". Eine Teilaufgabe wäre, zu zählen, auf wie viele Arten 1 DM ohne die erste Münzart (50 Pf) gewechselt werden kann. Dann muss noch gezählt werden, auf wieviele Arten mit allen Münzarten gewechselt werden kann. Im Beispiel heißt das, auf wie viele Arten können 50 Pf mit allen Münzarten gewechselt werden, weil die erste Münzart ja mindestens einmal verwendet wird. Zu verdeutlichen bleibt, dass jede Wechselmöglichkeit genau einmal erfasst wird.
Falls der Betrag negativ ist, hat mir mein großer Bruder eine Teilaufgabe gestellt, die zu keiner weiteren Lösung führt. Wenn nur noch eine Münzart vorhanden ist, dann sofort entschieden werden, ob der Betrag ohne Rest ausgegeben werden kann.

Diese Abbruchbedingung führt zu folgender, effizienteren Funktion wg2:

let wg2 = (betrag, muenzarten) => {
z += 1; // z zählt Funktionsaufrufe
    if (betrag < 0) return 0;
    if (muenzarten.length === 1)
        return (betrag % muenzarten[0] === 0) ? 1 : 0;
    return wg2(betrag, muenzarten.slice(1)) + 
           wg2(betrag - muenzarten[0], muenzarten);
};


z = 0; console.log(wg(100, [50, 10, 5, 2, 1]), z);
// 2498 Möglichkeiten bei z = 129545 Funktionsaufrufen
z = 0; console.log(wg2(100, [50, 10, 5, 2, 1]), z);
// 2498 Möglichkeiten bei z = 5355 Funktionsaufrufen

Man spart noch weitere Funktionsaufrufe, wenn vor den rekursiven Aufrufen noch eingefügt wird: if (betrag === 0) return 1. Auch die Reihenfolge der Münzarten spielt eine Rolle, was sofort getestet werden kann.



Crockford, D., Das Beste an JavaScript, Köln: O'Reilly, 2008
Hughes-Croucher, T; Wilson, S.: Einführung in Node.js, Köln: O'Reilly, 2012, S. XI
Seibel, P.: Coders at Work. Bedeutende Programmierer und ihre Erfolgsgeschichten. mitp-Verlag, 2011



Ideen für später


// Man kann gar nicht genug Danke sagen!
const danke = (n) => n > 0
    ? "Danke, " + danke(n - 1)
    : "Danke!";

const fibFolge = (n, reihung = [1, 1]) =>
    reihung[1] + reihung[0] >= n
    ? reihung
    : fibFolge(n, [reihung[0]+reihung[1]].concat(reihung));

fibFolge(34) // [21, 13, 8, 5, 3, 2, 1, 1]
fibFolge(35).reverse() // [1, 1, 2, 3, 5, 8, 13, 21, 34]

/////////////////////////////////////////////////////////////////
const fibMax = (ziel, f1 = 1, f2 = 1) =>
// fibMax ist größte Fibonaccizahl, die nicht größer als ziel ist
     f2 > ziel
     ? f1
     : fibMax(ziel, f2, f1 + f2);

//console.log(fibMax(54), fibMax(55), fibMax(56)) // 34 55 55

const zeckendorf = (zahl, zSequenz = []) => {
// Zeckendorfsequenz von zahl erzeugen
    const max = fibMax(zahl);
    zSequenz = zSequenz.concat(max);
    return max === zahl
         ? zSequenz
         : zeckendorf(zahl - max, zSequenz)
}

zeckendorf(1e6) // [832040, 121393, 46368, 144, 55]
Dazu passt ein NIM-Spiel FIBONACCI-NIM
///////////////////////////////////////////////////
//   Übung: Zahlen mittels Binärbaum sortieren   //

const einfuegen = (element, bbaum) => {
    if (bbaum.inhalt === undefined) {
        bbaum.inhalt = element;
        bbaum.links = {};
        bbaum.rechts = {};
    } else {
        if (element < bbaum.inhalt) {
            einfuegen(element, bbaum.links);
        } else {
            einfuegen(element, bbaum.rechts);
        }
    }
    return bbaum;
};

const erzeugeBaum = (n, bbaum = {}) => {
    let reihung = []; // zur Dokumentation
    for (let i = 0; i < 12; i++) {
        let n = Math.round(Math.random()*90 + 10);
        reihung.push(n); // Doku
        einfuegen(n, bbaum);
    };
    console.log(...reihung); // Dokumentation
    return bbaum;
};

const zeigeBaum = (bbaum, t = '') => {
    if (bbaum.inhalt !== undefined) {
        zeigeBaum(bbaum.rechts, t + ' – ');
        console.log(t + bbaum.inhalt);
        zeigeBaum(bbaum.links, t + ' – ');
    };
};

const traversiereBaum = (bbaum, accu = []) => {
    if (bbaum.inhalt !== undefined) {
        accu = traversiereBaum(bbaum.links, accu);
        accu = accu.concat(bbaum.inhalt);
        accu = traversiereBaum(bbaum.rechts, accu);
    };
    return(accu);
};

let reihung = ["E", "A", "S", "Y", "Q", "U", "E", "S", "T", "I", "O", "N"];
console.log(...reihung);
baum = reihung.reduce((a, b) => einfuegen(b, a), {});

//let baum = erzeugeBaum(12);
zeigeBaum(baum);
console.log(...traversiereBaum(baum));
/* Ausgabe:
38 38 43 26 36 24 95 73 80 65 25 64
 –  –  – 95
 –  –  –  –  – 80
 –  –  –  – 73
 –  –  –  –  – 65
 –  –  –  –  –  – 64
 –  – 43
 – 38
38
 –  – 36
 – 26
 –  –  – 25
 –  – 24
[24, 25, 26, 36, 38, 38, 43, 64, 65, 73, 80, 95]
*/
const groesstesElement = (bbaum) =>
       // gibt es rechten Nachfolger?
    bbaum.rechts.rechts
       // ja, dann such im rechten Teilbaum
    ? groesstesElement(bbaum.rechts)
       // nein, dann Maximum gefunden
    : bbaum.inhalt

groesstesElement(baum.links) // 68

/* Übungen:
1.
   let reihung = ["E", "A", "S", "Y",
                  "Q", "U", "E", "S", "T", "I", "O", "N"];
   let baum2 = reihung.reduce((a, b) => einfuegen(b, a), {});
   a) Skizziere baum2.
   b) Wie sieht der Baum aus, nachdem das Q gelöscht wird?

2.

*/
///////////////////////////////////////////////////////////////

HTML-Code in HTML darstellen

Um HTML-Code auf einer Seite darzustellen, müssen die HTML-eigenen Zeichen <, >, & und "
durch die Zeichenfolgen &lt;, &gt;, &amp; und &quot; ersetzt werden.
Die Umlaute (&auml; statt ä) müssen nicht ersetzt werden, wenn im head-Bereich <meta charset="utf-8"> steht.





<textarea id='TA1'> </textarea>

<button type='button'
   onclick = '
     "use strict";
     var eingabe = document.getElementById("TA1").value;
     var ausgabe = eingabe.replace(/&/g, "&\amp;")
                   .replace(/</g, "&\lt;")
                   .replace(/>/g, "&\gt;")
                   .replace(/"/g, "&\quot;");
     document.getElementById("TA2").value =
                               "<pre><code>\n"
                               + ausgabe
                               + "\n</code><pre>";
  '>HTML-Code konvertieren</button>

<button type="button"
  onclick = "document.getElementById('TA1').value = ''";
> Eingabe löschen </button>

<textarea id='TA2'> </textarea>
siehe Swift Implementation von Joey Devilla
let cookupTable = {
    "🐮": "🍔", // Cow face -> burger
    "🐄": "🍔", // Cow -> burger
    "🐂": "🍖", // Ox -> meat on bone
    "🐷": "🍖", // Pig face -> meat on bone
    "🐽": "🍖", // Pig nose -> meat on bone
    "🐖": "🍖", // Pig -> meat on bone
    "🐑": "🍖", // Sheep -> meat on bone
    "🐐": "🍖", // Goat -> meat on bone
    "🐔": "🍗", // Chicken -> poultry leg
    "🦃": "🍗", // Turkey -> poultry leg
    "🐸": "🍗", // Frog  -> poultry leg (no frog leg emoji...yet)
    "🐟": "🍣", // Fish -> sushi
    "🐠": "🍣", // Tropical fish -> sushi
    "🐡": "🍣", // Blowfish -> sushi
    "🐙": "🍣", // Octopus -> sushi
    "🍠": "🍟", // (Sweet) potato -> French fries
    "🌽": "🍿", // Corn -> popcorn
    "🌾": "🍚", // Rice -> cooked rice
    "🍓": "🍰", // Strawberry -> shortcake
    "🍂": "🍵", // Dried leaves -> tea
};