Add solutions to exercises from subsection 2.2.3

Notes:
The exercise 2.37 on Hexlet's site has an error, noted in the comments.
Also, their page for exercise 2.38 should probably have 0 tests.

And finally, I did not calculate the exact number in the final exercise
2.43, but I included a relevant discussion.
This commit is contained in:
Petar Kapriš 2025-02-11 20:14:03 +01:00
parent 8f4c2125bc
commit 2ca5e73a27
11 changed files with 368 additions and 0 deletions

21
ex-2.33.scm Normal file
View file

@ -0,0 +1,21 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (map p sequence)
(accumulate (lambda (x y) (cons (p x)
y))
nil
sequence))
(define (append seq1 seq2)
(accumulate cons seq2 seq1))
(define (inc x y) (+ y 1))
(define (length sequence)
(accumulate inc 0 sequence))

13
ex-2.34.scm Normal file
View file

@ -0,0 +1,13 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (horner-eval x coefficient-sequence)
(accumulate (lambda (this-coeff higher-terms)
(+ (* higher-terms x) this-coeff))
0
coefficient-sequence))

14
ex-2.35.scm Normal file
View file

@ -0,0 +1,14 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (count-leaves t)
(accumulate + 0 (map (lambda (sub-t)
(cond ((pair? sub-t) (count-leaves sub-t))
((null? sub-t) 0)
(else 1)))
t)))

13
ex-2.36.scm Normal file
View file

@ -0,0 +1,13 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (accumulate-n op init seqs)
(if (null? (car seqs))
nil
(cons (accumulate op init (map car seqs))
(accumulate-n op init (map cdr seqs)))))

36
ex-2.37.scm Normal file
View file

@ -0,0 +1,36 @@
#lang sicp
;;;;
;; Error in the hexlet exercise: the matrix-*-vector line should say
;; m_ij*v_j not m_ij*v_i
;;;;
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (accumulate-n op init seqs)
(if (null? (car seqs))
'()
(cons (accumulate op init (map car seqs))
(accumulate-n op init (map cdr seqs)))))
(define (dot-product v w)
(accumulate + 0 (map * v w)))
(define (matrix-*-vector m v)
(map (lambda (row)
(dot-product row v))
m))
(define (matrix-*-matrix m n)
(let ((cols (transpose n)))
(map (lambda (row-m)
(map (lambda (col)
(dot-product row-m col))
cols))
m)))
(define (transpose m)
(accumulate-n cons '() m))

22
ex-2.38.scm Normal file
View file

@ -0,0 +1,22 @@
#lang sicp
(define (fold-right op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(fold-right op initial (cdr sequence)))))
(define (fold-left op initial sequence)
(define (iter result rest)
(if (null? rest)
result
(iter (op result (car rest))
(cdr rest))))
(iter initial sequence))
; (fold-right / 1 (list 1 2 3)) -> (1/(2/(3/1))) = 3/2
; (fold-left / 1 (list 1 2 3)) -> (((1/1)/2)/3) = 1/6
; (fold-right list nil (list 1 2 3)) -> (1 (2 (3 ())))
; (fold-left list nil (list 1 2 3)) -> (((() 1) 2) 3)
; in order to produce the same results, the operation
; must be associative.

21
ex-2.39.scm Normal file
View file

@ -0,0 +1,21 @@
#lang sicp
(define (fold-right op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(fold-right op initial (cdr sequence)))))
(define (fold-left op initial sequence)
(define (iter result rest)
(if (null? rest)
result
(iter (op result (car rest))
(cdr rest))))
(iter initial sequence))
(define (reverse-right sequence)
(fold-right (lambda (x y) (append y (list x))) nil sequence))
(define (reverse-left sequence)
(fold-left (lambda (x y) (cons y x)) nil sequence))

55
ex-2.40.scm Normal file
View file

@ -0,0 +1,55 @@
#lang sicp
; a) Define unique-pairs
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (flatmap proc seq)
(accumulate append nil (map proc seq)))
(define (enumerate-interval a b)
(if (> a b)
'()
(cons a (enumerate-interval (+ a 1) b))))
(define (unique-pairs n)
(flatmap (lambda (i)
(map (lambda (j) (list i j))
(enumerate-interval 1 (- i 1))))
(enumerate-interval 1 n)))
; b) use it to define prime-sum-pairs
(define (filter predicate sequence)
(cond ((null? sequence) nil)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
(define (square x) (* x x))
(define (smallest-divisor n)
(find-divisor n 2))
(define (find-divisor n test-divisor)
(cond ((> (square test-divisor) n) n)
((divides? test-divisor n) test-divisor)
(else (find-divisor n (+ test-divisor 1)))))
(define (divides? a b)
(= (remainder b a) 0))
(define (prime? n)
(= n (smallest-divisor n)))
(define (prime-sum? pair)
(prime? (+ (car pair) (cadr pair))))
(define (prime-sum-pairs n)
(map (lambda (pair)
(list (car pair) (cadr pair) (+ (car pair)
(cadr pair))))
(filter prime-sum?
(unique-pairs n))))

40
ex-2.41.scm Normal file
View file

@ -0,0 +1,40 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (flatmap proc seq)
(accumulate append nil (map proc seq)))
(define (filter predicate sequence)
(cond ((null? sequence) nil)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
(define (enumerate-interval a b)
(if (> a b)
'()
(cons a (enumerate-interval (+ a 1) b))))
(define (sum l) (accumulate + 0 l))
; returns a function which checks given list for
; specific sum
(define (sum-is-num s)
(lambda (list)
(= (sum list) s)))
(define (ordered-triples-with-sum n s)
(filter (sum-is-num s)
(flatmap (lambda (i)
(flatmap (lambda (j)
(map (lambda (k)
(list i j k))
(enumerate-interval 1 (- j 1))))
(enumerate-interval 1 (- i 1))))
(enumerate-interval 1 n))))

71
ex-2.42.scm Normal file
View file

@ -0,0 +1,71 @@
#lang sicp
(define (accumulate op initial sequence)
(if (null? sequence)
initial
(op (car sequence)
(accumulate op initial (cdr sequence)))))
(define (flatmap proc seq)
(accumulate append nil (map proc seq)))
(define (filter predicate sequence)
(cond ((null? sequence) nil)
((predicate (car sequence))
(cons (car sequence)
(filter predicate (cdr sequence))))
(else (filter predicate (cdr sequence)))))
(define (enumerate-interval a b)
(if (> a b)
'()
(cons a (enumerate-interval (+ a 1) b))))
; the board will be implemented as a list, which will, in a reversed order, list
; the rows of the queens, who are sorted by columns, for example:
; (5 3 1 4) means there are four queens with coordinates:
; (1,4), (2,1), (3,3), (4,5)
; this implementation is mostly chosen because of its efficiency and ease of use
(define empty-board '())
; with this implementation, k is unnecessary
(define (adjoin-position new-row k rest-of-queens)
(cons new-row rest-of-queens))
(define (all-false lst)
(cond ((eq? lst '()) #t)
((not (car lst)) (all-false (cdr lst)))
(else #f)))
(define (diagonals-from-row row len)
(list (enumerate-interval (+ row 1) (+ row len))
(reverse (enumerate-interval (- row len) (- row 1)))))
; same here
(define (safe? k positions)
(let* ((first (car positions))
(rest (cdr positions))
(diags (diagonals-from-row first (length rest)))
(upper-diag (car diags))
(lower-diag (cadr diags))
(match-row (map (lambda (x) (= x first))
rest))
(match-diag1 (map = rest upper-diag))
(match-diag2 (map = rest lower-diag)))
(and (all-false match-row)
(all-false match-diag1)
(all-false match-diag2))))
(define (queens board-size)
(define (queen-cols k)
(if (= k 0)
(list empty-board)
(filter
(lambda (positions) (safe? k positions))
(flatmap
(lambda (rest-of-queens)
(map (lambda (new-row)
(adjoin-position new-row k rest-of-queens))
(enumerate-interval 1 board-size)))
(queen-cols (- k 1))))))
(queen-cols board-size))

62
ex-2.43.txt Normal file
View file

@ -0,0 +1,62 @@
The normal function presented here:
(define (queens board-size)
(define (queen-cols k)
(if (= k 0)
(list empty-board)
(filter
(lambda (positions) (safe? k positions))
(flatmap
(lambda (rest-of-queens)
(map (lambda (new-row)
(adjoin-position
new-row k rest-of-queens))
(enumerate-interval 1 board-size)))
(queen-cols (- k 1))))))
(queen-cols board-size))
gets executed in roughly the following way: Before any of the calls to filter
or flatmap are performed (queen-cols (- k 1)) will be run. This will happen
recursively, so it will recursively call until (queen-cols 0), which will
return a list with a single empty-board.
Then, in the (queens-col 1) call, eight new boards, each with a single queen,
will be returned, then in the (queens-col 2), 64 will be created, then
filtered, and so forth, each time we exit the current call in the stack, we
have already filtered the results of the previous call.
On the other hand Louis Reasoner's function looks like this:
(define (queens board-size)
(define (queen-cols k)
(if (= k 0)
(list empty-board)
(filter
(lambda (positions) (safe? k positions))
(flatmap
(lambda (new-row)
(map (lambda (rest-of-queens)
(adjoin-position new-row k rest-of-queens))
(queen-cols (- k 1))))
(enumerate-interval 1 board-size)))))
(queen-cols board-size))
And it first calls enumerate-interval, and generates a list of 8 numbers.
After that, each of these numbers get the outer lambda applied to them, which
then calls (queen-cols 7) eight times. It will then adjoin, and afterwards
filter the results. It should be noted, these sub-calls WILL generate filtered
lists of queens, it's not as if all possible cases will be generated, and only
then filtered, however, each of these recursive subcalls will generate a list
of (queen-cols (- k 1)) eight times, instead of once, and so on recursively.
In other words, for the older version of the function, the execution time T(n)
(assuming board-size is 8), is roughly equal to:
T(n) = T(n-1) + O(8*Q(n-1))
^adjoining and filtering ^
(Q(n-1) is length of result of (queen-cols n))
unraveling this we get: O(Q(n-1)+Q(n-2)+Q(n-3)+...+Q(0))
Whereas the new function's time looks like this:
T(n) = 8*T(n-1) + O(8*Q(n-1))
unraveling, we get, very roughly:
O(8*Q(n-1) + 8*8*Q(n-2) + ... + 8^8*Q(0))
I haven't tried to run the exact numbers, because it would involve learning
exactly what Q(0), ... ,Q(8) are, which I didn't care to do.