关于length
写Scheme代码时,经常要判断表的长度。长度为0时一个操作,长度为1时,另一个操作,长度为2时,第三种操作 ...。
最容易想到的方法是
(define (length=? m l)
(= m (length l)))
但这个 length=? 很低效。它要先计算表的长度n。length 的复杂度是O (n),。这个函数的复杂度也是O (n)。
改进一
实际上表的长度等不等于m,在计算表的长度*时*,就可以确定,不必等到计算表的长度*后*。【不多不少原则】
length 可以这样写
(define (length l)
(if (null? l)
0
(+ 1 (length (cdr l)))))
要在计算 length 的时候就判断其长度是否等于 m ,就可以这样写
(define (length=? m l)
(if (null? l)
(zero? m)
(length=? (- m 1) (cdr l))))
[根据 length 来写 length=? 是很简单的小技巧]
改进二
上面的改进是考察表的长度得到的。这里我们把另外一参数 m 也考虑进去。高效的 length=? 的复杂度应该是 min (m,n)。也就是说当m比n小时,m步就可以判定了。
(define (length=? m l)
(cond ((null? l) (zero? m))
((zero? m) #f)
(else (length=? (- m 1) (cdr l)))))
类似地,要判断表的长度是否相等就可以这样
(define (same-length? l1 l2)
(cond ((null? l1) (null? l2))
((null? l2) #f)
(else (same-length? (cdr l1) (cdr l2)))))
如果要判断两个以上的表的长度是否相等呢?
方法应该还是一样的。
1. 如果其中一个表是空表,看其他表是否也都是空表。
2. 否则去掉所有表的头部,递归。
这就要两个辅助函数 any? 和 all?
(define (any? p? ls)
(define anyloop
(lambda (ls)
(if (null? ls)
#f
(if (p? (car ls))
#t
(anyloop (cdr ls))))))
(anyloop ls))
(define (all? p? ls)
(define allloop
(lambda (ls)
(if (null? ls)
#t
(if (not (p? (car ls)))
#f
(allloop (cdr ls))))))
(allloop ls))
(define (same-length? l1 l2 . ls)
(define args (cons l1 (cons l2 ls)))
(cond ((any? null? args)
(all? null? args))
(else
(apply same-length?
(map cdr args)))))
不过这个函数的复杂度,我就不愿意分析了,因为不会呵呵。
length=? 也可以作类似扩展。