Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

help rewriting in functional style

I'm learning Scala as my first functional-ish language. As one of the problems, I was trying to find a functional way of generating the sequence S up to n places. S is defined so that S(1) = 1, and S(x) = the number of times x appears in the sequence. (I can't remember what this is called, but I've seen it in programming books before.)

In practice, the sequence looks like this:

  S = 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7 ...

I can generate this sequence pretty easily in Scala using an imperative style like this:

  def genSequence(numItems: Int) = {
    require(numItems > 0, "numItems must be >= 1")
    var list: List[Int] = List(1)
    var seq_no = 2
    var no     = 2
    var no_nos = 0
    var num_made = 1

    while(num_made < numItems) {
      if(no_nos < seq_no) {
        list = list :+ no
        no_nos += 1
        num_made += 1
      } else if(no % 2 == 0) {
        no += 1
        no_nos = 0
      } else {
        no += 1
        seq_no += 1
        no_nos = 0
      }
    }
    list
  }

But I don't really have any idea how to write this without using vars and the while loop.

Thanks!

like image 550
Aaron Yodaiken Avatar asked Feb 27 '11 19:02

Aaron Yodaiken


1 Answers

Pavel's answer has come closest so far, but it's also inefficient. Two flatMaps and a zipWithIndex are overkill here :)

My understanding of the required output:

  • The results contain all the positive integers (starting from 1) at least once
  • each number n appears in the output (n/2) + 1 times

As Pavel has rightly noted, the solution is to start with a Stream then use flatMap:

Stream from 1

This generates a Stream, a potentially never-ending sequence that only produces values on demand. In this case, it's generating 1, 2, 3, 4... all the way up to Infinity (in theory) or Integer.MAX_VALUE (in practice)

Streams can be mapped over, as with any other collection. For example: (Stream from 1) map { 2 * _ } generates a Stream of even numbers.

You can also use flatMap on Streams, allowing you to map each input element to zero or more output elements; this is key to solving your problem:

val strm = (Stream from 1) flatMap { n => Stream.fill(n/2 + 1)(n) }

So... How does this work? For the element 3, the lambda { n => Stream.fill(n/2 + 1)(n) } will produce the output stream 3,3. For the first 5 integers you'll get:

1 -> 1
2 -> 2, 2
3 -> 3, 3
4 -> 4, 4, 4
5 -> 5, 5, 5
etc.

and because we're using flatMap, these will be concatenated, yielding:

1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, ...

Streams are memoised, so once a given value has been calculated it'll be saved for future reference. However, all the preceeding values have to be calculated at least once. If you want the full sequence then this won't cause any problems, but it does mean that generating S(10796) from a cold start is going to be slow! (a problem shared with your imperative algorithm). If you need to do this, then none of the solutions so far is likely to be appropriate for you.

like image 177
Kevin Wright Avatar answered Oct 31 '22 16:10

Kevin Wright