【问题标题】:Sum of a List with all Intermediate Values具有所有中间值的列表的总和
【发布时间】:2017-03-30 03:24:48
【问题描述】:

我正在尝试计算一个包含所有中间值的列表的总和。我的代码如下,但它不起作用。

(: sums : (Listof Integer) -> (Listof Integer))
;; compute the sum of a list,
;; produce all the intermediate sums along the way
;; start with 0
;; (sums (list 1 2 3 4)) ==> (list 0 1 3 6 10)
(define (sums x)
  (match x
    ('() (list 0))
    ((cons hd '()) (append (list 0) (list (+ 0 hd))))
    ((cons hd (cons a b))
     (append (list 0) (list (+ 0 hd)) (list (+ 0 hd a))) (sums (cons a b)))))

我正在自己在家学习球拍,因此我们将不胜感激!

【问题讨论】:

  • (append (list 0) ..)代替(cons 0 ...)
  • 你在最后一行有错误的括号。最后一个表达式是(sums (cons a b))append 表达式无效。 (顺便说一句,(append (list a) (list b) (list c))(list a b c) 相同。)
  • 但是,您的方法可能行不通,因为您总是从 0 开始,但递归调用应该从更新的中间总和值开始。顺便说一句,这种计算称为 partial-sums(在 Haskell 中被抽象为高阶函数 scanl)。

标签: functional-programming scheme racket typed-racket


【解决方案1】:

所以你想写一个这样的函数

(sums (list))       = (list 0) ;; Your implementation has this right
(sums (list x))     = (list 0 x)                   = (list                     0             (+ x 0))
(sums (list y x))   = (list 0 y (+ y x))           = (list        0       (+ y 0)       (+ y (+ x 0)))
(sums (list z y x)) = (list 0 z (+ z y) (+ z y x)) = (list 0 (+ z 0) (+ z (+ y 0)) (+ z (+ y (+ x 0))))

等等(我在这里使用非常暗示性的名称、括号和布局,你会明白为什么)。

请注意,所有结果列表都以0 开头,其余与上一行的结果相同,除了第一个输入项添加到每个后续项。

换句话说,我们有

(sums (car x items)) = (cons 0 (add-to-each x (sums items)))

所以首先你需要实现

(: add-to-each : Integer -> (Listof Integer))
(define (add-to-each x items)
   ...)

然后在sums 的实现中使用它。要实现add-to-each,我们需要注意这一点

(add-to-each x                   ())  =                               ()
(add-to-each x (cons y1          ())) =                (cons (+ x y1) ())
(add-to-each x (cons y2 (cons y1 ())) = (cons (+ x y2) (cons (+ x y1) ()))

等等。

因为你说你这样做是为了学习 Racket,所以我就停在这里,看看你能不能从这里弄清楚。

【讨论】:

  • 谢谢!那解决了!我对 add-to-each 应该做的事情感到困惑。但现在我开始工作了!非常感谢!
  • 请注意,在实际代码中,您将使用map 编写add-to-each,而不是出于教育目的自己重写它;即你会写(cons 0 (map (lambda (y) (+ x y))) (sums items)).
  • 当然,此时您可以使用foldlfoldr 来编写sums,而不是直接递归。
【解决方案2】:

这是一个简单的尾递归解决方案,其成本与列表的大小成线性关系:

(define (sums l)
  (define (subsums prefix l)
    (if (null? l)
        (reverse prefix)
        (subsums (cons (+ (car prefix) (car l)) prefix) (cdr l))))
  (subsums '(0) l))

(sums '(2 5 3)) ; => (0 2 7 10)

辅助函数subsums 给出了到目前为止的部分和列表,该列表仍有待处理。它在第一个参数上使用它的第一个元素和列表的第一个元素的总和,并在它和列表的其余部分上递归。最后,反转的第一个参数是预期的结果。

【讨论】:

    【解决方案3】:

    这是另一个使用延续传递样式的解决方案。它还使用尾递归并使用线性迭代过程在恒定时间内运行。它使用 lambda 作为表示不完整答案的累加器,以正向构建结果列表。一旦所有xs 都被迭代通过,我们将累加器应用于最终总和s——当然,同时注意也用empty 终止列表。这个解决方案特别好,因为我们完成后不必反转答案。

    (define (sums xs)
      (let loop ((s 0) (xs xs) (k identity))
        (if (empty? xs)
            (k (cons s empty))
            (loop (+ s (car xs)) (cdr xs) (λ (rest) (k (cons s rest)))))))
    
    (sums '(1 2 3 4))
    ; => '(0 1 3 6 10)
    

    我们很聪明,我们看到我们的 λ 表达式只是 kcons 的函数组合。我们可以这样改写

    (define (sums xs)
      (let loop ((s 0) (xs xs) (k identity))
        (if (empty? xs)
            (k (cons s empty))
            (loop (+ s (car xs)) (cdr xs) (compose k (curry cons s))))))
    
    (sums '(1 2 3 4))
    ; => '(0 1 3 6 10)
    

    【讨论】:

    • 这会在列表前进的路上构建一个 n 嵌套 lambda 链,然后应用最后一个 lambda 会导致结果列表以正确的顺序从结束(所以,向后方向),就像嵌套的cons 调用一样。所以它是线性空间和时间(当然,不计算结果列表的 n 个 cons 单元格)。但 TRMC 解决方案实际上是从顶部构建结果列表,在 forward 方向,即从其头部开始,空间开销为 O(1)。
    【解决方案4】:

    以下是另一种选择:

    (define (sums l)
      (let loop ((l l)
                 (sl '(0)))
        (if (empty? l) (reverse sl)
            (loop (rest l)
                  (cons
                   (+ (first l)
                      (first sl))
                   sl)
                  ))))
    

    测试:

    (sums (list 1 2 3 4)) 
    

    输出:

    '(0 1 3 6 10)
    

    【讨论】:

      【解决方案5】:

      解决更广泛的问题通常更容易:概括,解决更普遍的问题,然后专精回到原来的问题。

      在伪代码中,

      partial-sums (x . xs) = [ 0 x ... ] 
                            = ( 0 . [x ...] )
                            = ( 0 . partial-sums-from (0 + x) xs )
                            = partial-sums-from 0 (x . xs)
      

      因此partial-sums-from 可以实现为递归函数。

      结果列表也可以迭代地构建,在top-down manner(参见this)中,通过执行cons的副作用左折叠递归调用之前,根据tail-recursion-modulo-cons 纪律(另见 ),因此它不仅在线性时间中运行,而且在恒定空间中运行,这与所有其他变体不同。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多