3

I have the following question "Given a list of integer pairs, write a function to return a list of even numbers in that list in sml".

this is what I've achieved so far

val x = [(6, 2), (3, 4), (5, 6), (7, 8), (9, 10)];

fun isEven(num : int) = 
    if num mod 2 = 0 then num else 0;

fun evenNumbers(list : (int * int) list) = 
    if null list then [] else 
    if isEven(#1 (hd list)) <> 0
    then if isEven(#2 (hd list)) <> 0
         then #1 (hd list) :: #1 (hd list) :: evenNumbers(tl list)
         else []
    else if isEven(#2 (hd list)) <> 0
         then #1 (hd list) :: evenNumbers(tl list)
         else [];

evenNumbers(x);

the result should be like this [6,2,4,6,8,10]

any help would be appreciated.

sshine
  • 15,635
  • 1
  • 41
  • 66
John Smith
  • 229
  • 3
  • 11

2 Answers2

4

I see two obvious problems.

If both the first and second number are even, you do

#1 (hd list) :: #1 (hd list) :: evenNumbers(tl list)

which adds the first number twice and ignores the second.

If the first number is odd and the second even, you do

#1 (hd list) :: evenNumbers(tl list)

which adds the number that you know is odd and ignores the one you know is even.

Programming with selectors and conditionals gets complicated very quickly (as you've noticed).

With pattern matching, you could write

fun evenNumbers [] = []
  | evenNumber ((x,y)::xys) = ...

and reduce the risk of using the wrong selector.

However, this still makes for complicated logic, and there is a better way.

Consider the simpler problem of filtering the odd numbers out of a list of numbers, not pairs.
If you transform the input into such a list, you only need to solve that simpler problem (and there's a fair chance that you've already solved something very similar in a previous exercise).

Exercise: implement this transformation. Its type will be ('a * 'a) list -> 'a list.

Also, your isEven is more useful if it produces a truth value (if you ask someone, "is 36 even?", "36" is a very strange answer).

fun isEven x = x mod 2 = 0

Now, evenNumbers can be implemented as "just" a combination of other, more general, functions.

molbdnilo
  • 64,751
  • 3
  • 43
  • 82
2

So running your current code,

- evenNumbers [(6, 2), (3, 4), (5, 6), (7, 8), (9, 10)];
val it = [6,6,3,5,7,9] : int list

suggests that you're not catching all even numbers, and that you're catching some odd numbers.

The function isEven sounds very much like you want to have the type int -> bool like so:

fun isEven n =
    n mod 2 = 0

Instead of addressing the logic error of your current solution, I would like to propose a syntactically much simpler approach which is to use pattern matching and fewer explicit type annotations. One basis for such a solution could look like:

fun evenNumbers [] = ...
  | evenNumbers ((x,y)::pairs) = ...

Using pattern matching is an alternative to if-then-else: the [] pattern is equivalent to if null list ... and the (x,y)::pairs pattern matches when the input list is non-empty (holds at least one element, being (x,y). At the same time, it deconstructs this one element into its parts, x and y. So in the second function body you can express isEven x and isEven y.

As there is a total of four combinations of whether x and y are even or not, this could easily end up with a similarly complicated nest of if-then-else's. For this I might do either one of two things:

  1. Use case-of (and call evenNumbers recursively on pairs):

    fun evenNumbers [] = ...
      | evenNumbers ((x,y)::pairs) =
          case (isEven x, isEven y) of
               ... => ...
             | ... => ...
    
  2. Flatten the list of pairs into a list of integers and filter it:

    fun flatten [] = ...
      | flatten ((x,y)::pairs) = ...
    
    val evenNumbers pairs = ...
    
sshine
  • 15,635
  • 1
  • 41
  • 66