Tracks
/
Factor
Factor
/
Übungen
/
Das Hauptbuch der Bibliothekarin
Das Hauptbuch der Bibliothekarin

Das Hauptbuch der Bibliothekarin

Lernübung

Einführung

Manchmal möchtest du eine Sequenz zu einem einzigen Wert zusammenfassen, manchmal möchtest du jeden Zwischenwert sehen, den die Zusammenfassung dabei erzeugt. Factor teilt das in zwei Werkzeuge auf: reduce (in sequences) für die Faltung auf einen einzigen Wert und die kumulative Familie in math.statistics für die laufende Form.

reduce – die allgemeine Faltung

reduce ( seq init quot: ( prev elt -- next ) -- result )

reduce geht eine Sequenz Element für Element durch, trägt dabei ein laufendes Ergebnis mit (den Akkumulator) und übergibt es an eine Quotation mit zwei Argumenten. Die Quotation erhält den laufenden Akkumulator und das nächste Element; was auch immer sie auf dem Stack hinterlässt, wird zum neuen Akkumulator.

USING: math sequences ;

{ 1 2 3 4 } 0 [ + ] reduce .         ! => 10
{ 1 2 3 4 } 1 [ * ] reduce .         ! => 24

Ein Startwert ungleich null und eine eigene Kombinationsfunktion sind die Teile von reduce, die sum und product nicht erreichen können. Zum Beispiel der größte Wert in einer Sequenz, mit einem Standardwert, falls ihn kein Wert übertrifft:

USING: math.order ;

{ 3 1 -4 5 -2 } 0 [ max ] reduce .   ! => 5
{ -3 -1 -4 }    0 [ max ] reduce .   ! => 0

Der Startwert 0 nimmt am Vergleich teil: Er dient als Ergebnis, wenn jedes Element unterliegt, sodass eine Sequenz aus lauter negativen Werten trotzdem 0 liefert statt eines beliebigen kleinsten Werts.

Kumulative Reduktionen

Manchmal möchtest du jeden Zwischenwert, nicht nur den letzten. Die kumulative Familie in math.statistics gibt eine Sequenz zurück, die so lang ist wie die Eingabe, wobei jede Position die Reduktion über das an dieser Position endende Präfix ist:

cum-sum     ( seq -- newseq )    ! running total
cum-product ( seq -- newseq )    ! running product
cum-min     ( seq -- newseq )    ! running minimum
cum-max     ( seq -- newseq )    ! running maximum
USING: math.statistics ;

{ 3 1 4 1 5 9 2 6 } cum-sum .        ! => { 3 4 8 9 14 23 25 31 }
{ 1 2 3 4 } cum-product .            ! => { 1 2 6 24 }
{ 3 1 4 1 5 9 2 6 } cum-min .        ! => { 3 1 1 1 1 1 1 1 }
{ 3 1 4 1 5 9 2 6 } cum-max .        ! => { 3 3 4 4 5 9 9 9 }

Ein nützliches Muster sind verkettete kumulative Reduktionen: Die Ausgabe der einen ist selbst eine Sequenz, bereit, in eine andere eingespeist zu werden. So lässt sich „laufende Zusammenfassung einer laufenden Zusammenfassung“ mit zwei Wörtern ausdrücken. Die Kombinationen sind flexibel; kombiniere sie danach, was jeder Schritt zusammenfasst.

produce – die Entfaltung

reduce verbraucht eine Sequenz zu einem Wert. produce (in sequences) geht den umgekehrten Weg und erzeugt aus einem Startwert eine Sequenz, indem es wiederholt prüft und weiterschreitet:

produce ( pred quot -- seq )

Jede Iteration führt zuerst pred mit dem aktuellen Zustand aus; liefert es einen wahren Wert, wird quot aufgerufen, um das nächste Element zu erzeugen und den Zustand zu aktualisieren. Wenn pred f zurückgibt, stoppt die Iteration und die gesammelten Elemente werden zurückgegeben.

Ein klassisches Beispiel ist die Fibonacci-Folge (jede Zahl ist die Summe der beiden vorherigen). Der laufende Zustand ist das Paar (a, b). Jeder Schritt gibt b aus und ersetzt dann das Paar durch (b, a + b):

USING: kernel math sequences ;

! Fibonacci numbers strictly below 100:
0 1 [ dup 100 < ] [ tuck + over ] produce 2nip .
! => { 1 1 2 3 5 8 13 21 34 55 89 }

Der laufende Zustand umfasst zwei Werte, deshalb nutzt der Rumpf tuck (in kernel) – das dreielementige Umordnen, das das oberste Element unter das zweite kopiert –, um das Paar weiterzuschieben, und 2nip (ebenfalls in kernel, das zweielementige Gegenstück zu nip) räumt am Ende auf. Liest man den Aufruf von links nach rechts:

  • Die Prädikat-Funktion [ dup 100 < ] blickt auf das obere Element des Paares (die nächste auszugebende Zahl) und fährt fort, solange sie noch unter der Grenze liegt.
  • Der Rumpf [ tuck + over ] schiebt den Zustand auf (b, a + b) weiter und gibt b aus, sodass drei Werte auf dem Stack bleiben – das neue Paar unten, die ausgegebene Zahl oben.
  • Nachdem produce stoppt, werden die beiden abschließenden Werte (das letzte Paar) mit 2nip verworfen, sodass nur die erzeugte Sequenz übrig bleibt.

produce ist das exakte Gegenstück zu reduce: Während reduce eine Sequenz zu einem Wert zusammenfaltet, entfaltet produce einen Wert zu einer Sequenz.

Anleitung

Du bist der Bibliothekar und führst das Kontobuch der Nutzerkonten. Jede Woche landen zwei Arten von Arbeit auf deinem Schreibtisch:

  • Eine Warteschlange von Anfragen: Gutschriften, die ein Nutzer anwenden möchte (Buchrückgaben, bezahlte Mahngebühren), und neue Belastungen, die das System bereits verbucht hat (neu aufgelaufene Mahngebühren für überfällige Medien). Das Konto des Nutzers ist gutschriftgeschützt: Eine Gutschrift, die groß genug wäre, um den Nutzer ins Minus zu drücken, wird nur bis zur Höhe der Schulden angewendet, sodass der laufende Saldo nie unter null fällt.
  • Eine Liste von Transaktionen: Buchungen, die bereits auf dem Konto erfasst sind. Positive Beträge sind Belastungen (neue Mahngebühren), negative Beträge sind Gutschriften (Zahlungen).

Jede Woche rechnest du die Bücher ab: einen Endsaldo, nachdem du die Anfragen abgearbeitet hast, einen laufenden Saldo pro Tag aus den Transaktionen und einen laufenden Tiefststand, der Phasen markiert, in denen die Mahngebühren in die Höhe geschossen sind.

1. Die Warteschlange der Anfragen abarbeiten

Definiere protected-balance so, dass es einen Anfangssaldo opening und ein Array requests (vorzeichenbehaftete Beträge) entgegennimmt und den Endsaldo zurückgibt, nachdem jede Anfrage der Reihe nach abgearbeitet wurde. Eine Abbuchung, die den Saldo unter null drücken würde, wird nur bis zum verfügbaren Betrag ausgeführt, sodass der laufende Saldo bei null nach unten begrenzt wird.

100 { 50 -200 30 } protected-balance .
! => 30

500 { 100 -300 -250 } protected-balance .
! => 50

0 { -10 50 } protected-balance .
! => 50

2. Laufender Saldo

Definiere running-balance so, dass es ein Array transactions entgegennimmt und eine Sequenz gleicher Länge zurückgibt, deren i-tes Element der Saldo nach den ersten i+1 Transaktionen ist (bezogen auf einen Anfangssaldo von null).

{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }

3. Geringster Saldo bisher

Definiere least-balance-so-far so, dass es ein Array transactions entgegennimmt und eine Sequenz gleicher Länge zurückgibt, deren i-tes Element der niedrigste laufende Saldo ist, der bis einschließlich Position i aufgetreten ist. Das ist der laufende Tiefststand, nützlich, um Tage zu erkennen, an denen das Konto riskant aussah.

{ 50 -30 -20 100 } least-balance-so-far .
! => { 50 20 0 0 }

{ 200 -50 -100 -200 } least-balance-so-far .
! => { 200 150 50 -150 }

4. Halbieren bis zum Zielwert

Die Bibliothek führt ein Mahngebühren-Amnestieprogramm durch: Der offene Saldo eines Nutzers wird in jeder Zahlungsperiode halbiert, bis er auf oder unter eine Erlassschwelle fällt. Definiere halve-until so, dass es einen Startwert principal und einen Zielwert target entgegennimmt und die Sequenz der halbierten Werte (mit Ganzzahldivision) zurückgibt, beginnend mit der ersten Halbierung und fortgesetzt, solange der laufende Wert noch echt größer als target ist. Der zuletzt ausgegebene Wert ist der erste, der auf oder unter target fällt.

100 5 halve-until .
! => { 50 25 12 6 3 }

64 1 halve-until .
! => { 32 16 8 4 2 1 }

3 5 halve-until .
! => { }
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Factor Exercism

Bereit, mit Das Hauptbuch der Bibliothekarin zu starten?

Melde dich bei Exercism an, um Factor mit 47 Konzepte163 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.