forked from deinprogramm/schreibe-dein-programm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i1rek2.tex
333 lines (321 loc) · 11.2 KB
/
i1rek2.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
% Diese Datei ist Teil des Buchs "Schreibe Dein Programm!"
% Das Buch ist lizensiert unter der Creative-Commons-Lizenz
% "Namensnennung - Weitergabe unter gleichen Bedingungen 4.0 International (CC BY-SA 4.0)"
% https://creativecommons.org/licenses/by-sa/4.0/deed.de
\chapter{Fortgeschrittenes Programmieren mit Rekursion}
\label{cha:recursion-advanced}
FIXME: Vielleicht sowas wie Mergesort, parallele Listenverarbeitung
\section{Lastwagen optimal beladen}
FIXME: Backtracking sagen
Hier ist ein weiteres Problem, zu dessen Lösung
Listen hervorragend taugen:
Die Aufgabe ist, einen Lastwagen optimal auszulasten: Aus einem Lager mit zu
transportierenden Artikeln, jeder mit einem bestimmten Gewicht, sind solche Artikel
auszuwählen, dass die Tragfähigkeit des Lastwagens möglichst gut ausgeschöpft
wird.
Für die Lösung muss erst einmal festgelegt werden, wie Ein- und Ausgabe
des Programms aussehen sollen. Die elementare Größe im Problem ist
ein \textit{Artikel}, der aus einer Artikelnummer und seinem Gewicht
besteht. Folgende Daten- und Record-Definition passen dazu:\index{title@\texttt{title}}
%
\begin{alltt}
; Ein Artikel ist ein Wert
; (make-article n w)
; wobei n die Nummer des Artikels ist
; und w das Gewicht des Artikels in Kilo
(define-record-procedures article
make-article article?
(article-number article-weight))
\end{alltt}
%
Ein Beispiel-Lager wird durch die folgende
Liste beschrieben:
%
\begin{alltt}
; Ein Beispiel-Lager
(: stock (list(article))
(define stock
(list (make-article 1 274)
(make-article 2 203)
(make-article 3 268)
(make-article 4 264)
(make-article 5 229)
(make-article 6 406)
(make-article 7 220)
(make-article 8 232)
(make-article 9 356)
(make-article 10 197)
(make-article 11 207)
(make-article 12 373)))
\end{alltt}
%
Die Lösung des Problems soll eine Funktion \texttt{load-list}
sein, welche eine Liste der Artikel zurückliefert, die in den Lastwagen geladen
werden sollen. Neben der Liste der Artikel braucht
\texttt{load-list} auch noch die Tragfähigkeit eines Lastwagens. Die
Funktion soll Kurzbeschreibung und Signatur wie folgt haben:\index{load-list@\texttt{load-list}}
%
\begin{alltt}
; Maximale Liste von Artikeln berechnen,
; die auf einen Lastwagen passen
(: load-list (list(article) number -> list(article))
\end{alltt}
%
Im Fall des Beispiel-Lagers und eines Lastwagens mit 1800 kg Tragfähigkeit
soll folgendes
passieren, wenn das Programm fertig ist:
%
\begin{alltt}
(load-list stock 1800)
\evalsto{} #<list #<record:article 1 274> #<record:article 2 203>
#<record:article 3 268> #<record:article 6 406>
#<record:article 7 220> #<record:article 8 232>
#<record:article 10 197>>
\end{alltt}
%
Die Funktion arbeitet auf Listen, was folgende Schablone nahelegt:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) ...)
((cons? articles)
... (first articles) ...
... (load-list (rest articles) capacity) ...))))
\end{alltt}
%
Wenn keine Artikel da sind, kommen
auch keine in den Lastwagen. Die Liste im ersten Zweig ist also leer. Der
zweite Fall ist etwas komplizierter.
Das liegt daran, dass es dort
eine weitere Fallunterscheidung gibt, je nach dem ersten Artikel
\texttt{(first articles)}:
die Funktion muss entscheiden, ob dieser erste
Artikel schließlich in den Lastwagen kommen soll oder nicht.
Ein erstes Ausschlußkriterium ist, wenn der Artikel schwerer ist als die
Tragfähigkeit erlaubt:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) empty)
((cons? articles)
(if (> (article-weight (first articles)) capacity)
(load-list (rest articles) capacity)
... (load-list (rest articles) capacity) ...)))))
\end{alltt}
%
Damit ist die Frage, ob der erste Artikel im Lastwagen landet, aber immer
noch nicht abschließend beantwortet. Schließlich muss
\texttt{load-list} noch entscheiden, ob unter Verwendung dieses
Artikels der Lastwagen optimal vollgepackt werden
kann. Dazu muss die Funktion vergleichen, wie ein Lastwagen \emph{mit}
dem ersten Artikel und wie er \emph{ohne} diesen Artikel am besten
vollgepackt werden würde. Die Variante "<ohne"> wird mit folgendem
Ausdruck ausgerechnet:
%
\begin{alltt}
(load-list (rest articles) capacity)
\end{alltt}
%
Die Variante "<mit"> ist etwas trickreicher~-- sie entsteht, wenn im
Lastwagen der Platz für den ersten Artikel reserviert wird und
der Rest der Tragfähigkeit mit den restlichen Artikeln optimal gefüllt wird.
Die optimale Füllung für den Rest wird mit folgendem Ausdruck
berechnet, der die Induktionsannahme für \texttt{load-list} benutzt:
%
\begin{alltt}
(load-list (rest articles)
(- capacity (article-weight (first articles))))
\end{alltt}
%
Die vollständige Artikelliste entsteht dann durch nachträgliches
Wieder-Anhängen des ersten Artikels:
%
\begin{alltt}
(cons (first articles)
(load-list (rest articles)
(- capacity (article-weight (first articles)))))
\end{alltt}
%
Diese beiden Listen müssen jetzt nach ihrem Gesamtgewicht verglichen
werden. Die Liste mit dem größeren Gewicht gewinnt. Als erster
Schritt werden die beiden obigen Ausdrücke in die Schablone eingefügt:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) empty)
((cons? articles)
(if (> (article-weight (first articles)) capacity)
(load-list (rest articles) capacity)
... (load-list (rest articles) capacity) ...
... (cons (first articles)
(load-list
(rest articles)
(- capacity
(article-weight (first articles)))))
...)))))
\end{alltt}
%
Die Ausdrücke für die beiden Alternativen sind in dieser Form
unhandlich groß, was die Funktion schon unübersichtlich macht, bevor
sie überhaupt fertig ist.
Es lohnt sich also, ihnen Namen zu geben:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) empty)
((cons? articles)
(if (> (article-weight (first articles)) capacity)
(load-list (rest articles) capacity)
(let ((articles-1 (load-list (rest articles) capacity))
(articles-2
(cons (first articles)
(load-list
(rest articles)
(- capacity
(article-weight (first articles)))))
...))))))))
\end{alltt}
%
Der Ausdruck
\texttt{(article-weight (first articles))} kommt zweimal vor. Die Einführung einer
weiteren lokalen Variable macht die Funktion noch übersichtlicher:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) empty)
((cons? articles)
(let ((first-weight (article-weight (first articles))))
(if (> first-weight capacity)
(load-list (rest articles) capacity)
(let ((articles-1 (load-list (rest articles) capacity))
(articles-2
(cons (first articles)
(load-list
(rest articles)
(- capacity first-weight)))
...)))))))))
\end{alltt}
%
Zurück zur eigentlichen Aufgabe: \texttt{articles-1} und
\texttt{articles-2} sollen hinsichtlich ihres Gewichts verglichen werden.
Dies muss natürlich berechnet werden. Da dafür noch eine Funktion
fehlt, kommt Wunschdenken zur Anwendung:\index{articles-weight@\texttt{articles-weight}}
%
\begin{alltt}
; Gesamtgewicht einer Liste von Artikeln berechnen
(: articles-weight (list(article) -> number)
\end{alltt}
%
Damit kann \texttt{load-list} vervollständigt werden:
%
\begin{alltt}
(define load-list
(lambda (articles capacity)
(cond
((empty? articles) empty)
((cons? articles)
(let ((first-weight (article-weight (first articles))))
(if (> first-weight capacity)
(load-list (rest articles) capacity)
(let ((articles-1 (load-list (rest articles) capacity))
(articles-2
(cons (first articles)
(load-list
(rest articles)
(- capacity first-weight)))))
(if (> (articles-weight articles-1)
(articles-weight articles-2))
articles-1
articles-2))))))))
\end{alltt}
%
Es fehlt noch \texttt{articles-weight}, die wieder streng nach Anleitung
geht und für welche die Schablone folgendermaßen aussieht:
%
\begin{alltt}
(define articles-weight
(lambda (articles)
(cond
((empty? articles) ...)
((cons? articles)
... (first articles) ...
... (articles-weight (rest articles)) ...))))
\end{alltt}
%
Das Gesamtgewicht der leeren Liste ist 0~-- der erste Fall ist also
wieder einmal einfach. Im zweiten Fall interessiert vom ersten
Artikel nur das Gewicht:
%
\begin{alltt}
(define articles-weight
(lambda (articles)
(cond
((empty? articles) 0)
((cons? articles)
... (article-weight (first articles)) ...
... (articles-weight (rest articles)) ...))))
\end{alltt}
%
Nach Induktionsannahme liefert \texttt{(articles-weight (rest articles))} das
Gewicht der restlichen Artikel. Das Gewicht des ersten Artikels muss also
nur addiert werden:
%
\begin{alltt}
(define articles-weight
(lambda (articles)
(cond
((empty? articles) 0)
((cons? articles)
(+ (article-weight (first articles))
(articles-weight (rest articles)))))))
\end{alltt}
%
Mit \texttt{articles-weight} lässt sich bestimmen, wie voll der
Lastwagen beladen ist. Im Falle von \texttt{stock} ist das
Ergebnis sehr erfreulich; kein Platz wird verschenkt:
%
\begin{alltt}
(articles-weight (load-list stock 1800))
\evalsto{} 1800
\end{alltt}
\section*{Aufgaben}
\begin{aufgabe}\label{ex:coke-finances}
Schreiben Sie eine Funktion \texttt{drink-machine}, die das
Finanzmanagement eines Getränkeautomaten durchführt.
\texttt{Drink-machine} soll drei Argumente akzeptieren: den Preis eines
Getränks (in Euro-Cents), eine Liste der Centbeträge der
Wechselgeldmünzen, die noch im Automaten sind, und den Geldbetrag,
den der Kunde eingeworfen hat. (Also gibt es ein Listenelement pro
Münze. Falls z.B.\ mehrere Groschen im Automaten sind, finden sich
in der Liste mehrmals die Zahl 10.) Herauskommen soll eine Liste
der Centbeträge der Münzen, welche die Maschine herausgibt oder
\verb|#f|, falls die Maschine nicht herausgeben kann.
%
\begin{alltt}
(drink-machine 140 (list 50 100 500 10 10) 200)
\evalsto{} #<list 50 10>
(drink-machine 140 (list 50 20 20 20) 200)
\evalsto{} #<list 20 20 20>
\end{alltt}
\end{aufgabe}
\begin{aufgabe}
Schreiben Sie eine Funktion \texttt{backlog-list}, welche die
gleichen Eingaben wie \texttt{load-list} akzeptiert, aber die
nach dem Beladen eines Lastwagens im Lager zurückbleibenden Artikel
zurückgibt. Die Funktion soll
\texttt{load-list} als Hilfsfunktion verwenden.
\end{aufgabe}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "i1"
%%% End: