2.3.1 따옴표 연산
글자를 데이터처럼 다루고자 한다면, 글자 자체를 데이터로 "인용할" 표현수단을 도입해야한다.
=> (list a b) 라는 식을 만든다면 'a'와 'b'라는 '문자'가 list에 대입되는 것이 아니라
a와 b에 존재하는 변수를 대입한다.
=> 이러한 이유로 ' '를 통해 문자를 데이터화한다.
ex)
(list 'a 'b) -> (a b)
==> 자연어와 다른 점: 글자 데이터 앞에 하나만 친다
(define b 2)
(list 'a b) -> (a 2)
(car '(a b c)) -> (car (quote a b c)) -> a
(cdr '(a b c)) -> (cdr (quote a b c)) -> (b c)
* '() == nil
글자 데이터 기본 연산: eq?(글자 두개 비교)
ex)
(define (memq item x)
(cond ((null? x) false)
((eq? item (car x)) x)
(else (memq item (cdr x)))))
2.3.2 연습: 글자 식의 미분
ex) a*x^2+b*x+c
요약된 데이터로 미분하는 프로그램 짜기
가정)
모든 식이 오로지 덧셈식과 곱셈식만으로 이루어져 있다.
식을 미분하는 규칙)
1. 상수를 미분하면 0, x를 미분하면 1
2. 분배 법칙
3. d(u*v)/dx = u*(dv/dx)+v*(du/dx)
=> 2, 3번 규칙은 재귀 정의다. 아무리 높은 차수의 미분방정식이 있다하더라도 2, 3번 규칙을 반복적으로 사용하다보면 결국 미분결과로서 0 or 1 이 나오게 된다.(1번)
(define (deriv exp var) //deriv 미분방정식 미분변수
(cond ((number? exp) 0) //상수인가?
((variable? exp) //변수인가?
(if? (same-variable? exp var) 1 0)) //밑과 지수가 각각 1과 0인가?
((sum? exp) //덧셈식인가?
(make-sum (deriv (addend exp) var) //식에서 가수를 골라낸 후 미분
(deriv (augend exp) var))) //식에서 피가수를 골라낸 후 미분
((product? exp) //곱셈식인가?
(make-sum
(make-product (multiplier exp) //식에서 곱수를 골라낸다
(deriv (multiplicand exp) var)) //식에서 피곱수를 골라낸 후 미분
(make-product (deriv (multiplier exp) var) //식에서 곱수를 골라낸 후 미분
(multiplicand exp)))) //식에서 피곱수를 골라낸다.
(else
(error "unknown expression type -- DERIV" exp))))
대수식 표현하기
// (+ (* a x) b)를 ax + b로 표현하기
(define (variable? x) (symbol? x)) -> 단순한 기호 인가 변수인가?
(define (same-variable? v1 v2)
(and (variable? v1) (variable? v2) (eq? v1 v2))) -> 두 변수가 서로 같은가?
(define (make-sum a1 a2) (list '+ a1 a2))
(define (make-product m1 m2) (list '* m1 m2))
(define (sum? x)
(and (pair? x) (eq? (car x) '+)))
(define (product? x)
(and (pair? x) (eq? (car x) '*)))
(define (augend s) (cadr s))
(define (multiplicand p) (cadr p))
(define (addend s) (caddr s))
(define (multiplier p) (caddr p))
이 식들을 가지고 미분을 했을때 깔끔하게 나오지 않는다.
ex)
d(x*y)/dx = x*0+1*y ----------> y
깔끔하게
깔끔하게 식을 고친다
// deriv 식을 고치는 것이 아닌 make-sum을 고친다.
// 이런 식으로 기계를 고치듯이 기계 전체를 바꾸기 보다는 기계 부품을 바꾼다.
(define (make-sum a1 a2)
(cond ((=number? a1 0) a2) //한 식이 어떤 수와 같은지 알아보는 프로시저
((=number? a2 0) a1)
((and (number? a1) (number? a2)) (+ a1 a2))
(else (list '+ a1 a2))))
(define (make-product m1 m2)
(cond ((or (=number? m1 0) (=number? m2 0)) 0)
((=number? m1 1) m2)
((=number? m2 1) m1)
((and (number? m1) (number? m2)) (* m1 m2))
(else (list '* m1 m2))))
2.3.3 연습: 집합을 나타내는 방법
집합 연산: union-set, intersection-set, element-of-set, adjoin-set
element-of-set?: 어떤 데이터가 집합의 원소인지 알아보는 술어 연산
adjoin-set: 물체 하나와 집합 하나를 인자로 받아서 그 물체와 집합을 엮은 새 집합을 내놓는다.
차례 없는 리스트로 표현한 집합
(define (element-of-set? x set)
(cond ((null? set) false)
((equals? x (car set)) true) // x 와 set에 있는 원소가 같으면 true
(else (element-of-set? x (cdr set)))))
(define (adjoin-set x set)
(if (element-of-set? x set)
set
(cons x set)))
(define (intersection-set set1 set2)
(cond ((or (null? set1) (null? set2)) '())
((element-of-set? (car set1) set2)
(cons (car set1)
(intersection-set (cdr set1) set2)))
(else (intersection-set (cdr set1 set2))))
==>한 데이터의 표현 방식을 설계할 때 효율은 큰 고민거리 중 하나이다. 위에서 보다시피 모든 집합 연산에서 element-of-set?을 쓰므로, 이 연산의 빠르기가 집합 구현의 효율에 큰 힘을 미친다. //기계부품이 좋을 수로 기계의 성능이 좋은 것과 비슷한 이치
차례 매긴 리스트로 표현된 집합
집합연산의 속도를 끌어올리는 방법)
집합의 표현 방식을 바꾸어서 리스트 속 모든 원소가 오름차순으로 늘어서게끔 만든다.
->집합 속 두 원소를 비교해 어느 것이 큰 지 따질 방법이 필요
1) 글자가 원소일 때 => 사전 차례
2) 물체마다 구별되는 수를 매겨 놓고 수를 비교한다.
원소에 차례를 매겨서 좋은 점)
한 집합의 모든 원소를 훑어보지 않아도 된다.
ex)
(define (element-of-set? x set)
(cond ((null? set) false)
((= x (car set)) true)
((< x (car set)) false)
//찾던 원소보다 더 큰 원소를 만나면 그 원소는 집합에 없다는 뜻
(else (element-of-set? x (cdr set)))))
(define (intersection-set set1 set2)
(if (or (null? set1) (null? set2))
'()
(let ((x1 (car set1)) (x2 (car set2))) //x1, x2는 두 집합의 첫번째 원소
(cond ((= x1 x2) //x1 = x2 이면 이 값은 교집합의 원소이다.
(cons x1
(intersection-set (cdr set1)
(cdr set2))))
((< x1 x2) //x1 < x2 이면 x1은 교집합의 원소가 아니다.
(intersection-set (cdr set1) set2))
((< x2 x1) //x1 > x2 이면 x2는 교집합의 원소가 아니다.
(intersection-set set1 (cdr set2)))))))
두 갈래 나무로 표현한 집합 <= 차례 매긴 리스트 보다 효율 좋음
각 마디에는 집합원소(열매)가 하나씩
왼쪽 고리는 그 마디에 있는 원소보다 작은 원소들, 오른쪽 고리는 그보다 큰 원소들
같은 집합을 여러가지 방식으로 표현할 수 있다. |
x가 집합의 원소인지 아닌 따질때 먼저 x와 맨 꼭대기 마디의 열매를 견준다. x가 열매보다 작으면 왼쪽나무만 살펴보면 되고, 반대로 x가 열매보다 크면 오른쪽나무만 살펴보면 된다.
// 훓어내려갈 때 마다 따져나가야 할 수가 반씩 줄어든다.
ex)
(define (entry tree) (car tree))
(define (left-branch tree) (cadr tree))
(define (right-branch tree) (caddr tree))
(define (make-tree entry left right)
(list entry left right))
(define (element-of-set? x set)
(cond ((null? set) false)
((= x (entry set)) true)
((< x (entry set))
(element-of-set? x (left-branch set)))
((> x (entry set))
(element-of-set? x (right-branch set)))))
(define adjoin-set x set) // 변수 x 와 집합 set을 받아들인다.
(cond ((null? set) (make-tree x '() '())) // null 일때
((= x (entry set)) set) // x = set일때
((< x (entry set)) // x < set 일때
(make-tree (entry set)
(adjoin-set x (left-branch set)) //왼쪽 가지에 x
(right-branch set)))
((> x (entry set)) // x > set 일때
(make-tree (entry set)
(left-branch set)
(adjoin-set x (right-branch set))))) //오른쪽 가지에 x
*주의점!!!
나무의 생김새가 균형잡히게 나와야 한다.
=> 새로운 연산식(adjoin-set)을 정의해 사용한다.
집합에서 정보 찾아내기
찾고자 하는 데이터에 빨리 다가갈 수 있도록 연산의 효율을 끌어올리는 것이 중요하다.
=> 각 데이터마다 키를 부여해주는 것
=> 데이터 부여된 키를 뽑아내는 프로시저 필요!!
(define (lookup given-key set-of-records)
(cond ((null? set-of-records) false)
((equals? given-key (key (car set-of-records)))
(car set-of-records))
(else (lookup given-key (cdr set-of-records)))))
2.3.4 연습 : 허프만 인코딩 나무
=>글자의 출현빈도에 따라 코드의 길이를 다르게 주는 방식
만드는 법)
가장 적게 나오는 글자가 나무뿌리에서 가장 떨어져있게 하면 된다.
ex)
나뭇잎 집합 {(A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) (E 1) (F 1) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F} 2) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F} 2) ({G H} 2)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F G H} 4)}
묶어내기 {(A 8) ({B C D} 5) ({E F G H} 4)}
묶어내기 {(A 8) ({B C D E F G H} 9)}
마지막으로 묶어내기 {({A B C D E F G H} 17)}
=> 마지막 마디가 남을 때까지 가장 가벼운 가지 2개를 묶는다.
표현하는 방법
(define (make-leaf symbol weight)
(list leaf symbol weight))
(define (leaf? object)
(eq? (car object) 'leaf))
(define (symbol-leaf x) (cadr x))
(define (weight-leaf x) (caddr x))
(define (make-code-tree left right) //허프만 트리 생성 프로시저
(list left
right
(append (symbols left) (symbols right))
(+ (weight left) (weight right))))
(define (left-branch tree) (car tree))
(define (right-branch tree) (cadr tree))
(define (symbols tree) //문자 집합
(if (leaf? tree)
(list (symbol-leaf tree))
(caddr tree)))
(define (weight tree) //무게 집합
(if (leaf? tree)
(weight-leaf tree)
(caddr tree)))
디코딩 프로시저
(define (decode bits tree)
(define (decode-1 bits current-branch)
(if (null? bits)
'()
(let ((next-branch //next-branch 정의
(choose-branch (car bits) current-branch)))
(if (leaf? next-branch)
(cons (symbol-leaf next-branch)
(decode-1 (cdr bits) tree))
(decode-1 (cdr bits) next-branch)))))
(decode-1 bits tree))
(define (choose-branch bit branch) //문자를 비트로 변환
(cond ((= bit 0) (left-branch branch))
((= bit 1) (right-branch branch))
(else (error "bad bit -- CHOOSE-BRANCH" bit))))
무게가 있는 원소들의 집합
앞의 나무표현 방식에서는 잎이 아닌 마디에 글자 집합이 들어 있었는데, 이 집합을 리스트로 표현했다. 하지만, 위에서 얘기한 알고리즘대로 나무를 만들려면, 가장 작은 원소를 잇달아 묶어내는 과정에서 나뭇잎과 나무들로 이루어진 집합을 쓸 수 있어야 한다. 가장 작은 원소를 찾는 일이 되풀이되므로, 이런 경우에는 차례 매긴 집합 표현을 쓰는 것이 편리하다.
(define (adjoin-set x set)
(cond ((null? set) (list x))
((< (weight x) (weight (car set))) (cons x set))
(else (cons (car set)
(adjoin-set x (cdr set))))))
위에 adjoin-set과 차이점)
원소를 무게에 따라 분류한다.
(define (make-leaf-set pairs) //글자와 빈도 쌍으로 이루어진 리스트: pairs
(if (null? pairs)
'()
(let ((pair (car pairs))) //pair 정의
(adjoin-set (make-leaf (car pair)
(cadr pair))
(make-leaf-set (cdr pairs))))))
(make-tree (entry set)
(left-branch set)
(adjoin-set x (right-branch set))))) //오른쪽 가지에 x
*주의점!!!
나무의 생김새가 균형잡히게 나와야 한다.
=> 새로운 연산식(adjoin-set)을 정의해 사용한다.
집합에서 정보 찾아내기
찾고자 하는 데이터에 빨리 다가갈 수 있도록 연산의 효율을 끌어올리는 것이 중요하다.
=> 각 데이터마다 키를 부여해주는 것
=> 데이터 부여된 키를 뽑아내는 프로시저 필요!!
(define (lookup given-key set-of-records)
(cond ((null? set-of-records) false)
((equals? given-key (key (car set-of-records)))
(car set-of-records))
(else (lookup given-key (cdr set-of-records)))))
2.3.4 연습 : 허프만 인코딩 나무
=>글자의 출현빈도에 따라 코드의 길이를 다르게 주는 방식
만드는 법)
가장 적게 나오는 글자가 나무뿌리에서 가장 떨어져있게 하면 된다.
ex)
나뭇잎 집합 {(A 8) (B 3) (C 1) (D 1) (E 1) (F 1) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) (E 1) (F 1) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F} 2) (G 1) (H 1)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F} 2) ({G H} 2)}
묶어내기 {(A 8) (B 3) ({C D} 2) ({E F G H} 4)}
묶어내기 {(A 8) ({B C D} 5) ({E F G H} 4)}
묶어내기 {(A 8) ({B C D E F G H} 9)}
마지막으로 묶어내기 {({A B C D E F G H} 17)}
=> 마지막 마디가 남을 때까지 가장 가벼운 가지 2개를 묶는다.
표현하는 방법
(define (make-leaf symbol weight)
(list leaf symbol weight))
(define (leaf? object)
(eq? (car object) 'leaf))
(define (symbol-leaf x) (cadr x))
(define (weight-leaf x) (caddr x))
(define (make-code-tree left right) //허프만 트리 생성 프로시저
(list left
right
(append (symbols left) (symbols right))
(+ (weight left) (weight right))))
(define (left-branch tree) (car tree))
(define (right-branch tree) (cadr tree))
(define (symbols tree) //문자 집합
(if (leaf? tree)
(list (symbol-leaf tree))
(caddr tree)))
(define (weight tree) //무게 집합
(if (leaf? tree)
(weight-leaf tree)
(caddr tree)))
디코딩 프로시저
(define (decode bits tree)
(define (decode-1 bits current-branch)
(if (null? bits)
'()
(let ((next-branch //next-branch 정의
(choose-branch (car bits) current-branch)))
(if (leaf? next-branch)
(cons (symbol-leaf next-branch)
(decode-1 (cdr bits) tree))
(decode-1 (cdr bits) next-branch)))))
(decode-1 bits tree))
(define (choose-branch bit branch) //문자를 비트로 변환
(cond ((= bit 0) (left-branch branch))
((= bit 1) (right-branch branch))
(else (error "bad bit -- CHOOSE-BRANCH" bit))))
무게가 있는 원소들의 집합
앞의 나무표현 방식에서는 잎이 아닌 마디에 글자 집합이 들어 있었는데, 이 집합을 리스트로 표현했다. 하지만, 위에서 얘기한 알고리즘대로 나무를 만들려면, 가장 작은 원소를 잇달아 묶어내는 과정에서 나뭇잎과 나무들로 이루어진 집합을 쓸 수 있어야 한다. 가장 작은 원소를 찾는 일이 되풀이되므로, 이런 경우에는 차례 매긴 집합 표현을 쓰는 것이 편리하다.
(define (adjoin-set x set)
(cond ((null? set) (list x))
((< (weight x) (weight (car set))) (cons x set))
(else (cons (car set)
(adjoin-set x (cdr set))))))
위에 adjoin-set과 차이점)
원소를 무게에 따라 분류한다.
(define (make-leaf-set pairs) //글자와 빈도 쌍으로 이루어진 리스트: pairs
(if (null? pairs)
'()
(let ((pair (car pairs))) //pair 정의
(adjoin-set (make-leaf (car pair)
(cadr pair))
(make-leaf-set (cdr pairs))))))
댓글 없음:
댓글 쓰기