【发布时间】:2014-11-30 00:02:00
【问题描述】:
给定一个 s 表达式 '((a . b) . (c . d)) 和一个列表 '(e f g h),我如何遍历 s 表达式创建一个形状相同但元素取自列表的 s 表达式?例如,对于上面的 s 表达式和列表,结果将是 '((e . f) g . h)?
【问题讨论】:
标签: scheme
给定一个 s 表达式 '((a . b) . (c . d)) 和一个列表 '(e f g h),我如何遍历 s 表达式创建一个形状相同但元素取自列表的 s 表达式?例如,对于上面的 s 表达式和列表,结果将是 '((e . f) g . h)?
【问题讨论】:
标签: scheme
按从左到右的顺序遍历一对树并不是特别困难,因为 car 和 cdr 可以让你到达两边,而 cons 可以把事情重新组合起来。像这样的问题中棘手的部分是,要“替换”树右侧的元素,您需要知道在处理树的左侧时使用了多少可用输入。因此,这是一个过程 reshape,它采用 template(具有您想要的 shape 的树)和一个 list 在新树中使用的元素。它将新树和列表中的任何剩余元素作为多个值返回。这意味着在对一对的递归调用中,您可以轻松获得新的左子树和右子树以及剩余的元素。
(define (reshape template list)
;; Creates a tree shaped like TEMPLATE, but with
;; elements taken from LIST. Returns two values:
;; the new tree, and a list of any remaining
;; elements from LIST.
(if (not (pair? template))
(values (first list) (rest list))
(let-values (((left list) (reshape (car template) list)))
(let-values (((right list) (reshape (cdr template) list)))
(values (cons left right) list)))))
(reshape '((a . b) . (c . d)) '(e f g h))
;=> ((e . f) g . h)
;=> ()
(reshape '((a . b) . (c . d)) '(e f g h i j k))
;=> ((e . f) g . h)
;=> (i j k) ; leftovers
【讨论】:
reshape 和template,它们是更好的名称,但请避免使用list 作为参数名称。另外值得一提的是,let-values 在某些解释器中可能不可用。
let-values 捕获结果并返回第一个值。但请注意,这不适用于所有输入,例如试试这个:(reshape '(a b) '(c d))
(a b) 是树(a b . '())。有 三个 东西要替换,而不是两个,所以 (c d) 是不够的元素。我认为,由于问题是关于替换 s 表达式而不是列表,因此考虑树而不是列表是合适的。
我假设您要创建一个新的 s-表达式,其形状与第一个参数给出的 s-表达式相同,但使用第二个参数中的列表元素。
如果这是正确的,这是一种可能的解决方案,它使用列表来保存我们在替换列表中的位置,并使用 Racket 的 begin0 来保持列表更新(如果在您的解释器中不可用,请使用 let,如由 Chris 和 Joshua 在 cmets 中建议):
(define (transform sexp lst)
(let loop ((sexp sexp)) ; the s-expression list to be traversed
(cond ((null? sexp) '()) ; if it's empty, we're finished
((not (pair? sexp)) ; if it's an atom
(begin0 ; then (alternatively: use a `let`)
(car lst) ; return first element in replacements list
(set! lst (cdr lst)))) ; and update replacements to next element
(else ; otherwise advance recursion
(cons (loop (car sexp)) ; over both the `car` part of input
(loop (cdr sexp))))))) ; and the `cdr` part
例如:
(transform '((a . b) . (c . d)) '(e f g h))
=> '((e . f) g . h)
(transform '((a . b) (c d (x y) . z) . t) '(e f g h i j k m))
=> '((e . f) (g h (i j) . k) . m)
【讨论】:
set! 的使用总体上令人反感,但目前我没有更好的解决方案。不过,最好不要使用list-ref。你可以改用(let ((result (car lst))) (set! lst (cdr lst)) result) 之类的方法。 (Common Lisp 通常有一个 prog1 宏来处理这类事情,尽管在这种特定情况下,它的 pop 宏更加直接。)
(set! lst (rest lst)) 更容易吗?那么结果的元素总是在 lst 的前面可用。
list-ref 是一个深夜黑客:P。我们不知道begin0 是否在 OP 的解释器中可用,如果没有,我们总是可以按照建议使用let。
解决方法和我的previous answer类似:
(define (transform sxp lst)
(let loop ((sxp sxp))
(cond ((null? sxp) sxp)
((pair? sxp) (cons (loop (car sxp)) (loop (cdr sxp))))
(else (begin0 (car lst) (set! lst (cdr lst)))))))
然后
> (transform '((a . b) . (c . d)) '(e f g h))
'((e . f) g . h)
【讨论】: