scheme学习笔记

前话

开了SICP的坑,记一下lisp的方言scheme的一些东西

我的笔记本是manjaro,本来折腾了半天装不上mit-scheme,后面发现直接自带guile就蛮能用的(能用就行)

一个很重要的点就是求值的时候有两种规则:正则序和应用序,分别表示先替换后计算和先计算后替换。

一般的scheme解释器用的都是应用序,这样可以减少一部分重复运算(具体例子见后)

基本操作

记一个操作为 op,则 (op a1 a2 ... an) 的值就是分别递归对 a1, a2 ... an 求值,然后将 op 作用于它们的值

比如说 (/ 3 2) 就是 3/2。scheme中的整数相除默认是有理分数

分支

(cond
 	((cond1) (exp1))
 	((cond2) (exp2))
 	(else (exp3)))
(if
	(cond1) (exp1)
	(exp2))

与一般的过程不同,这俩都是从上到下分别对 (cond) 表达式求值,然后选择第一个为真的入口对 exp 求值

过程/函数

(define (max a b)
	(if (< a b)) b a)

condif 不同,函数在解释器中的行为是:先求值,后替换,即使是 condif 的封装也是这样。因此会出现一些不同的行为

define 同样可以用来定义变量,比如说

(define PI 3.1415926)

但我更愿意把这个看成是一个常函数

有了这些就足够写出和一般C程序设计基础课程里会出现的、等价的程序了!

然后就涉及到了一个素数测试的小程序

费马小定理

\(p\text{ is prime}\Rightarrow a^p\equiv a\pmod p\text{, }\forall 1\leqslant a< p\)

定理的初等证明比较麻烦,但是在群论里就能很简单地说明

\({\mathbb Z_p}^*=\left\{\;a|0\leqslant a<p\text{ and }\gcd(a,p)=1\;\right\}\),由 \(p\) 是质数容易有 \({\mathbb Z_p}^*=\left\{\;0,1,2\ldots p-1\;\right\}\)\({\mathbb Z_p}^*\) 是循环群

不妨取 \(|g|=p,\,g\in{\mathbb Z_p}^*\),这就是通常说的原根,也就是循环群的生成元。

这样 \(\forall a\in {\mathbb Z_p}^*\) 都存在 \(k\) 使得 \(a=g^k\),即 \(a^p={\left(g^k\right)}^p={\left(g^p\right)}^k=e^k=e=1\),这样就证明了费马小定理。

费马素性测试

假如给定了数 \(p\),那么随机一个数 \(a\),若 \(a^p\not\equiv a\pmod p\),那么 \(p\) 一定不是质数。

依据这一点,我们可以随机出若干基底,快速幂判断。

引理

\(p\) 是质数,则 \(\forall x\in {\mathbb Z_p}^*,\;x^2\equiv1\pmod p\)

\(p\mid (x-1)(x+1)\),即 \(p\mid (x-1)\)\(p\mid (x+1)\),又 \(x<p\),所以 \(x=1\)\(x=-1\)

Miller-Rabin素性测试

费马测试的问题在于存在一些数,它们能完全通过费马测试,但不是质数,比如说561

这样的数叫Carmichael数,561是最小的卡迈克尔数,这样的数字有无穷多个

因此我们不用 \(a^p\equiv a\pmod p\),而用 \(a^{p-1}\equiv 1\pmod p\),这样 \(p-1\) 就是偶数

考虑 \((p-1)=2^ab\)\(b\) 为素数,那么在快速幂的时候判断一下是否存在 \(1\) 的非平凡平方根就好了

代码

写的比较丑,不过真的很好玩!

(define (fermat_test p k)
	(define (exp_mod a x)
		(define (square x) (remainder (* x x) p))
		(cond
			((= x 0) 1)
			((= x 1) a)
			((= 0 (remainder x 2)) (square (exp_mod a (/ x 2))))
			((= 1 (remainder x 2)) (remainder (* a (square (exp_mod a (/ (- x 1) 2)))) p))))
	
	(define a (+ 1 (random (- p 1))))

	(cond
		((= k 0) #t)
		((= (exp_mod a p) a) (fermat_test p (- k 1)))
		(else #f)))

(define (miller_rabin_test p k)
	(define (!= a b) (not (= a b)))
	(define (square x)
			(define tmp (remainder (* x x) p))
			(if (and (!= x 1) (!= x (- p 1)) (= 1 tmp))
				0
				tmp	))

	(define (even? x) (= 0 (remainder x 2)))
	(define (get x)
		(if (even? x)
			(/ x 2)
			(/ (- x 1) 2)))

	(define (exp_mod a x)
		(cond
			((= x 0) 1)
			((= x 1) a)
			(else (square (exp_mod a (get x))))))
	
	(define a (+ 1 (random (- p 1))))

	(cond
		((= k 0) #t)
		((= (exp_mod a (- p 1)) 1) (miller_rabin_test p (- k 1)))
		(else #f)))

快排

非常naive的快排,用序列的第一项作为比较项,可以传入<或>来控制递增/递减

(define (sort rel ls)
	(define (merge la lb)
		(cond
			((null? la) lb)
			((null? lb) la)
			((rel (car la) (car lb)) (cons (car la) (merge (cdr la) lb)))
			(else (cons (car lb) (merge la (cdr lb))))))
	(define (split predicate lst)
		(if (null? lst) (cons #nil #nil)
			(let ((cur (car lst)) (result (split predicate (cdr lst))))
				(if (predicate cur)
					(cons (cons cur (car result)) (cdr result))
					(cons (car result) (cons cur (cdr result)))))))
	(if (not (< 1 (length ls))) ls
		(let ((res-pair (split (lambda (x) (not (rel x (car ls)))) ls)))
			(merge
				(cons (caar res-pair) (sort rel (cdar res-pair)))
				(sort rel (cdr res-pair))))))

符号求导

是'的用法的最后作业,写了完全括号的中缀求导(加法、乘法、幂函数法则)
不完全括号的写法大概是和sum-snd取列表的rest是一样的,因此这里先没写

(define (sum? exp) (and (pair? exp) (eq? '+ (cadr exp))))

(define (mul? exp) (and (pair? exp) (eq? '* (cadr exp))))

(define (exponentiation? exp) (and (pair? exp) (eq? '** (cadr exp))))

(define (=num? a b)
	(and (number? a) (number? b) (= a b)))

(define (make-sum a b)
	(cond
		((=num? a 0) b)
		((=num? b 0) a)
		((and (number? a) (number? b)) (+ a b))
		(else (list a '+ b))))

(define (make-mul a b)
	(cond
		((=num? a 1) b)
		((=num? b 1) a)
		((or (=num? a 0) (=num? b 0)) 0)
		(else (list a '* b))))

(define (make-exponentiation base exponent)
	(cond
		((=num? exponent 0) 1)
		((=num? exponent 1) base)
		(else (list base '** exponent))))

(define (sum-fst exp) (car exp))
(define (sum-snd exp) (caddr exp))

(define mul-fst sum-fst)
(define (mul-snd exp) (caddr exp))

(define base sum-fst)
(define (exponent exp) (caddr exp))

(define (deriv exp var)
	(cond
		((number? exp) 0)
		((symbol? exp) (if (eq? exp var) 1 0))
		((exponentiation? exp)
			(make-mul
				(make-mul
					(exponent exp)
					(make-exponentiation (base exp) (- (exponent exp) 1)))
				(deriv (base exp) var)))
		((sum? exp)
			(make-sum
				(deriv (sum-fst exp) var)
				(deriv (sum-snd exp) var)))
		((mul? exp) 
			(make-sum
				(make-mul (deriv (mul-fst exp) var) (mul-snd exp))
				(make-mul (mul-fst exp) (deriv (mul-snd exp) var))))))

eq?和equal?

书里面讲得比较奇怪,有点玄学

其实就和java里判断两个引用指向的对象是否相等是一样的
pA == pB 表示pApB指向同一个对象,这就和eq?的语义相同
pA.equals(pB)表示pApB指向的对象在内容意义上是相等的,在scheme中指的就是结构和对应位置的内容是同一个对象,也就是euqal?的含义
这也是为什么判断两个list用eq?会得到#f,这是因为两个list是不同的对象

数据导向的程序设计和可加性

这部分讲得挺好的。在写软件分析作业的时候就遇到了要判断某个对象Obj的类,然后分类操作的情况。然后那个lab要对11种子类进行讨论....这种时候简单的if-else-if分支就很麻烦了,对后期的维护和可读性都没有好处

书上讲的是对数据打标签来实现分流处理,事实上在OO语言就是把过程写进每个类的内部,然后用一个统一的覆写方法,这样实际上就是封装

posted @ 2021-08-15 13:10  jjppp  阅读(281)  评论(0编辑  收藏  举报