最近自学MIT 6.5151课程时,先看了CS 61AS Unit 0作为准备。然后我按照ps0的要求阅读了SICP 1到2.1(以及相关的讲义)(也按照CS 61A笔记的要求阅读了2.2.1),然后软件灵活性设计(SDF)序言,第1章和部分内容方案附录。
目前我正在阅读 SDF 第 2 章并做练习 2.11 (f)。
f。另一个重要的扩展是构建 make-converter,以便它 可以根据需要从之前导出复合转换 注册的转换。这将需要图形搜索。
我想让由
unit-conversion-key-graph
构造的 unit-conversion-pairs
等于以下代码中的 ((tonne (kg g)) (celsius (kelvin fahrenheit)))
。
但是在
set-car!
中使用 fold
会抛出错误,因为 res
可能像 fold
中的 state变量一样使用(这与 python 中的
for i in range(10): i=i+1; print(i)
类似,但后者不会抛出错误并且 i=i+1
根本不执行任何操作。)。这是一项限制。它将抛出错误“;作为第一个参数传递给 car 的对象#!unspecified,不是正确的类型。” set-car!
之后的某个时间。
以下
unit-conversion-list
是为了与代码库中的此代码块保持一致,其中每个单元对都与一些转换过程配对。
(define (displayln x)
(newline)
(display x))
(define unit-conversion-list '(((celsius . kelvin) . 1) ((tonne . kg) . 2)
((tonne . g) . 3) ((celsius . fahrenheit) . 4)))
(define unit-conversion-pairs (map car unit-conversion-list))
(displayln unit-conversion-pairs)
; display:
; ((celsius . kelvin) (tonne . kg) (tonne . g) (celsius . fahrenheit))
;; https://stackoverflow.com/a/7382392/21294350
(define (list-set! lst k val)
(if (zero? k)
(begin
(displayln "set to")
(displayln val)
(set-car! lst val))
(list-set! (cdr lst) (- k 1) val)))
;; https://cookbook.scheme.org/find-index-of-element-in-list/
(define (list-index fn lst)
(displayln lst)
(let iter ((lst lst) (index 0))
(if (null? lst)
-1
(let ((item (car lst)))
(if (fn item)
index
(iter (cdr lst) (+ index 1)))))))
(define (adjacency-pairs-to-adjacency-list adjacency-pairs)
(fold
(lambda (adjacency-pair res)
(let* ((from (car adjacency-pair))
(to (cdr adjacency-pair))
(from-idx
(list-index
(lambda (adjacent-list-elem) (equal? from (car adjacent-list-elem)))
res)))
(if (>= from-idx 0)
(begin
(displayln from-idx)
(list-set! res from-idx (list from (list (cadr (list-ref res from-idx)) to)))
(displayln res)
(displayln "ending"))
(cons (list from to) res))))
'()
adjacency-pairs))
(define unit-conversion-key-graph (adjacency-pairs-to-adjacency-list unit-conversion-pairs))
(displayln unit-conversion-key-graph)
我们可以定义一个迭代函数来解决上述问题,其基本思想相同:
(define (adjacency-pairs-to-adjacency-list adjacency-pairs)
(let iter ((rest-adjacency-pairs adjacency-pairs)
(res '()))
(if (null? rest-adjacency-pairs)
res
(let* ((adjacency-pair (car rest-adjacency-pairs)))
(let* ((from (car adjacency-pair))
(to (cdr adjacency-pair))
(from-idx
(list-index
(lambda (adjacent-list-elem) (equal? from (car adjacent-list-elem)))
res)))
(let ((rest-adjacency-pairs (cdr rest-adjacency-pairs)))
(if (>= from-idx 0)
(begin
(displayln from-idx)
(list-set! res from-idx (list from (list (cadr (list-ref res from-idx)) to)))
(displayln res)
(displayln "ending")
(iter rest-adjacency-pairs res))
(iter rest-adjacency-pairs (cons (list from to) res))))))))
)
那么有没有一些类似于上面
fold
(两本书都推荐函数式编程)但没有上述限制的内部MIT计划函数可以使unit-conversion-key-graph
正确?
我想让由
构造的unit-conversion-key-graph
等于以下代码中的unit-conversion-pairs
。((tonne (kg g)) (celsius (kelvin fahrenheit)))
(以函数式而非命令式的方式)。
功能代码的一个重要部分是不可变数据结构——添加、删除或更改元素会返回一个包含更改的新结构(可能与原始结构共享结构以节省内存)。没有任何标准函数可以以这种方式更新列表,但很容易编写一个函数并将其与折叠原始对列表相结合:
;;; returns a new alist with the value of the element keyed with `key` replaced
;;; with the result of calling `f` on that value. If not present, adds a new
;;; mapping with a value created by calling `f` on `(default-thunk)`
;;; Uses `eq?` to ompare keys
(define (alistq-update alist key f default-thunk)
(cond
((null? alist)
(list (cons key (f (default-thunk)))))
((eq? key (caar alist))
(cons (cons key (f (cdar alist))) (cdr alist)))
(else
(cons (car alist) (alistq-update (cdr alist) key f default-thunk)))))
;;; Like alistq-update, but takes a default value instead of a procedure to
;;; call.
(define (alistq-update/default alist key f default-value)
(alistq-update alist key f (lambda () default-value)))
(define (adjacency-pairs->lists pairs)
(fold
(lambda (p alist)
(alistq-update/default alist (car p) (lambda (list) (cons (cdr p) list)) '()))
'() pairs))
;; ((celsius fahrenheit kelvin) (tonne g kg))
(displayln (adjacency-pairs->lists unit-conversion-pairs))
注意这会产生
((celsius fahrenheit kelvin) (tonne g kg))
而不是 ((tonne (kg g)) (celsius (kelvin fahrenheit)))
;在关联列表中顺序并不重要,并且删除额外的嵌套列表将使以后访问值变得更简单。如果第二种形式是你真正想要的,你可以做
(map (lambda (p) (list (car p) (cdr p))) '((celsius fahrenheit kelvin) (tonne g kg)))
得到它。
关联列表虽然有用且易于使用,但它是一种 O(N) 数据结构 - 随着存储在其中的事物数量的增加,使用它们进行操作的时间会以线性方式增加。列表越大,程序越慢。麻省理工学院计划附带许多不同的键值映射数据结构,其中包括一种具有高效不可变操作的权重平衡树。让我们尝试一下:
;;; No idea why these aren't provided by MIT Scheme already
(define (wt-tree->alist wt-tree)
(wt-tree/fold (lambda (key value list) (cons (cons key value) list))
'()
wt-tree))
(define (wt-tree/update wt-tree key f default)
(let ((current (wt-tree/lookup wt-tree key default)))
(wt-tree/add wt-tree key (f current))))
(define symbol-wt-tree-type (make-wt-tree-type symbol<?))
(define (adjacency-pairs->wt-tree pairs)
(fold
(lambda (p wt-tree)
(wt-tree/update wt-tree (car p) (lambda (list) (cons (cdr p) list)) '()))
(make-wt-tree symbol-wt-tree-type)
pairs))
(displayln (wt-tree->alist (adjacency-pairs->wt-tree unit-conversion-pairs)))