Scala - increasing prefix of a sequence

后端 未结 4 675
离开以前
离开以前 2021-01-18 21:02

I was wondering what is the most elegant way of getting the increasing prefix of a given sequence. My idea is as follows, but it is not purely functional or any elegant:

相关标签:
4条回答
  • 2021-01-18 21:25

    And, another way to skin the cat:

     val sequence = Seq(1,2,3,1,2,3,4,5,6)
     sequence.head :: sequence
                      .sliding(2)
                      .takeWhile{case List(a,b) => a <= b}
                      .map(_(1)).toList
    // List[Int] = List(1, 2, 3)
    
    0 讨论(0)
  • 2021-01-18 21:29

    I will interpret elegance as the solution that most closely resembles the way we humans think about the problem although an extremely efficient algorithm could also be a form of elegance.

    val sequence = List(1,2,3,2,3,45,5)
    val increasingPrefix = takeWhile(sequence, _ < _)
    

    I believe this code snippet captures the way most of us probably think about the solution to this problem.

    This of course requires defining takeWhile:

    /**
     * Takes elements from a sequence by applying a predicate over two elements at a time.
     * @param xs The list to take elements from
     * @param f The predicate that operates over two elements at a time
     * @return This function is guaranteed to return a sequence with at least one element as
     *         the first element is assumed to satisfy the predicate as there is no previous
     *         element to provide the predicate with.
     */
    def takeWhile[A](xs: Traversable[A], f: (Int, Int) => Boolean): Traversable[A] = {
      // function that operates over tuples and returns true when the predicate does not hold
      val not = f.tupled.andThen(!_)
      // Maybe one day our languages will be better than this... (dependant types anyone?)
      val twos = sequence.sliding(2).map{case List(one, two) => (one, two)}
      val indexOfBreak = twos.indexWhere(not)
      // Twos has one less element than xs, we need to compensate for that
      // An intuition is the fact that this function should always return the first element of
      // a non-empty list
      xs.take(i + 1)
    }
    
    0 讨论(0)
  • 2021-01-18 21:30

    You can take your solution, @Samlik, and effectively zip in the currentElement variable, but then map it out when you're done with it.

    sequence.take(1) ++ sequence.zip(sequence.drop(1)).
        takeWhile({case (a, b) => a < b}).map({case (a, b) => b})
    

    Also works with infinite sequences:

    val sequence = Seq(1, 2, 3).toStream ++ Stream.from(1)
    

    sequence is now an infinite Stream, but we can peek at the first 10 items:

    scala> sequence.take(10).toList
    res: List[Int] = List(1, 2, 3, 1, 2, 3, 4, 5, 6, 7)
    

    Now, using the above snippet:

    val prefix = sequence.take(1) ++ sequence.zip(sequence.drop(1)).
        takeWhile({case (a, b) => a < b}).map({case (a, b) => b})
    

    Again, prefix is a Stream, but not infinite.

    scala> prefix.toList
    res: List[Int] = List(1, 2, 3)
    

    N.b.: This does not handle the cases when sequence is empty, or when the prefix is also infinite.

    0 讨论(0)
  • 2021-01-18 21:37

    If by elegant you mean concise and self-explanatory, it's probably something like the following:

    sequence.inits.dropWhile(xs => xs != xs.sorted).next
    

    inits gives us an iterator that returns the prefixes longest-first. We drop all the ones that aren't sorted and take the next one.

    If you don't want to do all that sorting, you can write something like this:

    sequence.scanLeft(Some(Int.MinValue): Option[Int]) {
      case (Some(last), i) if i > last => Some(i)
      case _ => None
    }.tail.flatten
    

    If the performance of this operation is really important, though (it probably isn't), you'll want to use something more imperative, since this solution still traverses the entire collection (twice).

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