Mark Needham

Thoughts on Software Development

Scala: 99 problems

with 7 comments

My colleague Liz Douglass and I have been playing around with Scala and Liz recently pointed out Phil Gold's 'Ninety Nine Scala Problems' which we've been working through.

One in particular which is quite interesting is number 7 where we need to flatten a nested list structure.

Therefore given this input:

flatten(List(List(1, 1), 2, List(3, List(5, 8))))

We would expect this output:

res0: List[Any] = List(1, 1, 2, 3, 5, 8)

I tried this out on my own using recursion but kept ending up creating a stack overflow by writing code that never terminated!

We decided to try it out while pairing together using a more imperative approach so that we could get something that actually worked to begin with and ended up with this:

def flatten[A](list : List[A]) : List[A] =  {
 
	var flattenedList : List[A] = Nil
	for(item <- list) {
		item match {
		   case x : List[A] => flattenedList = flatten(x) ::: flattenedList
           case _    => flattenedList = item :: flattenedList
		}
	}
	flattenedList
}

It works and we've got a bit of recursion going on but we're still mutatating the state of a variable so it feels like we're missing out on the strength of a language which provides us with the ability to work in a functional way.

Our second attempt made use of the knowledge we gained from this first attempt with respect to that cases that existed for flattening the list and we made use of a technique which I picked up while learning a bit of F# – the inner function which keeps an accumulator with the flattened list on each recursion:

def flatten[A](list : List[A]) : List[A] = {
	def flat[A](rest : List[A], flattenedList : List[A]) : List[A] = rest match {
			case Nil => flattenedList
			case (head:List[A])::tail =>  flat(tail, flat(head, List()) ::: flattenedList)
			case head::tail => flat(tail, head :: flattenedList) 		
	}
 
	flat(list, Nil).reverse
}

I like the fact that we don't have to mutate any state with this solution but the second case statement is pretty difficult to understand.

We had to distinguish between whether the head of the list was another list or just an element in order to determine whether we needed to break that value down further or just add it to the new list.

Is there a better way to do that than to explicitly state the type of head as we've done here?

Another colleague, Ben Barnard, came over to work with us on the next iteration of this solution which was somewhat driven by seeing how Phil Gold had solved the kth item problem for which he describes a solution which narrows down the initial list each time while still calling the same function:

  def nth[T](n: Int, l: List[T]): T = (n, l) match {
    case (0, e :: _   ) => e
    case (n, _ :: tail) => nth(n - 1, tail)
    case (_, Nil      ) => throw new NoSuchElementException
  }

In this case when we reduce the list we are searching on we also reduce the index that we are looking for by one which works quite nicely.

For the flatten function we were able to recurse on the original function by running the list back through the flatten function whenever we didn't know if the 'head' was an element:

def flatten[A](list : List[A]) : List[A] = list match {
	case Nil => list
	case (head:List[A])::tail => flatten(head) ::: flatten(tail)
	case head::tail => head :: flatten(tail)
}

The nice thing about this solution is that the list is in the correct order at the end so we don't need to reverse it as we did with the previous solutions.

This way of coding is still not quite intuitive to me but I think the solution is cleaner and easier to read than the others.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • HackerNews
  • StumbleUpon
  • Twitter

Written by Mark Needham

September 30th, 2009 at 11:39 pm

Posted in Scala

Tagged with

7 Responses to 'Scala: 99 problems'

Subscribe to comments with RSS or TrackBack to 'Scala: 99 problems'.

  1. Is there any point in the type parameter A, as it will nearly always be deduced as type Any?

    Hector Chu

    1 Oct 09 at 2:19 am

  2. Hehe "not quite intuitive" that's the word,
    "the solution is cleaner and easier to read" that's the message.

    I never thought I would dig up my old Prolog assignments and fundamental scripts (#07) from https://prof.ti.bfh.ch/hew1/informatik3/prolog/p-99/

    Thank you for keeping me hooked on Scala!

    Oli

    1 Oct 09 at 2:46 am

  3. @Hector – yeh that's a good point. I think that's probably a hang over from the fact that in the previous problems 'A' could be a specific type but you're right for this problem it's irrelevant.

    @Oli – looking at the Prolog problems reminds me of university days!

    Mark Needham

    1 Oct 09 at 6:07 am

  4. FWIW the second option is tail recursive, so it can deal with indefinitely long lists, whereas the third option will stack overflow given a long enough input.

    Raphael Speyer

    1 Oct 09 at 8:59 am

  5. I am still trying to wrap my head around functional programming in general. I find the below solution to make the most sense to me:

    def flatten[A](list : List[A]) : List[A] = {
    list.foldRight(Nil:List[A])((elem, start:List[A]) => {
    elem match {
    case x:List[A] => flatten(x) ::: start
    case _ => elem :: start
    }
    }
    )
    }

  6. @Raphael – man I always spot the tail recursion but I missed this one! Good point.

    @Charlie – that solution reads quite well. I think it's possible to do a solution using the built in function 'flatMap' which would make it even more concise.

    Mark Needham

    1 Oct 09 at 6:09 pm

  7. [...] recently Liz and I have been playing around with Scala and from trying out some of these exercises I am seeing that when writing recursive functions my [...]

Leave a Reply