연습문제 풀이 01
1_01
;; file: 1_01.rkt
10
;;=> 10
(+ 5 3 4)
;;=> 12
(- 9 1)
;;=> 8
(/ 6 2)
;;=> 3
(+ (* 2 4) (- 4 6))
;;=> 6
;; ===========================================
(define a 3)
(define b (+ a 1))
(+ a b (* a b))
;;=> 19
(= a b)
;;=> #f
(if (and (> b a) (< b (* a b)))
b
a)
;;=> 4
(cond ((= a 4) 6)
((= b 4) (+ 6 7 a))
(else 25))
;;=> 16
(+ 2 (if (> b a)
b
a))
;;=> 6
(* (cond ((> a b) a)
((< a b) b)
(else -1))
(+ a 1))
;;=> 16
1_02
$$\frac{5 + 4 + (2 - (3 - (6 + 4/5)))}{3(6 - 2)(2 - 7)}$$
;; file: 1_02.rkt
(/ (+ 5 4 (- 2 (- 3 (+ 6 (/ 4 5)))))
(* 3 (- 6 2) (- 2 7)))
;;=> -37/150
1_03
;; file: 1_03.rkt
(#%require rackunit)
(#%require threading)
(#%require "../allcode/helper/my-util.rkt")
;; a, b, c 를 인자로 받고 가장 큰 두 수의 제곱의 합
;; 1.1.4에 나온 sum-of-squares 사용.
(define (square x)
(* x x))
(define (sum-of-squares x y)
(+ (square x) (square y)))
(define (ex1-03 a b c)
(cond ((and (< a b) (< a c))
(sum-of-squares b c))
((< b c)
(sum-of-squares a c))
(else
(sum-of-squares a b))))
(check-equal? (ex1-03 2 10 3) 109)
;; ======================================================================================
;; 번외. sequcne(list)와 고차함수를(filter/sort/take) 이용한 방법.
(define (filter pred? sequence)
(cond ((null? sequence) '())
((pred? (first sequence))
(cons (first sequence) (filter pred? (rest sequence))))
(else
(filter pred? (rest sequence)))))
(define (sort less-than? lst)
(if (or (null? lst) (null? (rest lst)))
lst
(let* ((pivot (first lst))
(rest (rest lst))
(smaller (filter (lambda (x) (less-than? x pivot)) rest))
(greater-equal (filter (lambda (x) (not (less-than? x pivot))) rest)))
(append (sort less-than? smaller)
(cons pivot (sort less-than? greater-equal))))))
(define (take n sequence)
(cond ((<= n 0) '())
((null? sequence) '())
(else (cons (first sequence)
(take (- n 1) (rest sequence))))))
(define (largest-squares n xs)
(~>> xs
(sort >)
(take n)
(map (lambda (x) (* x x)))
(apply +)))
(check-equal? (largest-squares 2 '(2 10 3)) 109)
1_04
;; file: 1_04.rkt
(define (a-plus-abs-b a b)
((if (> b 0)
+
-)
a b))
(a-plus-abs-b 2 7)
;;=> 9
(a-plus-abs-b 2 -7)
;;=> 9
(a-plus-abs-b -2 7)
;;=> 5
(a-plus-abs-b -2 -7)
;;=> 5
1_05
;; file: 1_05.rkt
(define (p)
(p))
(define (test x y)
(if (= x 0)
0
y))
(test 0 (p))
;;=> 0
;; #lang sicp 라면 y로 들어온 (p)가 무한히 호출됨.
;; #lang lazy 라면 y를 평가하지 않아 0이 반환.
;; #lang lazy는 엄밀히 말하면 Lazy Evaluation인데 normal-order evaluation에 캐쉬를 단거라 생각하면됨.
1_06
;; file: 1_06.rkt
(#%require threading)
;; 1.1.7 연습: 뉴튼 법으로 제곱근 찾기
(define (square x)
(* x x))
#;(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x)))
(define (improve guess x)
(average guess (/ x guess)))
(define (average x y)
(~> (+ x y)
(/ 2)))
(define (good-enough? guess x)
(~> (square guess)
(- x)
(abs)
(< 0.001)))
(define (sqrt x)
(sqrt-iter 1.0 x))
;; ref: https://docs.racket-lang.org/reference/if.html#%28form._%28%28quote._~23~25kernel%29._if%29%29
;;
;; (if test-expr
;; then-expr
;; else-expr)
;;
;; if는 Special form으로 predicate를 수행후 then이나 else를 수행한다.
;;
;; 하지만 new-if는 함수인데, Applicative-Order evaluation에서의 함수는 인자를 다 평가시켜버려서,
;; (sqrt-iter (improve guess x) x) 도 계속 실행시켜버려 메모리가 부족해져버린다.
(define (new-if predicate then-clause else-clause)
(cond (predicate then-clause)
(else else-clause)))
(define (sqrt-iter guess x)
(new-if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x)))
;; #lang sicp에서는 Applicative-Order Evaluation이기에
(sqrt 9)
;;!> . Interactions disabled; out of memory
;; #lang lazy에서는 Lazy Evaluation이기에
(sqrt 9)
;;=> 3.00009155413138
1_07
;; file: 1_07.rkt
(#%require threading)
(#%require (prefix racket: racket))
(#%require rackunit)
;; 1.1.7 연습: 뉴튼 법으로 제곱근 찾기
(define (square x)
(* x x))
#;(define (sqrt-iter guess x)
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x) x)))
(define (improve guess x)
(average guess (/ x guess)))
(define (average x y)
(~> (+ x y)
(/ 2)))
#;(define (good-enough? guess x)
(~> (square guess)
(- x)
(abs)
(< 0.001)))
(define (sqrt x)
(sqrt-iter 1.0 x))
;; ========================================
;; 중요한거. 번역판을 읽으면 이 문제를 풀기 어려워진다.
;; 원문을 읽어야 이 문제를 보다 풀기가 쉬워진다.
;;
;; An alternative strategy for implementing good-enough? is to watch how guess changes from one iteration to the next
;; and to stop when the change is a very small fraction of the guess.
;; 반복할 때마다 `guess`의 **변화량**를 살펴보고,
;; `guess`에 **변화량**이 **아주 작은** 비율이 되었을 때 멈추는 것이다.
(define DECIMAL-EPSILON
(let loop ([e 1.0])
(if (= (+ 1.0 e) 1.0)
(* 2 e)
(loop (/ e 2)))))
(define VERY-SMALL-RADIO DECIMAL-EPSILON)
;; (define VERY-SMALL-RADIO 0.00000000001)
VERY-SMALL-RADIO
;;=> 2.220446049250313e-16
(define (good-enough? guess next-guess)
(let ((diff (- guess next-guess)))
(~> (/ diff next-guess)
(abs)
(< VERY-SMALL-RADIO))))
(define (sqrt-iter guess x)
(let ((next-guess (improve guess x)))
(if (good-enough? guess next-guess)
guess
(sqrt-iter next-guess x))))
;; 아주 큰 수의 제곱근을 잘 구하는가?
;; 아주 작은 수 의 제곱근을 잘 구하는가?
(define COMPARE-EPSILON 0.00000001)
(check-= (sqrt 0.00000000123456) (racket:sqrt 0.00000000123456) COMPARE-EPSILON)
(check-= (sqrt 123456789012345) (racket:sqrt 123456789012345) COMPARE-EPSILON)
;; ref: https://sicp-solutions.net/post/sicp-solution-exercise-1-7/
1_08
$$ \text{목표: } y = \sqrt[3]{x} $$
$$ \text{⇒ 양변을 세제곱: } y^3 = x $$
$$ \text{⇒ 함수로 표현: } f(y) = y^3 - x $$
$$ \text{⇒ 도함수: } f'(y) = 3y^2 $$
$$ \text{⇒ 뉴튼 방법 일반식: } y_{n+1} = y_n - \frac{f(y_n)}{f'(y_n)} $$
$$ \text{⇒ 대입: } y_{n+1} = y_n - \frac{y_n^3 - x}{3y_n^2} $$
$$ = y_n - \frac{1}{3} \left( y_n - \frac{x}{y_n^2} \right) $$
$$ = \frac{2y_n + \frac{x}{y_n^2}}{3} $$
$$ \therefore \boxed{ y_{n+1} = \frac{x/y_n^2 + 2y_n}{3} } $$
;; file: 1_08.rkt
(#%require threading)
(#%require rackunit)
;; imporve 가 바뀌고 나머지는sqrt를 구할때와 거의 비슷한 흐름으로 흘러간다.
(define (improve guess x)
"( x/y^2 + 2y ) / 3"
(/ (+ (/ x (* guess guess)) (* 2 guess)) 3))
(define VERY-SMALL-RADIO 0.00000000001)
(define (good-enough? guess next-guess)
(let ((diff (- guess next-guess)))
(~> (/ diff next-guess)
(abs)
(< VERY-SMALL-RADIO))))
(define (cube-root-iter guess x)
(let ((next-guess (improve guess x)))
(if (good-enough? guess next-guess)
guess
(cube-root-iter next-guess x))))
(define (cube-root x)
(cube-root-iter 1.0 x))
(define (cube x)
(* x x x))
(define COMPARE-EPSILON 0.00000001)
(check-= 12345 (cube (cube-root 12345)) COMPARE-EPSILON)
1_09
;; file: 1_09.rkt
(#%require (prefix trace: racket/trace))
(define (x+ a b)
(if (= a 0)
b
(inc (x+ (dec a) b))))
;; (x+ 2 1)
;; (inc (x+ 1 1))
;; (inc (inc (x+ 0 1)))
;; (inc (inc 1))
;; (inc 2)
;;=> 3
(define (y+ a b)
(if (= a 0)
b
(y+ (dec a) (inc b))))
;; (y+ 2 1)
;; (y+ 1 2))
;; (y+ 0 3)
;;=> 3
(trace:trace x+)
(trace:trace y+)
(display "x+ ==============================\n")
(x+ 2 1)
;;>> >{x+ 2 1}
;;>> > {x+ 1 1}
;;>> > >{x+ 0 1}
;;>> < <1
;;>> < 2
;;>> <3
;;=> 3
(display "y+ ==============================\n")
(y+ 2 1)
;;>> >{y+ 2 1}
;;>> >{y+ 1 2}
;;>> >{y+ 0 3}
;;>> <3
;;=> 3
1_10
;; file: 1_10.rkt
(#%require (prefix trace: racket/trace))
(define (A x y)
;; A(x, 0) = 0
;; A(0, y) = 2y
;; A(x, 1) = 2
;; A(x, y) = A(x-1, A(x, y-1))
(cond ((= y 0) 0)
((= x 0) (* 2 y))
((= y 1) 2)
(else (A (- x 1) (A x (- y 1))))))
(A 1 10)
;;=> 1024
(A 2 4)
;;=> 65536
(A 3 3)
;;=> 65536
(A 4 2)
;;=> 4
(define (f n) (A 0 n))
;; A(0, y) = 2y
;; f(n) = 2n
(define (g n) (A 1 n))
;; A(1, n-0)
;; = 2 * A(1, n-1)
;; = 2 * 2 * A(1, n-2)
;; = 2 * 2 * ... * A(1, n-(n- 1))
;; = 2 * 2 * ... * 2
;; g(n) = 2^n
(define (h n) (A 2 n))
;; 2^2^2 or 2^h(n-1)
;; A(2, n - 0)
;; = A(1, A(2, n-1))
;; = A(1, A(1, A(2, n-2))
;; = A(1, A(1, A(1, ... A(1, A(2, n-(n-1))))
;; h(n) = 2^2^2....2 (2의 갯수는 n개)
;; h(n) = pow(2, h(n-1))
(define (k n) (* 5 n n))
;; k(n) = 5n^2
;; Primitive Recursive Function = 반복 횟수가 미리 정해져 있어서 for문 같은 단순 반복으로 구현 가능 (factorial같은거)
;; Ackermann은 'for' 문 같은 구조로는 표현할 수 없는 함수도 있다는걸 보여주기 위해 Ackermann함수를 만듬.
;; 재귀 깊이가 입력값에 따라 아주 빠르게 늘어나서, 고정된 반복 횟수로 미리 제한하는 'for'문으로는 표현하기 힘듬.
;;
;; ref:
;; - https://en.wikipedia.org/wiki/Ackermann_function
;; - https://plato.stanford.edu/Entries/recursive-functions/ackermann-peter.html
;; - https://sites.google.com/site/pointlesslargenumberstuff/home/2/ackermann
;; Ackermann original function
(define (φ x y z)
;; φ(x, y, 0) = x + y
;; φ(x, 0, z) = α(x, z-1)
;; φ(x, y, z) = φ(x, φ(x, y-1, z) z-1)
(cond ((= z 0) (+ x y))
((= y 0) (α x (- z 1)))
(else (φ x (φ x (- y 1) z) (- z 1)))))
(define (α x y)
;; α(x, 0) = 0
;; α(x, 1) = 1
;; α(x, y) = x
(cond ((= y 0) 0)
((= y 1) 1)
(else x)))
;; Ackermann-Peter function
(define (C m n)
;; C(0, n) = n+1
;; C(m, 0) = C(m-1, 1)
;; C(m, n) = C(m-1, C(m, n-1))
(cond ((= m 0) (+ n 1))
((= n 0) (C (- m 1) 1))
(else (C (- m 1) (C m (- n 1))))))
(trace:trace φ)
(trace:trace C)
(φ 1 2 3)
(C 2 1)
1_11
;; file: 1_11.rkt
(#%require (prefix racket: racket))
(#%require rackunit)
;; n < 3 : f(n) = n
;; n >= 3 : f(n) = f(n-1) + 2f(n-2) + 3f(n-3)
(define (f-recur n)
(cond ((< n 3) n)
(else (+ (f-recur (- n 1))
(* 2 (f-recur (- n 2)))
(* 3 (f-recur (- n 3)))))))
;; | p1 | p2 | p3 |
;; f(n) = f(n-1) + 2f(n-2) + 3f(n-3)
;; ...
;; f(4) = f(3) + 2f(2) + 3f(1) = (2 + 2*1 + 3*0) + 2*2 + 3*1 = 11
;; f(3) = f(2) + 2f(1) + 3f(0) = 2 + 2*1 + 3*0 = 4
;; f(2) = 2
;; f(1) = 1
;; f(0) = 0
(define (iter curr-n target-n fn-1 fn-2 fn-3)
(let ((next-fn-1 (+ fn-1 (* 2 fn-2) (* 3 fn-3)))
(next-fn-2 fn-1)
(next-fn-3 fn-2))
(if (= curr-n target-n)
next-fn-1
(iter (inc curr-n) target-n next-fn-1 next-fn-2 next-fn-3))))
(define (f-iter n)
(if (< n 3)
n
(iter 3 n 2 1 0)))
(racket:for ([i 20])
(check-eq? (f-recur i)(f-iter i)))
1_12
;; file: 1_12.rkt
(#%require (prefix racket: racket))
;; 파스칼 삼각형
(define (P x y)
(cond ((zero? x) 1)
((= x y) 1)
(else (+ (P (- x 1) y)
(P (- x 1) (- y 1))))))
(racket:for ([y (racket:in-inclusive-range 0 5)])
(display y)
(display ": ")
(racket:for ([x (racket:in-inclusive-range 0 y)])
(display (P x y))
(display " "))
(newline))
;;>> 0: 1
;;>> 1: 1 1
;;>> 2: 1 2 1
;;>> 3: 1 2 4 1
;;>> 4: 1 2 4 8 1
;;>> 5: 1 2 4 8 16 1
1_13
;; file: 1_13.rkt
(#%require (prefix racket: racket))
(#%require threading)
(#%require rackunit)
;; ==============================================================
;; - 특성방정식(Characteristic equation)
;; - https://en.wikipedia.org/wiki/Characteristic_equation_(calculus)
;; - 선형 점화식의 일반해를 구하기 위해 도입하는 보조적인 다항방정식
;; - 즉, 쉽게 구할 수 있는걸로 바꿔 계산하자임.
;; - 제약조건
;; - 선형(linear)
;; - 곱하기, 더하기만 있음.(항들 사이에 곱하거나 제곱하지 않음)
;; - 안되는 경우: F(n)=F(n−1)*F(n−2) (항 들 사이에 곱했음)
;; - 상수 계수(with constant coefficients)
;; - 계수들이 모두 상수여야 함 (즉, n에 따라 변하면 안 됨)
;; - 안되는 경우: F(n)=n*F(n−1)+F(n−2) (n에 따라 변함)
;; - 동차(homogeneous)
;; - 오른쪽에 독립적인 항이 없음
;; - 안되는 경우: F(n)=F(n−1)+F(n−2)+1 (오른쪽에 상수, n, 2^n, +5 같은 추가적인 항이 붙어 있으면 비동차)
;; - 근의 공식(quadratic equation)
;; - https://en.wikipedia.org/wiki/Quadratic_equation
;; - (1+√5)/2
;; - 황금비(golden ratio): https://en.wikipedia.org/wiki/Golden_ratio
;; - 비넷공식(binet formula)
;; - https://en.wikipedia.org/wiki/Fibonacci_sequence#Binet's_formula
;; - 귀납법(induction)
;; - https://en.wikipedia.org/wiki/Mathematical_induction
;; - 歸納(돌아갈 귀, 들일 납)
;; - 개별적인 사실들로부터 일반적인 결론을 이끌어내는
;; - 기저 사례(Base Case): 증명하고자 하는 명제가 특정 초기 값에 대해 참임을 보이는 단계
;; - 귀납 단계(Induction Step): 특정 수 n에 대해 명제가 참이라고 가정하고 (귀납 가설), 이를 이용하여 n+1에 대해서도 명제가 참임을 증명.
;; ==============================================================
;; 1. Fib(n)이 (φ^n)/√5에 가까운 정수임을 증명해라. (φ = (1+√5)/2, ψ = (1−√5)/2)
;;
;; # 1.1: 일반적인 접근법
;;
;; ## Fib는 특성방정식을 만족함.(선형/상수계수/동차를 만족함)
;; F(n) = F(n−1) + F(n−2)
;;
;; Fib(n) = x^n 이라 바꿔 계산하면.
;; x^n = x^(n-1) + x^(n-2)
;; x^n / x^(n-2) = x^(n-1)/x^(n-2) + x^(n-2)/x^(n-2)
;;
;; x^2 = x + 1
;; x^2 - x - 1 = 0
;;
;; 근의 공식으로 풀면
;; x = (1+√5)/2 = φ
;; x = (1-√5)/2 = ψ
;;
;;
;; ## Binet공식: F(n)= (φ^n - ψ^n)/√5
;; 앞서구한 해 φ와 ψ로 F(n)을 나타내면
;; F(n) = Aφ^n + Bψ^n
;; F(0) = 0 = A + B | 즉, A = -B
;; F(1) = 1 = Aφ + Bψ
;; = Aφ - Aψ
;; = A(φ - ψ)
;;
;; A = 1/(φ - ψ)
;; B = -1/(φ - ψ)
;;
;; φ - ψ = (1+√5)/2 - (1-√5)/2
;; = 2√5/2
;; = √5
;;
;; A = 1/√5
;; B = -1/√5
;;
;; F(n) = Aφ^n + Bψ^n
;; = 1/√5*φ^n - 1/√5*ψ^n
;; = (φ^n - ψ^n)/√5
;;
;;
;; # 1.2: G(n)을 도입한 다른 접근법
;;
;; φ + ψ = 1
;; φ - ψ = √5
;; φ * ψ = -1
;;
;; F(n+2) = (φ+ψ)*F(n+1) - (φ*ψ)*F(n)
;; F(n+2) - φ*F(n+1) = ψ*(F(n+1) - φ*F(n))
;;
;;
;; ## G(n) 정의
;;
;; G(n ) = F(n+1) - φ*F(n)
;; G(n+1) = ψ*G(n)
;; G(0) = F(1) - φ*F(0)
;; = 1 - φ*0
;; = 1
;; = ψ^0
;; G(1) = ψ*G(0)
;; = ψ^1
;; G(n) = ψ^n
;; = F(n+1) - φ*F(n)
;;
;; ## 역전개하여 F(n+1) 유도
;; F(n+1) - φ*F(n) = ψ^n
;;
;; F(n+1) = φ*F(n) + ψ^n
;; = φ(φ*F(n-1) + ψ^(n-1)) + ψ^n
;; = φ^2*F(n-1) + ψ^n + φ*ψ^(n-1)
;; = φ^3*F(n-2) + ψ^n + φ*ψ^(n-1) + φ*ψ^(n-2)
;; = ...
;; = φ^(n+1)*F(0) + sum(k=0~n, φ^(n-k)*ψ^k)
;; = φ^(n+1)*0 + sum(k=0~n, φ^(n-k)*ψ^k)
;; = sum(k=0~n, φ^(n-k)*ψ^k)
;; = φ^n * sum(k=0~n, (ψ/φ)^k) | sum(k=0~n, r^k) = (1-r^(n+1)) / (1-r)
;; = φ^n * (1-(ψ/φ)^(n+1)) / (1-(ψ/φ))
;; = φ^n * (1-(ψ/φ)^(n+1)) * φ^(n+1) / (1-(ψ/φ)) * φ^(n+1)
;; = φ^n * (φ^(n+1) - ψ^(n+1)) / (φ^(n+1) - (ψ * φ^n))
;; = (φ^(n+1) - ψ^(n+1)) / (φ - ψ)
;; = (φ^(n+1) - ψ^(n+1)) / √5
;;
;; 따라서
;; F(n) = (φ^n - ψ^n) / √5
;;
;;
;; ## 1.3: Fib(n)이 (φ^n)/√5에 가까운 정수임을 증명.
;;
;; ψ = (1-√5)/2
;; = −0.6180339887...
;; |ψ^0|/√5 = 0.447.... | 1/2 보다 작음.
;; |ψ^1|/√5 = 0.276....
;; ...
;; |ψ^n|/√5 = 0.000.... | 0으로 수렴
;; |ψ^n|/√5 = 1/2보다 작고 0으로 수렴.
;;
;; F(n) = (φ^n - ψ^n)/√5
;; = (φ^n)/√5 - (ψ^n)/√5
;; ~= (φ^n)/√5 | (ψ^n)/√5 은 0으로 수렴함으로.
;; ~= (φ^n)/√5 | 따라서 F(n)은 (φ^n)/√5에 가까운 정수.
;;
;; 더 나가자면 |ψ^n|/√5 는 항상 1/2보다 작고 0으로 수렴하므로,
;; 오차는 round 함수의 오차 허용 한계인 0.5보다 항상 작다.
;; 따라서
;;
;; F(n) = round((φ^n)/√5)
;;
;; ==============================================================
;; 2. Fib의 정의로 Fib(n) = (φ^n – ψ^n)/√5 임을 induction으로 밝혀라
;;
;; ## 기저 사례(Base case)
;;
;; n = 0 일때 성립
;; (φ^0 – ψ^0)/√5 = (1 - 1)/√5
;; = 0
;;
;; n = 1 일때 성립
;; (φ^1 – ψ^1)/√5 = (φ – ψ)/√5
;; = ((1+√5)/2) - (1-√5)/2) /√5
;; = (√5/2 + √5/2)/√5
;; = 1
;;
;; ## 귀납단계(Induction Step)
;;
;; Fib(n ) = (φ^(n-0) – ψ^(n-0))/√5
;; Fib(n-1) = (φ^(n-1) – ψ^(n-1))/√5
;; Fib(n+1) = Fib(n) + Fib(n-1)
;; = (φ^n – ψ^n)/√5 + (φ^(n-1) – ψ^(n-1))/√5
;; = (φ^n + φ^(n-1)) – (ψ^n + ψ^(n-1)) /√5
;;
;; φ = (1+√5)/2
;; φ^2 = (1 + 2√5 + 5)/4
;; = (6 + 2√5)/4
;; = 2(3 + √5)/4
;; = (3 + √5)/2
;; = (1+√5)/2 + 1
;; = φ + 1
;;
;; (φ^n + φ^(n-1)) = φ^(n-1) * (φ + 1)
;; = φ^(n-1) * φ^2
;; = φ^(n+1)
;; (ψ^n + ψ^(n-1)) = ψ^(n+1)
;;
;; 따라서 Fib(n+1) = (φ^(n+1) - ψ^(n+1)) /√5
;;
;; ## 결론.
;; 기저 사례와 귀납 단계를 통해 Fib(n) = (φ^n – ψ^n)/√5 이다.
(define (fib n)
(fib-iter 1 0 n))
(define (fib-iter a b count)
(if (= count 0)
b
(fib-iter (+ a b) a (- count 1))))
(define (fib-binet n)
(let* ((root-5 (sqrt 5))
(φ (/ (+ 1 root-5) 2))
(ψ (/ (- 1 root-5) 2)))
(~> (- (expt φ n) (expt ψ n))
(/ root-5)
(round-half-up))))
(define (round-half-up x)
;; racket:exact-floor는 IEEE-754 방식(일명 "round half to even" 또는 bankers' rounding)을 사용
;; Round half up은 0.5이상 일때 무조건 올림.
(cond ((zero? x) 0)
((positive? x) (racket:exact-floor (+ x 0.5)))
(else (racket:exact-floor (- x 0.5)))))
(racket:for ([x (racket:in-inclusive-range 1 75)])
;; 부동소수점 정밀도의 한계로 에러가 발생.
(check-eq? (fib x) (fib-binet x) (racket:~a x)))
1_14
- 1.2.2에서 나온 count-change 함수가 11 센트(cent)에 맞게 잔돈을 만들어내는 트리를 그려보아라.
%%{init: {'flowchart' : {'curve' : 'monotoneX'}}}%%
graph TD
cc_11_05_a["(cc 11 5)"] --> cc_11_04_a["(cc 11 4)"]
cc_11_04_a --> cc_11_03_a["(cc 11 3)"]
cc_11_03_a --> cc_11_02_a["(cc 11 2)"]
cc_11_02_a --> cc_11_01_a["(cc 11 1)"]
cc_11_01_a --> cc_11_00_a["(cc 11 0)"]
cc_11_01_a --> cc_10_01_a["(cc 10 1)"]
cc_10_01_a --> cc_10_00_a["(cc 10 0)"]
cc_10_01_a --> cc_09_01_a["(cc 9 1)"]
cc_09_01_a --> cc_09_00_a["(cc 9 0)"]
cc_09_01_a --> cc_08_01_a["(cc 8 1)"]
cc_08_01_a --> cc_08_00_a["(cc 8 0)"]
cc_08_01_a --> cc_07_01_a["(cc 7 1)"]
cc_07_01_a --> cc_07_00_a["(cc 7 0)"]
cc_07_01_a --> cc_06_01_a["(cc 6 1)"]
cc_06_01_a --> cc_06_00_a["(cc 6 0)"]
cc_06_01_a --> cc_05_01_a["(cc 5 1)"]
cc_05_01_a --> cc_05_00_a["(cc 5 0)"]
cc_05_01_a --> cc_04_01_a["(cc 4 1)"]
cc_04_01_a --> cc_04_00_a["(cc 4 0)"]
cc_04_01_a --> cc_03_01_a["(cc 3 1)"]
cc_03_01_a --> cc_03_00_a["(cc 3 0)"]
cc_03_01_a --> cc_02_01_a["(cc 2 1)"]
cc_02_01_a --> cc_02_00_a["(cc 2 0)"]
cc_02_01_a --> cc_01_01_a["(cc 1 1)"]
cc_01_01_a --> cc_01_00_a["(cc 1 0)"]
cc_01_01_a --> cc_00_01_a["(cc 0 1)"]
cc_11_02_a --> cc_06_02_a["(cc 6 2)"]
cc_06_02_a --> cc_06_01_b["(cc 6 1)"]
cc_06_01_b --> cc_06_00_b["(cc 6 0)"]
cc_06_01_b --> cc_05_01_b["(cc 5 1)"]
cc_05_01_b --> cc_05_00_b["(cc 5 0)"]
cc_05_01_b --> cc_04_01_b["(cc 4 1)"]
cc_04_01_b --> cc_04_00_b["(cc 4 0)"]
cc_04_01_b --> cc_03_01_b["(cc 3 1)"]
cc_03_01_b --> cc_03_00_b["(cc 3 0)"]
cc_03_01_b --> cc_02_01_b["(cc 2 1)"]
cc_02_01_b --> cc_02_00_b["(cc 2 0)"]
cc_02_01_b --> cc_01_01_b["(cc 1 1)"]
cc_01_01_b --> cc_01_00_b["(cc 1 0)"]
cc_01_01_b --> cc_00_01_b["(cc 0 1)"]
cc_06_02_a --> cc_01_02_a["(cc 1 2)"]
cc_01_02_a --> cc_01_01_c["(cc 1 1)"]
cc_01_01_c --> cc_01_00_c["(cc 1 0)"]
cc_01_01_c --> cc_00_01_c["(cc 0 1)"]
cc_01_02_a --> cc_m4_02["(cc -4 2)"]
cc_11_03_a --> cc_01_03_a["(cc 1 3)"]
cc_01_03_a --> cc_01_02_b["(cc 1 2)"]
cc_01_02_b --> cc_01_01_d["(cc 1 1)"]
cc_01_01_d --> cc_01_00_d["(cc 1 0)"]
cc_01_01_d --> cc_00_01_d["(cc 0 1)"]
cc_01_02_b --> cc_m4_02_b["(cc -4 2)"]
cc_01_03_a --> cc_m9_03["(cc -9 3)"]
cc_11_04_a --> cc_m14_04["(cc -14 4)"]
cc_11_05_a --> cc_m39_05["(cc -39 5)"]
classDef highlightNode fill:#ffcccc,stroke:#cc0000,stroke-width:2px;
class cc_00_01_a highlightNode;
class cc_00_01_b highlightNode;
class cc_00_01_c highlightNode;
class cc_00_01_d highlightNode;
;; file: 1_14.rkt
(#%require (prefix trace: racket/trace))
;; 1.2.2: count-change
(define (count-change amount)
(cc amount 5))
(define (cc amount kinds-of-coins)
(cond ((= amount 0)
1)
((or (< amount 0) (= kinds-of-coins 0))
0)
(else
(+ (cc amount (- kinds-of-coins 1))
(cc (- amount (first-denomination kinds-of-coins)) kinds-of-coins)))))
(define (first-denomination kinds-of-coins)
(cond ((= kinds-of-coins 1) 1)
((= kinds-of-coins 2) 5)
((= kinds-of-coins 3) 10)
((= kinds-of-coins 4) 25)
((= kinds-of-coins 5) 50)))
(count-change 100)
;;=> 292
(trace:trace cc)
(count-change 11)
;;=> 4
;;
;; 1. 10x 1 + 1x 1
;; 2. 5x 1 + 1x 6
;; 3. 5x 2 + 1x 1
;; 4. 1x11
;; amount가 증가함에 따라 사용되는 공간과 수행 단계의 증가 차수는?
;;
;; 1. 수행 단계 수 (시간 복잡도)
;; - https://en.wikipedia.org/wiki/Time_complexity
;;
;; - 얼핏보면: O(2^n)
;; - cc안에서 cc가 두번 호출. 호출 트리가 이진 트리처럼 보임
;; - 사실은: O(n^5)
;; - amount뿐만 아니라 동전 종류도 고려되야함.
;; - 그리고 cc를 보면 중복호출하는데 이 중복 계산도 포함하게 되면 - O(n^k)
;; - 메모이제이션이나 동적 계획법으로 풀면 O(n*k) 복잡도는 줄어들 수 있음.
;;
;; 2. 공간 사용량 (공간 복잡도)
;; - https://en.wikipedia.org/wiki/Space_complexity
;;
;; - 선형 O(n)
;; - amount가 지속적 감소 ( 최대 호출 스택 깊이 )
1_15
;; file: 1_15.rkt
(#%require (prefix trace: racket/trace))
(define (cube x)
(* x x x))
(define (p x)
(- (* 3 x)
(* 4 (cube x))))
(define (sine angle)
(if (not (> (abs angle) 0.1))
angle
(p (sine (/ angle 3.0)))))
(trace:trace p)
(trace:trace sine)
;; 1. (sine 12.15)가 호출되면 p가 몇번 호출되나?
;; sine에서 angle을 3.0으로 계속 나누고 탈출조건은 |angle| < 0.1이니
;;
;; |12.15/3^(x - 1)} < 0.1
;; |3^(x - 1)| < 121.5
;; 3^4=81
;; 3^5=243
;; 따라서 x = 5
;; (/ 12.15 3) 4.05
;; (/ 4.05 3) 1.3499999999999999
;; (/ 1.3499999999999999 3) 0.44999999999999996
;; (/ 0.44999999999999996 3) 0.15
;; (/ 0.15 3) 0.049999999999999996
(sine 12.15)
;;>> >{sine 12.15}
;;>> > {sine 4.05}
;;>> > >{sine 1.3499999999999999}
;;>> > > {sine 0.44999999999999996}
;;>> > > >{sine 0.15}
;;>> > > > {sine 0.049999999999999996}
;;>> < < < 0.049999999999999996
;;>> > > >{p 0.049999999999999996} --- 1
;;>> < < <0.1495
;;>> > > {p 0.1495} --- 2
;;>> < < 0.4351345505
;;>> > >{p 0.4351345505} --- 3
;;>> < <0.9758465331678772
;;>> > {p 0.9758465331678772} --- 4
;;>> < -0.7895631144708228
;;>> >{p -0.7895631144708228} --- 5
;;>> <-0.39980345741334
;;=> -0.39980345741334
;; 2. (sine a)를 계산시 시간 복접도와 공간 복잡도를 a로 표현.
;;
;; 깊이 - 여기선 시간복잡도, 공간사용량도 깊이랑 같음.
;; a/3^n < 0.1
;; 3^n > 10a
;; n > log3(10a)
;;
;; - 수행 단계 수 (시간 복잡도): O(log(a))
;; - 공간 사용량 (공간 복잡도): O(log(a))
1_16
;; file: 1_16.rkt
;; 1_16 / 1_17 / 1_18
(#%require threading)
(#%require rackunit)
(#%require profile)
;; fast-expt를 iterate하게 바꿔라
(define (square x)
(* x x))
(define (fast-expt b n)
(cond (( = n 0)
1)
((even? n)
(square (fast-expt b (/ n 2))))
(else
(* b (fast-expt b (- n 1))))))
(define (fast-expt-iter b n)
(define (iter acc b n)
(cond (( = n 0)
acc)
((even? n)
(iter acc (square b) (/ n 2)))
(else
(iter (* acc b) b (- n 1)))))
(iter 1 b n))
(~> (fast-expt 2 0)
(check-equal? 1))
(~> (fast-expt 2 1)
(check-equal? 2))
(~> (fast-expt 2 5)
(check-equal? 32))
(~> (fast-expt 2 10)
(check-equal? 1024))
(~> (fast-expt-iter 2 0)
(check-equal? 1))
(~> (fast-expt-iter 2 1)
(check-equal? 2))
(~> (fast-expt-iter 2 5)
(check-equal? 32))
(~> (fast-expt-iter 2 10)
(check-equal? 1024))
1_17
;; file: 1_17.rkt
;; 1_16 / 1_17 / 1_18
(#%require rackunit)
;; Mul구현. fast-expt처럼 계산단계가 로그로 자라도록 짜라.
(define (double x)
(* 2 x))
(define (halve x)
(/ x 2))
(define (mul a b)
(if (= b 0)
0
(+ a (mul a (- b 1)))))
(define (fast-mul a b)
(cond ((= b 0)
0)
((even? b)
(double (fast-mul a (halve b))))
(else
(+ a (fast-mul a (- b 1))))))
(check-equal? (mul 2 0) 0)
(check-equal? (mul 2 1) 2)
(check-equal? (mul 2 2) 4)
(check-equal? (mul 2 10) 20)
(check-equal? (mul 2 11) 22)
(check-equal? (fast-mul 2 0) 0)
(check-equal? (fast-mul 2 1) 2)
(check-equal? (fast-mul 2 2) 4)
(check-equal? (fast-mul 2 10) 20)
(check-equal? (fast-mul 2 11) 22)
1_18
;; file: 1_18.rkt
;; 1_16 / 1_17 / 1_18
(#%require rackunit)
;; Mul구현. 1.16와 1.17를 합쳐 계산 단계가 로그로 자라되 iterative형태로 짜라.
(define (double x)
(* 2 x))
(define (halve x)
(/ x 2))
(define (fast-mul-iter a b)
(define (iter acc a b)
(cond ((= b 0)
acc)
((even? b)
(iter acc (double a) (halve b)))
(else
(iter (+ acc a) a (- b 1)))))
(iter 0 a b))
(check-equal? (fast-mul-iter 2 0) 0)
(check-equal? (fast-mul-iter 2 1) 2)
(check-equal? (fast-mul-iter 2 2) 4)
(check-equal? (fast-mul-iter 2 10) 20)
(check-equal? (fast-mul-iter 2 11) 22)
1_19
;; file: 1_19.rkt
(#%require racket/trace)
;; 빈칸체우기
#|
T(a, b) - p/q에 대해서
a' = bq + aq + ap
b' = bp + aq
T(T(a, b)) - p/q에 대해서
a'' = b'q + a'q + a'p
= ((bp + aq)q + (bq + aq + ap)q + (bq + aq + ap)p
= bpq + aq^2 + bq^2 + aq^2 + aqp + bqp + aqp + ap^2
= b(2qp + q^2)+ a(q^2 + p^2) + a(2qp + q^2)
b'' = b'p + a'q
= (bp + aq)p + (bq + aq + ap)q
= bp^2 + aqp + bq^2 + aq^2 + aqp
= b(p^2 + q^2) + a(2qp + q^2)
p' = p^2 + q^2
q' = 2qp + q^2
|#
(define (fib-recur n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib-recur (- n 1))
(fib-recur (- n 2))))))
(define (fast-fib-iter n)
(fib-iter 1 0 0 1 n))
(define (fib-iter a b p q count)
(cond ((= count 0)
b)
((even? count)
(fib-iter a
b
(+ (* p p) (* q q)) ; p' = p^2 + q^2
(+ (* 2 p q) (* q q)) ; q' = 2qp + q^2
(/ count 2)))
(else
(fib-iter (+ (* b q) (* a q) (* a p)) ; a = bq + aq + ap
(+ (* b p) (* a q)) ; b = bp + aq
p
q
(- count 1)))))
#|
| fib | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10|
|-----|---|---|---|---|---|---|---|---|---|---|---|
| res | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13| 21| 34| 55|
|#
(fib-recur 10)
(fast-fib-iter 10)
#|
(trace fib-iter)
>{fib-iter 1 0 0 1 10}
>{fib-iter 1 0 1 1 5}
>{fib-iter 2 1 1 1 4}
>{fib-iter 2 1 2 3 2}
>{fib-iter 2 1 13 21 1}
>{fib-iter 89 55 13 21 0}
<55
|#
1_20
;; file: 1_20.rkt
1_21
;; file: 1_21.rkt
1_22
;; file: 1_22.rkt
1_23
;; file: 1_23.rkt
1_24
;; file: 1_24.rkt
1_25
;; file: 1_25.rkt
1_26
;; file: 1_26.rkt
1_27
;; file: 1_27.rkt
1_28
;; file: 1_28.rkt
1_29
;; file: 1_29.rkt
1_30
;; file: 1_30.rkt
1_31
;; file: 1_31.rkt
1_32
;; file: 1_32.rkt
1_33
;; file: 1_33.rkt
1_34
;; file: 1_34.rkt
1_35
;; file: 1_35.rkt
1_36
;; file: 1_36.rkt
1_37
;; file: 1_37.rkt
1_38
;; file: 1_38.rkt
1_39
;; file: 1_39.rkt
1_40
;; file: 1_40.rkt
1_41
;; file: 1_41.rkt
1_42
;; file: 1_42.rkt
1_43
;; file: 1_43.rkt
1_44
;; file: 1_44.rkt
1_45
;; file: 1_45.rkt
1_46
;; file: 1_46.rkt