help rewriting in functional style

后端 未结 5 2129
再見小時候
再見小時候 2021-02-10 10:46

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

相关标签:
5条回答
  • 2021-02-10 11:03

    Here is a translation of your code to a more functional style:

    def genSequence(numItems: Int): List[Int] = {
      genSequenceR(numItems, 2, 2, 0, 1, List[Int](1))
    }
    
    
    def genSequenceR(numItems: Int, seq_no: Int, no:Int, no_nos: Int, numMade: Int, list: List[Int]): List[Int] = {
     if(numMade < numItems){
       if(no_nos < seq_no){
         genSequenceR(numItems, seq_no, no, no_nos + 1, numMade + 1, list :+ no)
       }else if(no % 2 == 0){
         genSequenceR(numItems, seq_no, no + 1, 0, numMade, list)
       }else{
         genSequenceR(numItems, seq_no + 1, no + 1, 0, numMade, list)
       }
      }else{
        list
      }
    }
    

    The genSequenceR is the recursive function that accumulates values in the list and calls the function with new values based on the conditions. Like the while loop, it terminates, when numMade is less than numItems and returns the list to genSequence.

    This is a fairly rudimentary functional translation of your code. It can be improved and there are better approaches typically used. I'd recommend trying to improve it with pattern matching and then work towards the other solutions that use Stream here.

    0 讨论(0)
  • 2021-02-10 11:12

    Here's an attempt from a Scala tyro. Keep in mind I don't really understand Scala, I don't really understand the question, and I don't really understand your algorithm.

    def genX_Ys[A](howMany : Int, ofWhat : A) : List[A] = howMany match {
        case 1 => List(ofWhat)
        case _ => ofWhat :: genX_Ys(howMany - 1, ofWhat)
    }
    
    def makeAtLeast(startingWith : List[Int], nextUp : Int, howMany : Int, minimumLength : Int) : List[Int] = {
        if (startingWith.size >= minimumLength) 
          startingWith 
        else 
          makeAtLeast(startingWith ++ genX_Ys( howMany, nextUp), 
                     nextUp +1, howMany + (if (nextUp % 2 == 1) 1 else 0), minimumLength)
    }
    
    def genSequence(numItems: Int) =  makeAtLeast(List(1), 2, 2, numItems).slice(0, numItems)
    

    This seems to work, but re-read the caveats above. In particular, I am sure there is a library function that performs genX_Ys, but I couldn't find it.

    EDIT Could be

    def genX_Ys[A](howMany : Int, ofWhat : A) : Seq[A]  = 
       (1 to howMany) map { x => ofWhat }
    
    0 讨论(0)
  • Here is a very direct "translation" of the definition of the Golomb seqence:

    val it = Iterator.iterate((1,1,Map(1->1,2->2))){ case (n,i,m) =>
        val c = m(n)
        if (c == 1) (n+1, i+1, m + (i -> n) - n)
        else (n, i+1, m + (i -> n) + (n -> (c-1)))
    }.map(_._1)
    
    println(it.take(10).toList)
    

    The tripel (n,i,m) contains the actual number n, the index i and a Map m, which contains how often an n must be repeated. When the counter in the Map for our n reaches 1, we increase n (and can drop n from the map, as it is not longer needed), else we just decrease n's counter in the map and keep n. In every case we add the new pair i -> n into the map, which will be used as counter later (when a subsequent n reaches the value of the current i).

    [Edit]

    Thinking about it, I realized that I don't need indexes and not even a lookup (because the "counters" are already in the "right" order), which means that I can replace the Map with a Queue:

    import collection.immutable.Queue
    
    val it = 1 #:: Iterator.iterate((2, 2, Queue[Int]())){
      case (n,1,q) => (n+1, q.head, q.tail + (n+1))
      case (n,c,q) => (n,c-1,q + n)
    }.map(_._1).toStream
    

    The Iterator works correctly when starting by 2, so I had to add a 1 at the beginning. The second tuple argument is now the counter for the current n (taken from the Queue). The current counter could be kept in the Queue as well, so we have only a pair, but then it's less clear what's going on due to the complicated Queue handling:

    val it = 1 #:: Iterator.iterate((2, Queue[Int](2))){
      case (n,q) if q.head == 1 => (n+1, q.tail + (n+1))
      case (n,q) => (n, ((q.head-1) +: q.tail) + n)
    }.map(_._1).toStream 
    
    0 讨论(0)
  • 2021-02-10 11:20

    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.

    0 讨论(0)
  • 2021-02-10 11:27

    The following code produces exactly the same sequence as yours:

    val seq = Stream.from(1)
            .flatMap(Stream.fill(2)(_))
            .zipWithIndex
            .flatMap(p => Stream.fill(p._1)(p._2))
            .tail
    

    However, if you want to produce the Golomb sequence (that complies with the definition, but differs from your sample code result), you may use the following:

    val seq = 1 #:: a(2)
    def a(n: Int): Stream[Int] = (1 + seq(n - seq(seq(n - 2) - 1) - 1)) #:: a(n + 1)
    

    You may check my article for more examples of how to deal with number sequences in functional style.

    0 讨论(0)
提交回复
热议问题