Search code examples
smlsmlnj

Generate a Real list from a Int list in SML


Hello every body im training some SMLs and im creating a code to get deviation of a int list . in the process of it , i need to get a Real list out of some numbers in a int list , which it doesnt let me get them. heres my code :

fun mean [] = 0.0
  | mean (first::rest) = 
      let 
        fun sum [] = 0
          | sum (x::xs) = x + sum xs
          
        fun counter [] = 0
          | counter (y::ys) = 1 + counter ys       
      in 
        Real.fromInt (sum (first::rest)) / Real.fromInt (counter (first::rest))
      end;

fun deviation [] = 0.0
  | deviation (first::rest) = 
      let 
        fun diff (x::xs) = (x - mean (x::xs)) :: diff xs;
      in
        diff (first , first::rest) + deviation rest
      end;

the problem is here :

fun diff (x::xs) = (x - mean (x::xs) ) :: diff xs;

Solution

  • diff is a recursive function, but the base case is never defined. When you try to run diff on an empty list, you will get a pattern match error.

    You also define diff to accept a list, but you call it with a tuple.

    You define diff as returning a list, given that you are using ::, but then you use addition on the result of that function, which will not work.

    Improving mean

    You can simplify your sum and counter functions with folds.

    fun mean [] = 0.0
      | mean lst = 
          let 
            val sum = foldl op+ 0 lst
            val counter = foldl (fn (_, c) => c + 1) 0 lst      
          in 
            Real.fromInt sum / Real.fromInt counter
          end;
    

    But this requires iterating the entire list twice, when both pieces of information can be ascertained at the same time.

    fun sumLen(lst) =
      foldl (fn (x, (sum, len)) => (sum+x, len+1)) (0, 0) lst
    

    mean can now be implemented as:

    fun mean(lst) =
      let 
        val (sum, len) = sumLen(lst)
      in
        Real.fromInt sum / Real.fromInt len
      end
    

    Deviation

    To get the differences from the mean for a list, you need only use map.

    fun diffs(lst) =
      let 
        val m = mean(lst)
      in
        map (fn x => Real.fromInt x - m) lst
      end
    

    Consider evaluating the following.

    diffs [1, 2, 3, 4, 5, 6, 7, 8]
    

    The result is:

    [~3.5, ~2.5, ~1.5, ~0.5, 0.5, 1.5, 2.5, 3.5]
    

    From there you can use map and Math.pow to square those differences, foldl to sum them, divide by the length of the list, and then Math.sqrt to get the standard deviation.