3

I am trying to compute the sum of a list with all the intermediate values along the way. My code is as follows but it does not work.

(: 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)))))

I am learning Racket from home on my own so any and all help will be appreciated!

testomg
  • 45
  • 6
  • Instead of `(append (list 0) ..)`, use `(cons 0 ...)`. – Cactus Nov 16 '16 at 04:11
  • you have wrong parenthesization in the very last line. the last expression is `(sums (cons a b))`, the `append` expression has no effect. (and btw `(append (list a) (list b) (list c))` is the same as `(list a b c)`.) – Will Ness Nov 16 '16 at 11:40
  • but then, your approach can't possibly work, because you start from 0 always, but the recursive call should start from an updated intermediate sum value. btw this kind of calculation is called *partial-sums* (and is abstracted in e.g. Haskell as the higher-order function `scanl`). – Will Ness Nov 16 '16 at 12:17

5 Answers5

2

So you want to write a function such that

(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))))

and so on (I am using very suggestive names, parenthesization and layout here, you'll see why).

Notice that all the result lists start with 0, and the rest is the same as the result from the previous line, except with the first input item added to each subsequent item.

In other words, we have

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

So first you'll need to implement

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

and then use that in the implementation of sums. To implement add-to-each, we need to observe that

(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) ()))

and so on.

Because you're saying you're doing this to learn Racket, I'll stop here and see if you can figure it out from here.

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • 1
    THANK YOU! That solved it! I was confused as to add-to-each was supposed to do. But now I got it to work! Thank you so so so much! – testomg Nov 16 '16 at 04:55
  • Note that in real code, you would write `add-to-each` using [`map`](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28lib._racket%2Fprivate%2Fmap..rkt%29._map%29%29) instead of rewriting it yourself for educational purposes; i.e. you'd write `(cons 0 (map (lambda (y) (+ x y))) (sums items))`. – Cactus Nov 16 '16 at 05:06
  • Of couse, at that point, you'd write `sums` using [`foldl`](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28lib._racket%2Fprivate%2Flist..rkt%29._foldl%29%29) or [`foldr`](https://docs.racket-lang.org/reference/pairs.html#%28def._%28%28lib._racket%2Fprivate%2Flist..rkt%29._foldr%29%29) instead of direct recursion. – Cactus Nov 16 '16 at 05:08
2

Here is a simple tail-recursive solution with a cost linear with the size of the list:

(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)

The auxiliary function subsums is given the list of partial sums so far and the list still to be processed. It conses over the first argument the sum of its first element and the first element of the list, and recurs over it and the rest of list. At the end the reversed first argument is the expected result.

Renzo
  • 26,848
  • 5
  • 49
  • 61
1

Here's another solution that uses continuation passing style. It also uses tail recursion and runs in constant time using a linear iterative process. It builds the resulting list in a forward direction using a lambda as an accumulator that represents the incomplete answer. Once all xs have been iterated thru, we apply the accumulator to the final sum, s – of course, while being mindful to also terminate the list with empty. This solution is particularly nice because we don't have to reverse the answer when we're done.

(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)

We're smart tho and we see that our λ expression is just a function composition of k and cons. We can rewrite it like this

(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)
Mulan
  • 129,518
  • 31
  • 228
  • 259
  • this builds a chain of *n* nested lambdas on the way forward along the list, and then applying the last lambda causes the result list to be built in the correct order from the end (so, *backward* direction), just like the nested `cons` calls would do. So it's linear space and time (not counting the *n* cons cells for the resulting list, of course). But the TRMC solution actually builds the result list from the top, in *forward* direction, i.e. from its head, with O(1) space overhead. – Will Ness Nov 17 '16 at 08:46
0

Following is an alternative:

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

Testing:

(sums (list 1 2 3 4)) 

Output:

'(0 1 3 6 10)
rnso
  • 23,686
  • 25
  • 112
  • 234
0

Often it's easier to solve a wider problem: generalize, solve the more general one, then specialize back to your original problem.

In pseudo-code,

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

Thus partial-sums-from can be implemented as a recursive function.

The resulting list can also be built iteratively, in top-down manner (cf. this), by side-effecting left fold performing the cons before the recursive call, as per the tail-recursion-modulo-cons discipline (see also ), so that it runs not only in linear time, but also in constant space, unlike all the other variants.

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181