schemeracketr5rs

Scheme groupSum Function


I'm trying to write a Scheme function that takes two parameters, a list of numbers and another number, and returns true if a subset of the list adds up to that number.

Example Output:

> (groupSum '(1 2 5) 7)
> #t
> (groupSum '(1 2 5) 4)
> f

So far, all I am able to do is get the sum of all the numbers in a list. What do I need to do with my code to reach the example output?

Here's my code so far:

(define (groupSum elemList)
  (if
    (null? elemList)
    0
    (+ (car elemList) (groupSum (cdr elemList)))
  ))

What my code returns:

> (groupSum '(1 2 5))
> 8

Solution

  • So what you ask is, does a subset of a given list exist (in a positional sense, i.e. keeping duplicates, if any) that adds up to a given number.

    To go through subsets is the job of the powerset function, except we want to jump out of it and return #t as soon as we detect success. Otherwise, just return #f in the end.

    Augmenting a powerset function from one of my other answers on this site, we get

    (define (group-adds-to aL aN)
      (call/cc
       (lambda (......)  ;; 2.
         (letrec 
            ([...... (lambda (aL)  ;; 4.
               (cond
                 [(empty? aL) (list empty)]
                 [else
                  (add-into   ;; 5b.
                            (powerset (rest aL))  ;; 3b.
                            (first aL))]))]
    
             [...... (lambda (r a)  ;; 6.       ; `r` is recursive result, `a` an element
               (cond
                 [(empty? r) empty]             ; nothing to add `a` to
                 [else
                  (cons (add a (first r)) ;; 7. ; either add `a`,
                        (cons (first r)         ;   or not, to the first in `r`
                              (add-into  ;; 5a. ; and recursively proceed
                               (rest r)         ;   to add `a` into the rest of `r`
                               a )))]))]
    
             [......                      ;; 8.
                      (lambda (......)       ;; 9...
                         (let ([sum ......])   ;; 10...
                           (if (= sum aN)  ;; 11.
                               (return #t)  ;; 1.
                               sum)))])
    
           (powerset aL)  ;; 3a.
           #f))))   ;; 12.
    

    Fill in the blanks!

    Testing:

    > (group-adds-to '(1 2 5) 7)
    #t
    > (group-adds-to '(1 2 5) 4)
    #f
    

    It takes a few trivial changes to make this #r5rs compliant, if you need it to be.

    Of course this code can be streamlined and shortened with the use of more built-in functions. It is quite simple to get rid of call/cc here, too. You can also get fancy and add checks to shorten the interim lists, detecting and removing such subgroups (subsets) that can't possibly contribute to a successful outcome.


    OK, how to fill in the blanks.

    1. return? what is that?
    2. it is an escape continuation set up for us by call/cc. When (if) it is called, as ;; 1., the value #t is returned right away as the final result of the whole group-adds-to function call, no matter how deep inside the recursion we are at that point. The recursion is just abandoned, and the result is just returned.
      And if return was never called, because the equality (= sum aN) ;; 11. was never detected, the recursion will end its course and #f will be returned normally, as the last expression in a function's body, at ;; 12..
    3. a,b. powerset? what is that?
    4. it is an internal function we've defined
    5. a,b. add-into?
    6. it too, defined here.
    7. and add?
    8. yep.
    9. Do it yourself. See how it is used, at ;; 7, what its arguments are, what must it return.
    10. Do It Yourself. You can do it!