I'm stuck trying to implement this function in Racket and ML

211 views Asked by At

I have this assignment:

Consider a list where every element is a nested list of length 2. The first element of each nested list is either a 0 or a 1. The second element of each nested list is some integer. An example input in Scheme is written below.

'((0 1) (1 2) (1 3) (0 4) (0 3))

For the purposes of this question, let’s call the first element of each nested list the key and the second element of the nested lists the value. Now consider a function, count_by_cat, that takes such a list as input and yields a two-element list where

  • the first element is the sum of the values of all nested lists with 0 as the key, and
  • the second element is the sum of the values of all nested lists with 1 as the key

Implement count_by_cat in

  • (a) Racket, and
  • (b) ML.

It might be helpful to create helper functions. Also do not forget about map and filter (filter is not a built-in in ML).

I'm new to Racket and ML. I'm stuck at using accessing lists and stuff in Racket. Some help with ML would also be great.

2

There are 2 answers

0
Shawn On

The hint about map and filter is a bit of a red herring (As well as being wrong about them not being in ML languages); a helper function is very useful, but it's a fold you want, which basically calls a function with the current element of a list and an accumulator value, and uses what it returns as the accumulator for the next element and ultimately the return value of the entire fold.

For example, in ocaml, using a fold to sum a list of integers:

let sum = List.fold_left (+) 0 [ 1; 2; 3; 4; 5 ];;

((+) is the function version of the infix operator +).

Or your problem in Racket, using the for/fold comprehension, and returning two values instead of a two-element list, which is more idiomatic in Racket and Scheme (In ocaml and presumably Standard ML, you'd return a two-element tuple instead of a list, and take a list of tuples).

(define (count-by-cat lol)
  (for/fold ([zero-sum 0]
             [one-sum 0])
            ([elem (in-list lol)])
    (case (first elem)
      [(0) (values (+ zero-sum (second elem)) one-sum)]
      [(1) (values zero-sum (+ one-sum (second elem)))])))

You could also use foldl, but for/fold is often easier to use, especially when working with multiple values or other non-trivial cases.

0
Will Ness On

Well you're suggested to use higher order functions like map and filter.

And indeed it is easy to manipulate our data as a whole instead of focusing, mentally, on one element at a time. Which approach (i.e. the former one) some might argue, is the essence of functional programming paradigm.

In Racket,

(define (count_by_cat ls)

"return a two-element list"

    (list
      ;; .....
      ;; ..... ))

where the first element is the sum of

      (sum-of

the values of all nested lists

        (map value

with 0 as the key

           (filter (lambda (x) (= 0 (key x))) ls)))

and the second element is the sum of the values of all nested lists

      (sum-of
        (map value

with 1 as the key

           (filter (lambda (x) (= 1 (key x))) ls))) ))

And so we need to define the helper functions accessing the nested lists,

(define (key n) (car n))

(define (value n) (cadr n))

and also the sum-of function, for summing up the numerical values in a list:

(require math/base)

(define (sum-of ls)
    (sum ls))

This was easy to write, and will be easy to maintain.

The more performant code would do everything in one pass over the input, like is shown in the other answer. It has all the logic fused into one loop body. It could be more brittle to write and maintain.