-
Notifications
You must be signed in to change notification settings - Fork 50
/
sicp3.scm
1520 lines (1265 loc) · 48.5 KB
/
sicp3.scm
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;; -*- mode: scheme; fill-column: 75; comment-column: 50; coding: utf-8; geiser-scheme-implementation: guile -*-
;; TODO: DEADLOCK
;; TODO: 3.4
;; Chapter 3 of SICP
(use-modules (ice-9 format))
(use-modules (ice-9 q))
(use-modules (oop goops))
;; utilities
(define (inc n) (+ n 1))
(define (dec n) (- n 1))
(define (square n) (* x x))
;; Section 3.1
#| Exercise 3.1
An accumulator is a procedure that is called repeatedly with a single numeric
argument and accumulates its arguments into a sum. Each time it is called, it
returns the currently accumulated sum. Write a procedure make-accumulator that
generates accumulators, each maintaining an independent sum. The input to
make-accumulator should specify the initial value of the sum;
(define A (make-accumulator 5)) |#
(define (make-accumulator x)
(lambda (n)
(+ x n)))
#| Exercise 3.2
In software-testing applications, it is useful to be able to count
the number of times a given procedure is called during the course of a
computation. Write a procedure make-monitored that takes as input a procedure,
f, that itself takes one input. The result returned by make-monitored is a third
procedure, say mf, that keeps track of the number of times it has been called by
maintaining an internal counter. If the input to mf is the special symbol
how-many-calls?, then mf returns the value of the counter. If the input is the
special symbol reset-count, then mf resets the counter to zero. For any other
input, mf returns the result of calling f on that input and increments the
counter. For instance, we could make a monitored version of the sqrt procedure: |#
(define (make-monitored f)
(define calls 0)
(lambda (n)
(if (eq? n 'how-many-calls?)
calls
(begin
(set! calls (inc calls))
(f n)))))
#| Exercise 3.3
Modify the make-account procedure so that it creates password-protected
accounts. That is, make-account should take a symbol as an additional argument,
as in
(define acc (make-account 100 'secret-password))
(define (zv-make-account acct# passwd)
(define (withdraw amount)
(if (>= balance amount)
(begin (set! balance
(- balance amount))
balance)
"Insufficient funds"))
(define (deposit amount)
(set! balance (+ balance amount))
balance)
(define (dispatch m)
(cond ((eq? m 'withdraw) withdraw)
((eq? m 'deposit) deposit)
(else (error "Unknown request:
MAKE-ACCOUNT" m))))
dispatch)
|#
#| Exercise 3.5
Monte Carlo integration is a method of estimating definite integrals by means of
Monte Carlo simulation. Consider computing the area of a region of space
described by a predicate p ( x , y ) that is true for points ( x , y ) in the
region and #f for points not in the region. For example, the region contained
within a circle of radius 3 centered at (5, 7) is described by the predicate
that tests whether ( x − 5 ) 2 + ( y − 7 ) 2 ≤ 3 2 . To estimate the area of the
region described by such a predicate, begin by choosing a rectangle that
contains the region. For example, a rectangle with diagonally opposite corners
at (2, 4) and (8, 10) contains the circle above. The desired integral is the
area of that portion of the rectangle that lies in the region. We can estimate
the integral by picking, at random, points ( x , y ) that lie in the rectangle,
and testing p ( x , y ) for each point to determine whether the point lies in
the region. If we try this with many points, then the fraction of points that
fall in the region should give an estimate of the proportion of the rectangle
that lies in the region. Hence, multiplying this fraction by the area of the
entire rectangle should produce an estimate of the integral.
Implement Monte Carlo integration as a procedure estimate-integral that takes as
arguments a predicate p, upper and lower bounds x1, x2, y1, and y2 for the
rectangle, and the number of trials to perform in order to produce the estimate.
Your procedure should use the same monte-carlo procedure that was used above to
estimate π . Use your estimate-integral to produce an estimate of π by measuring
the area of a unit circle.
You will find it useful to have a procedure that returns a number chosen at
random from a given range. The following random-in-range procedure implements
this in terms of the random procedure used in 1.2.6, which returns a nonnegative
number less than its input.136 |#
(define (random-in-range low high)
;; had to write my own
(+ low (random high)))
(define (estimate-integral p x1 x2 y1 y2 trials)
(define width (abs (- x2 x1)))
(define height (abs (- y2 y1)))
(define area (* width height))
(define (iter remaining passed)
(let* ((x (random-in-range x1 x2))
(y (random-in-range y1 y2))
(is-contained? (p x y)))
(cond ((= remaining 0) (/ passed trials))
(is-contained? (iter (dec remaining)
(inc passed)))
(else
(iter (dec remaining) passed)))))
(* area
(iter trials 0)))
(define (unit-circle-pred x y)
(<= (+ (* x x) (* y y)) 1))
#| Exercise 3.6
It is useful to be able to reset a random-number generator to produce a sequence
starting from a given value. Design a new rand procedure that is called with an
argument that is either the symbol generate or the symbol reset and behaves as
follows: (rand 'generate) produces a new random number; ((rand 'reset)
⟨new-value⟩) resets the internal state variable to the designated ⟨new-value⟩.
Thus, by resetting the state, one can generate repeatable sequences. These are
very handy to have when testing and debugging programs that use random numbers. |#
;; This is what I assume he meant??
(define (rand command)
(case command
('generate (random 10))
(else (λ (new) (seed->random-state new)))))
;; Utilities
(define (count-pairs x)
(if (not (pair? x))
0
(+ (count-pairs (car x))
(count-pairs (cdr x))
1)))
;; (define (append! x y)
;; (set-cdr! (last-pair x) y)
;; x)
#| Exercise 3.12: The following procedure for appending lists was introduced in 2.2.1:
(define (append x y)
(if (null? x)
y
(cons (car x) (append (cdr x) y))))
Append forms a new list by successively consing the elements of x onto y. The
procedure append! is similar to append, but it is a mutator rather than a
constructor. It appends the lists by splicing them together, modifying the final
pair of x so that its cdr is now y. (It is an error to call append! with an
empty x.)
|#
(define (last-pair x)
(if (null? (cdr x))
x
(last-pair (cdr x))))
(define x (list 'a 'b))
(define y (list 'c 'd))
(define z (append x y))
;; Exercise 3.13
;; What happens if we try to compute (last-pair z)?
(define (make-cycle x)
(set-cdr! (last-pair x) x)
x)
;;; Answer: An infinite loop occurs (a cycle in the linked list has been made)
#| Exercise 3.14: The following procedure is quite useful, although obscure:
|#
(define (mystery x)
(define (loop x y)
(if (null? x)
y
(let ((temp (cdr x)))
(set-cdr! x y)
(loop temp x))))
(loop x '()))
#|
Loop uses the “temporary” variable temp to hold the old value of the cdr of x,
since the set-cdr! on the next line destroys the cdr. Explain what mystery does
in general. Suppose v is defined by (define v (list 'a 'b 'c 'd)). Draw the
box-and-pointer diagram that represents the list to which v is bound. Suppose
that we now evaluate (define w (mystery v)). Draw box-and-pointer diagrams that
show the structures v and w after evaluating this expression. What would be
printed as the values of v and w?
|#
#|
Answer:
Mystery reverses an array "in-place"
|#
#| Exercise 3.16
Ben Bitdiddle decides to write a procedure to count the number of pairs in any
list structure. “It’s easy,” he reasons. “The number of pairs in any structure
is the number in the car plus the number in the cdr plus one more to count the
current pair.” So Ben writes the following procedure:
|#
(define (count-pairs x)
(if (not (pair? x))
0
(+ (count-pairs (car x))
(count-pairs (cdr x))
1)))
#|
Show that this procedure is not correct. In particular, draw box-and-pointer
diagrams representing list structures made up of exactly three pairs for which
Ben’s procedure would return 3; return 4; return 7; never return at all.
|#
(define count-three-pairs '(a b c))
(define count-four-pairs '(a b c))
(define count-seven-pairs '(a b c))
(set-car! (cdr count-four-pairs) (cdr (cdr count-four-pairs)))
(set-car! count-seven-pairs (cdr count-seven-pairs))
#|
Answer:
(count-pairs count-three-pairs) => 3
(count-pairs count-four-pairs) => 4
(count-pairs count-seven-pairs) => 7
|#
#| Exercise 3.17
Devise a correct version of the count-pairs procedure of Exercise 3.16 that
returns the number of distinct pairs in any structure.
(Hint: Traverse the structure, maintaining an auxiliary data structure that is
used to keep track of which pairs have already been counted.)
|#
(define (zv-count-pairs xs)
(define counted '())
(define (loop xs)
(cond ((not (pair? xs)) 1)
((null? xs) 0)
((memq (car xs) counted) 0)
(else
(begin
(set! counted (cons (car xs) counted))
(+ (loop (car xs))
(loop (cdr xs)))))))
(loop xs))
#| Exercise 3.18
Write a procedure that examines a list and determines whether it contains a
cycle, that is, whether a program that tried to find the end of the list by
taking successive cdrs would go into an infinite loop. Exercise 3.13 constructed
such lists.
|#
(define (has-cycles? xs)
(define visited '())
(define (search ys)
(cond ((null? ys) #f)
((memq (car ys) visited) #t)
(else
(begin
(set! visited (cons (car ys) visited))
(search (cdr ys))))))
(search xs))
#| Exercise 3.19
Redo Exercise 3.18 using an algorithm that takes only a constant amount of
space. (This requires a very clever idea.)
|#
(define* (linear-cycle-search x1
#:optional (x2 (cdr x1)))
(cond ((or (null? (cdr x1)) (null? (cdr x2))) #f)
((eq? x1 x2) #t)
(else (linear-cycle-search (cdr x1) (cdr (cdr x2))))))
;; -- Section 3.3.2 - Queues -----------------------
;; ---( Utility Functions )--------------------------
(define (make-queue) (cons '() '()))
(define (front-ptr queue) (car queue))
(define (rear-ptr queue) (cdr queue))
(define (set-front-ptr! queue item) (set-car! queue item))
(define (set-rear-ptr! queue item) (set-cdr! queue item))
(define (empty-queue? queue) (null? (front-ptr queue)))
(define (front-queue queue)
(if (empty-queue? queue)
(error "FRONT called with an empty queue" queue)
(car (front-ptr queue))))
(define (insert-queue! queue item)
(let ((new-pair (cons item '())))
(cond ((empty-queue? queue)
(set-front-ptr! queue new-pair)
(set-rear-ptr! queue new-pair)
queue)
(else (set-cdr! (rear-ptr queue) new-pair)
(set-rear-ptr! queue new-pair)
queue))))
(define (delete-queue! queue)
(cond ((empty-queue? queue)
(error "DELETE! called with an empty queue" queue))
(else
(set-front-ptr! queue (cdr (front-ptr queue)))
queue)))
#| Exercise 3.21
Ben Bitdiddle decides to test the queue implementation described above. He types
in the procedures to the Lisp interpreter and proceeds to try them out:
|#
(define q1 (make-queue))
(insert-queue! q1 'a)
;; ((a) a)
(insert-queue! q1 'b)
;; ((a b) b)
(delete-queue! q1)
;; ((b) b)
(delete-queue! q1)
;; (() b)
#|
“It’s all wrong!” he complains. “The interpreter’s response shows that the last
item is inserted into the queue twice. And when I delete both items, the second
b is still there, so the queue isn’t empty, even though it’s supposed to be.”
Eva Lu Ator suggests that Ben has misunderstood what is happening. “It’s not
that the items are going into the queue twice,” she explains. “It’s just that
the standard Lisp printer doesn’t know how to make sense of the queue
representation. If you want to see the queue printed correctly, you’ll have to
define your own print procedure for queues.” Explain what Eva Lu is talking
about. In particular, show why Ben’s examples produce the printed results that
they do. Define a procedure print-queue that takes a queue as input and prints
the sequence of items in the queue.
|#
(define (print-queue qs)
(format #t "~a~%" (car qs)))
#| Exercise 3.22
Instead of representing a queue as a pair of pointers, we can build a queue as a
procedure with local state. The local state will consist of pointers to the
beginning and the end of an ordinary list. Thus, the make-queue procedure will
have the form
(define (make-queue)
(let ((front-ptr … )
(rear-ptr … ))
⟨definitions of internal procedures⟩
(define (dispatch m) …)
dispatch))
Complete the definition of make-queue and provide implementations of the queue
operations using this representation.
|#
(define (make-curryq)
(let ((front-ptr '())
(rear-ptr '()))
(define (set-fptr! item) (set! front-ptr item))
(define (set-rptr! item) (set! rear-ptr item))
(define (empty-curryq?)
(null? front-ptr))
(define (front-curryq)
(if (empty-curryq?)
(error "FRONT on empty queue")
(car front-ptr)))
(define (insert-curryq! item)
(let ((new-pair (cons item '())))
(cond [(empty-curryq?)
(set-fptr! item)
(set-rptr! item)]
[else
(set! rear-ptr new-pair)
(set-rptr! new-pair)])))
(define (print-queue)
(format #t "~a~%" front-ptr))
(define (dispatch m)
(cond [(eq? m 'front-ptr) front-ptr]
[(eq? m 'rear-ptr) rear-ptr]
[(eq? m 'insert-queue!) insert-curryq!]
[(eq? m 'print-queue) print-queue]))
dispatch))
#| Exercise 3.23
A deque (“double-ended queue”) is a sequence in which items can be inserted and
deleted at either the front or the rear. Operations on deques are the
constructor make-deque, the predicate empty-deque?, selectors front-deque and
rear-deque, and mutators front-insert-deque!, rear-insert-deque!,
front-delete-deque!, rear-delete-deque!. Show how to represent deques using
pairs, and give implementations of the operations. All operations should be
accomplished in Θ(1) steps.
|#
#| Structure:
This is the structure I've decided to use for the deque. There may be other
neat ways to encode a deque with cons-cells. I'd love to hear if anyone has
a better structure:
F: Front Ptr
B: Back Ptr
X: Value
/: Null or End
+---+---+
| F | B |-----------------+
+-|-+---+ |
V V
+-+-+---+ +---+---+ +-+-+---+
| * | * |-->| * | * |-->| * | / |
+-|-+---+ +-|-+---+ +-|-+---+
V ^---+ V ^---+ V
+-+-+---+ | +---+---+ | +---+---+
| X | / | | | X | * | | | X | * |
+---+---+ | +---+-+-+ | +---+-+-+
| | | |
+-------+ +-------+
|#
(define (make-deque) '(() . ()))
(define (empty-deque? dq) (null? (front-deque dq)))
(define (front-deque dq) (car dq))
(define (rear-deque dq) (cdr dq))
(define (next-deque lst) (if (null? lst) '() (cdr lst)))
(define (prev-deque lst) (if (null? lst) '() (cdar lst)))
(define (front-insert-deque! dq value)
(let ([new-elt (cons (cons value '()) '())])
(cond
((empty-deque? dq)
(set-car! dq new-elt) (set-cdr! dq new-elt)
dq)
(else
;; link our next element to the current front
(set-cdr! new-elt (front-deque dq))
;; find the next element to make a backwards link
(set-cdr! (car (front-deque dq)) new-elt)
(set-car! dq new-elt)
dq))))
(define (rear-insert-deque! dq value)
(let ([new-elt (cons (cons value '()) '())])
(cond
((empty-deque? dq)
(set-car! dq new-elt) (set-cdr! dq new-elt)
dq)
(else
;; Link our backwards element
(set-cdr! (car new-elt) (rear-deque dq))
(set-cdr! (rear-deque dq) new-elt)
(set-cdr! dq new-elt)
dq))))
(define (front-delete-deque! dq)
(let ([next (next-deque (front-deque dq))]
[front (front-deque dq)])
(cond
((null? next) (set-car! dq '()) (set-cdr! dq '()))
(else
(set-car! dq next)
(set-cdr! (car (front-deque dq)) '())))
front))
(define (rear-delete-deque! dq)
(let ([rear (rear-deque dq)]
[prev (prev-deque (rear-deque dq))])
(cond
((null? rear) (set-car! dq '()) (set-cdr! dq '()))
(else
(set-cdr! dq prev)
(set-cdr! (rear-deque dq) '())))
rear))
;; -- 3.3.3
;; --( Utility Functions )----------------------------
(define (lookup key table)
(let ((record (assoc key (cdr table))))
(if record
(cdr record)
#f)))
(define (assoc key records)
(cond ((null? records) #f)
((equal? key (caar records))
(car records))
(else (assoc key (cdr records)))))
(define (insert! key value table)
(let ((record (assoc key (cdr table))))
(if record
(set-cdr! record value)
(set-cdr! table
(cons (cons key value)
(cdr table)))))
'ok)
(define (make-table)
(let ((local-table (list '*table*)))
(define (lookup key-1 key-2)
(let ((subtable
(assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record (cdr record) #f))
#f)))
(define (insert! key-1 key-2 value)
(let ((subtable
(assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr!
local-table
(cons (list key-1 (cons key-2 value))
(cdr local-table)))))
'ok)
(define (dispatch m)
(cond ((eq? m 'lookup-proc) lookup)
((eq? m 'insert-proc!) insert!)
(else (error "Unknown operation: TABLE" m))))
dispatch))
(define operation-table (make-table))
(define get (operation-table 'lookup-proc))
(define put (operation-table 'insert-proc!))
#| Exercise 3.24:
In the table implementations above, the keys are tested for equality using
`equal?' (called by `assoc'). This is not always the appropriate test. For
instance, we might have a table with numeric keys in which we don't need an
exact match to the number we're looking up, but only a number within some
tolerance of it. Design a table constructor `make-table' that takes as an
argument a `same-key?' procedure that will be used to test "equality" of
keys. `Make-table' should return a `dispatch' procedure that can be used to
access appropriate `lookup' and `insert!' procedures for a local table.
|#
(define (make-table-with-key same-key?)
(let ((local-table (list '*table*)))
;; just redefine `assoc' with `same-key?'
(define (assoc key records)
(cond ((null? records) #f)
((same-key? key (caar records)) (car records))
(else (assoc key (cdr records)))))
(define (lookup key-1 key-2)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(cdr record)
#f))
#f)))
(define (insert! key-1 key-2 value)
(let ((subtable (assoc key-1 (cdr local-table))))
(if subtable
(let ((record (assoc key-2 (cdr subtable))))
(if record
(set-cdr! record value)
(set-cdr! subtable
(cons (cons key-2 value)
(cdr subtable)))))
(set-cdr! local-table
(cons (list key-1
(cons key-2 value))
(cdr local-table))))))
(define (dispatch m)
(cond ((eq? m 'lookup-proc) lookup)
((eq? m 'insert-proc!) insert!)))
dispatch))
#| Exercise 3.25
Generalizing one and two-dimensional tables, show how to implement a table
in which values are stored under an arbitrary number of keys and different
values may be stored under different numbers of keys. The `lookup' and
`insert!' procedures should take as input a list of keys used to access the
table.
|#
#| Answer:
The easiest way to accomplish this is to accept variadic arguments to
`insert' and `lookup', folding them into a string or using the list
directly (which `equal?' can compare)
|#
#| Exercise 3.26
To search a table as implemented above, one needs to scan through the list
of records. This is basically the unordered list representation of section
*Note 2-3-3. For large tables, it may be more efficient to structure the
table in a different manner. Describe a table implementation where the
(key, value) records are organized using a binary tree, assuming that keys
can be ordered in some way (e.g., numerically or alphabetically). (Compare
Exercise 2-66 of Chapter 2)
|#
#| Answer:
The value that is to be inserted is converted into it's numeric form. Insert
& Lookup function as you would expect
|#
#| Notes:
This could be expanded out to actually implement the tree.
|#
#| Exercise 3.27
"Memoization" (also called "tabulation") is a technique that enables a
procedure to record, in a local table, values that have previously been
computed. This technique can make a vast difference in the performance of a
program. A memoized procedure maintains a table in which values of previous
calls are stored using as keys the arguments that produced the values. When
the memoized procedure is asked to compute a value, it first checks the
table to see if the value is already there and, if so, just returns that
value. Otherwise, it computes the new value in the ordinary way and stores
this in the table. As an example of memoization, recall from section
1-2-2 the exponential process for computing Fibonacci numbers:
(define (fib n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1))
(fib (- n 2))))))
The memoized version of the same procedure is
(define memo-fib
(memoize (lambda (n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (memo-fib (- n 1))
(memo-fib (- n 2))))))))
where the memoizer is defined as
(define (memoize f)
(let ((table (make-table)))
(lambda (x)
(let ((previously-computed-result (lookup x table)))
(or previously-computed-result
(let ((result (f x)))
(insert! x result table)
result))))))
Draw an environment diagram to analyze the computation of `(memo-fib
3)'. Explain why `memo-fib' computes the nth Fibonacci number in a number
of steps proportional to n. Would the scheme still work if we had simply
defined `memo-fib' to be `(memoize fib)'?
|#
#| Answer:
memo-fib is O(N) because the fibonacci sequence can simply be computed in
2*(Σ(N)) steps (half of which are 'precomputed')
The scheme would not work if each function were freshly memoized because
the `table' would not be shared between the various applications of
`memo-fib'. |#
;; -- 3.3.4 | Constraint Satisfaction ----------------
;; --( Utility Functions )----------------------------
(define-class <wire> ()
(signal-value
#:init-value 0
#:setter set-signal!
#:getter signal-value)
(action-procedures #:init-form '()
#:accessor action-procedures
#:setter set-action-procedures))
(define (make-wire)
(make <wire>))
(define-method (set-signal! (w <wire>) new-value)
(let ([old-value (signal-value w)]
[result (slot-set! w 'signal-value new-value)])
(unless (equal? old-value new-value)
(map (λ (x) (x)) (action-procedures w)))))
;; (define-generic add-action!)
(define-method (add-action! (w <wire>) proc)
(set-action-procedures w (cons proc (action-procedures w)))
(proc))
(define inverter-delay 2)
(define (inverter input output)
(define (invert-input)
(let ([new-value (logical-not (signal-value input))])
(after-delay inverter-delay
(λ ()
(set-signal! output
new-value)))))
(add-action! input invert-input)
'ok)
(define (logical-not s)
(cond [0 1]
[1 0]
(else (error "Invalid signal"))))
(define and-gate-delay 3)
(define (and-gate a1 a2 output)
(define (and-action-procedure)
(let ((new-value
(logand (signal-value a1)
(signal-value a2))))
(after-delay
and-gate-delay
(λ ()
(set-signal! output new-value)))))
(add-action! a1 and-action-procedure)
(add-action! a2 and-action-procedure)
'ok)
(define (after-delay delay action)
(add-to-agenda!
(+ delay (current-time the-agenda))
action
the-agenda))
(define (propagate)
(if (empty-agenda? the-agenda) 'done
(let ([first-item (first-agenda-item the-agenda)])
(first-item)
(remove-first-agenda-item! the-agenda)
(propagate))))
(define (make-time-segment time queue)
(cons time queue))
(define (segment-time s) (car s))
(define (segment-queue s) (cdr s))
(define (make-agenda) (list 0))
(define (current-time agenda) (car agenda))
(define (set-current-time! agenda time)
(set-car! agenda time))
(define (segments agenda) (cdr agenda))
(define (set-segments! agenda segments)
(set-cdr! agenda segments))
(define (first-segment agenda)
(car (segments agenda)))
(define (rest-segments agenda)
(cdr (segments agenda)))
(define (empty-agenda? agenda)
(null? (segments agenda)))
(define (add-to-agenda! time action agenda)
(define (belongs-before? segments)
(or (null? segments)
(< time
(segment-time (car segments)))))
(define (make-new-time-segment time action)
(let ((q (make-q)))
(enq! q action)
(make-time-segment time q)))
(define (add-to-segments! segments)
(if (= (segment-time (car segments)) time)
(q-push! (segment-queue (car segments))
action)
(let ((rest (cdr segments)))
(if (belongs-before? rest)
(set-cdr! segments
(cons (make-new-time-segment time action)
(cdr segments)))
(add-to-segments! rest)))))
(let ((segments (segments agenda)))
(if (belongs-before? segments)
(set-segments! agenda
(cons (make-new-time-segment time action)
segments))
(add-to-segments! segments))))
(define (remove-first-agenda-item! agenda)
(let ((q (segment-queue
(first-segment agenda))))
(deq! q)
(if (q-empty? q)
(set-segments!
agenda
(rest-segments agenda)))))
(define (first-agenda-item agenda)
(if (empty-agenda? agenda)
(error "Agenda is empty: FIRST-AGENDA-ITEM")
(let ((first-seg
(first-segment agenda)))
(set-current-time!
agenda
(segment-time first-seg))
(q-front
(segment-queue first-seg)))))
(define the-agenda (make-agenda))
(define (half-adder a b s c)
(let ((d (make-wire)) (e (make-wire)))
(or-gate a b d)
(and-gate a b c)
(inverter c e)
(and-gate d e s)
'ok))
(define (full-adder a b c-in sum c-out)
(let ((c1 (make-wire))
(c2 (make-wire))
(s (make-wire)))
(half-adder b c-in s c1)
(half-adder a s sum c2)
(or-gate c1 c2 c-out)
'ok))
(define (zv-probe name wire)
(add-action!
wire
(lambda ()
(newline)
(display name)
(display " ")
(display (current-time the-agenda))
(display " New-value = ")
(display (signal-value wire))
(display "\n"))))
(define input-1 (make-wire))
(define input-2 (make-wire))
(define sum (make-wire))
(define carry (make-wire))
#| Exercise 3.28
Define an or-gate as a primitive function box. Your or-gate constructor should
be similar to and-gate.
|#
(define or-gate-delay 5)
(define (or-gate a1 a2 output)
(define (or-action-procedure)
(let ((new-value
(logior (signal-value a1)
(signal-value a2))))
(after-delay
or-gate-delay
(λ ()
(set-signal! output new-value)))))
(add-action! a1 or-action-procedure)
(add-action! a2 or-action-procedure)
'ok)
#| Exercise 3.29
Another way to construct an or-gate is as a compound digital logic device, built
from and-gates and inverters. Define a procedure or-gate that accomplishes this.
What is the delay time of the or-gate in terms of and-gate-delay and
inverter-delay? |#
;; (!a1) && a2 is congruent to a1 || a2, it is as fast as (AND-DELAY + INVERTER_DELAY)
#| TODO: Exercise 3.30
Figure 3.27 shows a ripple-carry adder formed by stringing together n
full-adders. This is the simplest form of parallel adder for adding two n -bit
binary numbers. The inputs A 1 , A 2 , A 3 , …, A n and B 1 , B 2 , B 3 , …, B n
are the two binary numbers to be added (each A k and B k is a 0 or a 1). The
circuit generates S 1 , S 2 , S 3 , …, S n , the n bits of the sum, and C , the
carry from the addition. Write a procedure ripple-carry-adder that generates
this circuit. The procedure should take as arguments three lists of n wires
each—the A k , the B k , and the S k —and also another wire C . The major
drawback of the ripple-carry adder is the need to wait for the carry signals to
propagate. What is the delay needed to obtain the complete output from an n -bit
ripple-carry adder, expressed in terms of the delays for and-gates, or-gates,
and inverters?
|#
#| Exercise 3.31
The internal procedure `accept-action-procedure!' defined in make-wire specifies
that when a new action procedure is added to a wire, the procedure is
immediately run. Explain why this initialization is necessary. In particular,
trace through the half-adder example in the paragraphs above and say how the
system’s response would differ if we had defined accept-action-procedure! as
(define (accept-action-procedure! proc)
(set! action-procedures
(cons proc action-procedures)))
|#
#| Answer:
the signal value must be initialized or the entire system will run the action
procedures (no matter what has changed)
|#
#| TODO: Exercise 3.32
The procedures to be run during each time segment of the agenda are kept in a
queue. Thus, the procedures for each segment are called in the order in which
they were added to the agenda (first in, first out). Explain why this order must
be used. In particular, trace the behavior of an and-gate whose inputs change
from 0, 1 to 1, 0 in the same segment and say how the behavior would differ if
we stored a segment’s procedures in an ordinary list, adding and removing
procedures only at the front (last in, first out).
|#
;; ------- 3.3.5 - Propagation of Contraints --------
;; -----------( Utility Functions )------------------
;; ;; ;; macro to define the set-value terms
;; (define-syntax define-constraint-fns
;; (syntax-rules (value c)
;; ([_ elt ...]
;; [begin
;; (eval ;; (set-total c value) => (set-value! total value c)
;; `(define elt
;; (lambda (value c)
;; (set-value! elt value c)))
;; (interaction-environment)) ...])))
;; (define-constraint-fns set-total set-lhs set-rhs)
(define (for-each-except exception procedure list)
(define (loop items)
(cond ((null? items) 'done)
((equal? (car items) exception)
(loop (cdr items)))
(else (procedure (car items))
(loop (cdr items)))))
(loop list))
(define-class <connector> ()
(value #:init-value #f
#:accessor connector-value
#:setter set-connector-value)
(informant #:init-value #f
#:accessor informant
#:setter set-informant)
(constraints #:accessor constraints
#:setter set-constraints
#:init-form '()))
(define (make-connector)
(make <connector>))
(define-method (has-value? (connxn <connector>))
(if (informant connxn) #t
#f))
(define-method (set-value! (connxn <connector>) newval setter)