Search code examples
sml

How can I make my code more modular in SML?


I am programming in SML. My function takes an integer and then splices it into a list with a comma. For example digits 12345 -> [1,2.3,4,5]. My question is how do I make my code more modular. I kind of hardcoded my code. I would like it to work for an infinite amount of integers.

fun digits(m:int) =
  if m <10 then
    [m]
  else if m < 100 then
    [m div 10] @ [m mod 10]
  else if m > 100 andalso m < 1000 then
    [(m div 10) div 10] @[(m div 10) mod 10] @ [m mod 10]
  else if m > 1000 andalso m < 10000 then
    [((m div 10) div 10) div 10] @ [((m div 10) div 10) mod 10] @ [(m div 10) mod 10] @ [m mod 10]
  else if m > 10000 andalso m < 100000 then
    [(((m div 10) div 10) div 10) div 10] @ [((m div 10) div 10) div 10 mod 10] @ [((m div 10) div 10) mod 10] @ [(m div 10) mod 10] @ [m mod 10]
  else if m > 100000 andalso m < 1000000 then
    [((((m div 10) div 10) div 10) div 10) div 10] @ [(((((m div 10) div 10) div 10) div 10) mod 10) mod 10] @ [((m div 10) div 10) div 10 mod 10] @ [((m div 10) div 10) mod 10] @ [(m div 10) mod 10] @ [m mod 10]
  else if m > 1000000 andalso m < 10000000 then
    [(((m div 10) div 10) div 10) div 10 div 10 div 10] @ [(((m div 10) div 10) div 10) div 10 div 10 mod 10] @ [((m div 10) div 10) div 10 mod 10]@ [((m div 10) div 10) mod 10] @ [(m div 10) mod 10] @ [m mod 10]                                               
  else
    [(m div 10) div 10] @ [(m div 10) mod 10] @ [m mod 10]

Solution

  • Think about the problem you're trying to solve. There's no way you can hard-code all the possibilities.

    Let's consider a basic example: a single digit number. Clearly for any input less than 10 the result is just that number in a list.

    fun digits m =
      if m < 10 then
        [m]
    

    If the number is greater than 10, it could be two digits, or it could be more. We don't know. But can we find the smallest digit?

    fun smallestDigit m =
      if m < 10 then
        m
      else
        m mod 10
    

    So if we call smallestDigit 123 we get 3.

    And if we 123 div 10 we get 12. Turns out we get 2 if we call smallestDigit 12. We just need to repeat this until we've got all of our digits. In functional programming, this repetition is typically achieved with recursion.

    So let's put that together using recursion. What's critical is that we have an exit condition that stops the recursion, and that the other case converges toward that exit condition.

    fun digits m =
      if m < 10 then
        [m]
      else 
        let
          val smallestDigit = m mod 10
          val remainingDigits = m div 10
        in
          digits remainingDigits @ [smallestDigit]
        end
    

    For efficiency's sake we can use :: instead of @ with an accumulator.

    While the below achieves the goal of making the function tail-recursive, an integer is not going to be big enough that a stack overflow should be a real concern. More importantly, this avoids repeated calls to @ which would give the previous examples O(n^2) runtime complexity, instead yielding an O(n) runtime complexity.

    fun digits(n) =
      let 
        fun digits'(n, acc) =
          if n < 10 then n::acc
          else digits'(n div 10, (n mod 10)::acc)
      in
        digits'(n, [])
      end;