【问题标题】:How to construct a tree of particular shape with elements from a list如何使用列表中的元素构建特定形状的树
【发布时间】:2014-11-30 00:02:00
【问题描述】:

给定一个 s 表达式 '((a . b) . (c . d)) 和一个列表 '(e f g h),我如何遍历 s 表达式创建一个形状相同但元素取自列表的 s 表达式?例如,对于上面的 s 表达式和列表,结果将是 '((e . f) g . h)?

【问题讨论】:

    标签: scheme


    【解决方案1】:

    按从左到右的顺序遍历一对树并不是特别困难,因为 carcdr 可以让你到达两边,而 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
    

    【讨论】:

    • 我喜欢reshapetemplate,它们是更好的名称,但请避免使用list 作为参数名称。另外值得一提的是,let-values 在某些解释器中可能不可用。
    • 另外,这不适用于所有输入,例如在我的答案中使用第二个示例对其进行测试。
    • 这个方法效果很好,但是否可以只返回新树?
    • @Jacob 使用let-values 捕获结果并返回第一个值。但请注意,这不适用于所有输入,例如试试这个:(reshape '(a b) '(c d))
    • @ÓscarLópez 列表(a b) 是树(a b . '())。有 三个 东西要替换,而不是两个,所以 (c d) 是不够的元素。我认为,由于问题是关于替换 s 表达式而不是列表,因此考虑树而不是列表是合适的。
    【解决方案2】:

    我假设您要创建一个新的 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 宏更加直接。)
    • @ChrisJester-Young 我认为简单地让函数返回多个值(新树和输入列表中的剩余元素)会更优雅一些。然后,您可以从递归调用中获得继续所需的所有信息。我已经用这种方法添加了一个答案。
    • 即使在这种状态可变的方法中,为什么还要使用 list-ref 和索引?更新 lst 不会(即(set! lst (rest lst)) 更容易吗?那么结果的元素总是在 lst 的前面可用。
    • 好的,伙计们,我更新了我的答案。当然,list-ref 是一个深夜黑客:P。我们不知道begin0 是否在 OP 的解释器中可用,如果没有,我们总是可以按照建议使用let
    【解决方案3】:

    解决方法和我的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)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-09
      相关资源
      最近更新 更多