这里的真正问题似乎是需要一个适用于嵌套输入列表的解决方案。为此,可以编写一个map-nested 过程。这是一个类似于普通map 过程的简化版本的过程,但它适用于嵌套列表:
(define (map-nested proc xss)
(if (null? xss)
'()
(let ((xs (car xss)))
(if (pair? xs)
(cons (map-nested proc xs)
(map-nested proc (cdr xss)))
(cons (proc xs)
(map-nested proc (cdr xss)))))))
这与通常的map 不同的一个方式是map-nested 只接受一个列表作为输入;一个典型的map 过程采用任意数量的可以映射的列表。
map-nested 的上述实现不是尾递归的,这对于大型、深度嵌套的列表可能是个问题。将其重写为迭代(尾递归)过程并不太麻烦:
(define (map-nested proc xss)
(let iter ((xss xss)
(result '()))
(if (null? xss)
(reverse result)
(let ((xs (car xss)))
(if (pair? xs)
(iter (cdr xss)
(cons (map-nested proc xs) result))
(iter (cdr xss)
(cons (proc xs) result)))))))
这里的result 是一个累加器,用于跟踪要返回的结果,从而使递归调用无需保留堆栈帧。请注意,result 在返回之前必须反转,因为输入中的元素从输入列表的前面开始被 consed 到 result 的前面。这里使用了named let 形式;这是在另一个同时适用于 Scheme 和 Racket 的函数中设置本地过程的便捷方式。
要完成OP的问题,我们只需要使用map-nested;以上任何一个版本都可以使用:
;;; `substs` is an a-list of the form `((a . x) (b . y) ...)`
(define (replace-elts xss substs)
(map-nested (lambda (x)
(let ((subst-pair (assoc x substs)))
(if subst-pair ; subst-pair is #f if x is not found
(cdr subst-pair) ; otherwise, subst-pair is a dotted pair
x)))
xss))
这里,map-nested 负责处理输入列表,无论它是否嵌套。给proc 的过程负责在substs 中查找替换,假定它是由点对 组成的关联列表。如果需要两个元素的正确列表而不是点对,则可以简单地将cdr 更改为cadr。当subst 不提供替换输入xss 中的值时,该值保持不变。
replace-elts 可以通过多种方式更改为或多或少方便,具体取决于实际使用条件。
示例交互:
nested-map.rkt> (replace-elts '(a (b (c d) e))
'((a . 1) (b . 2) (c . 3) (d . 4) (e . 5)))
'(1 (2 (3 4) 5))
nested-map.rkt> (replace-elts '(a (b (c d) e))
'((a . 1) (b . 2) (c . 3) (d . 4)))
'(1 (2 (3 4) e))
nested-map.rkt> (replace-elts '(a and (b or c))
'((a . #t) (b . #f) (c . #t)))
'(#t and (#f or #t))