已编辑
非常感谢@WillNess 指出并修复了隐藏在原始代码中的错误。这是基于他的代码(逐步推导)的更正实现,评论并为球拍惯用:
(define (replace-one lst a b)
(let loop ([lst lst] ; input list
[f #f] ; have we made the first replacement?
[k (lambda (ls f) ls)]) ; continue with results: list and flag
(cond
(f ; replaced already:
(k lst f)) ; continue without changing anything
((empty? lst) ; empty list case
(k lst f)) ; go on with empty lst and flag as is
((not (pair? lst)) ; - none replaced yet - is this an atom?
(if (eq? lst a) ; is this the atom being searched?
(k b #t) ; replace, continue with updated flag
(k lst f))) ; no match, continue
(else ; is this a list?
(loop (first lst) ; process the `car` of `lst`
f ; according to flag's value, and then
(lambda (x f) ; accept resulting list and flag, and
(loop (rest lst) ; process the `cdr` of `lst`
f ; according to new value of flag,
(lambda (y f) ; getting the results from that, and then
(if f ; - if replacement was made -
(k ; continuing with new list, built from
(cons x y) ; results of processing the two branches,
f) ; and with new flag, or with
(k lst f)))))))))) ; the old list if nothing was changed
请注意,使用了一个成功延续(k
在上面的代码中调用),它接受两个结果值:列表和标志。初始延续只返回最终结果列表,并丢弃最终标志值。我们还可以返回标志,以表明是否已经进行了替换。它在内部用于尽可能多地保留原始列表结构,与持久数据类型一样(如本答案所示)。
最后,始终测试您的代码:
; fixed, this wasn't working correctly
(replace-one '((((1 2) 3 4) a) 6) 'a 'b)
=> '((((1 2) 3 4) b) 6)
(replace-one '(((-))) '- '+)
=> '(((+)))
(replace-one '((-) - b) '- '+)
=> '((+) - b)
(replace-one '(+ 1 2) '+ '-)
=> '(- 1 2)
(replace-one '((+) 1 2) '+ '-)
=> '((-) 1 2)
(replace-one '(1 2 ((+)) 3 4) '+ '-)
=> '(1 2 ((-)) 3 4)
(replace-one '() '+ '-)
=> '()
(replace-one '(1 2 ((((((+ 3 (+ 4 5)))))))) '+ '-)
=> '(1 2 ((((((- 3 (+ 4 5))))))))