pathikrit
pathikrit

Reputation: 33489

Scala's collection's sliding() is inconsistent when the window size is greater than step

This is Scala collection API's sliding():

/** Groups elements in fixed size blocks by passing a "sliding window"
   *  over them (as opposed to partitioning them, as is done in grouped.)
   *  @see [[scala.collection.Iterator]], method `sliding`
   *
   *  @param size the number of elements per group
   *  @param step the distance between the first elements of successive
   *         groups
   *  @return An iterator producing ${coll}s of size `size`, except the
   *          last and the only element will be truncated if there are
   *          fewer elements than size.
   */
  def sliding(size: Int, step: Int): Iterator[Repr] =

An easy way to understand this is that sliding is simply (0 until this.length by step).map(i => slice(i, i + size)). But this interpretation does not work when size > step:

object SlidingTest extends App {
  val n = 10

  val r1 = 0 until n

  val r2 = new Range(start = 0, end = n, step = 1) {
    override def sliding(size: Int, step: Int) = 
     (indices by step).iterator.map(i => slice(i, i + size))
  }

  for {
    i <- 1 to 2*n
    j <- 1 to 2*n
    s1 = r1.sliding(i, j).toList.map(_.toList)
    s2 = r2.sliding(i, j).toList.map(_.toList)
    if s1 != s2
  } println(s"Sliding fail for size=$i and step=$j: [s1=$s1; s2=$s2]")
}

Specifically consider r1 = 0 until 10. According to the docs, r1.sliding(size = 2, step = 1) should be this:

List(List(0, 1), List(1, 2), List(2, 3), List(3, 4), List(4, 5), List(5, 6), List(6, 7), List(7, 8), List(8, 9), List(9))

But actually is this:

List(List(0, 1), List(1, 2), List(2, 3), List(3, 4), List(4, 5), List(5, 6), List(6, 7), List(7, 8), List(8, 9))

(i.e. the last truncated slice is missing).

Another snippet copied from Scaladoc:

 /** Returns an iterator which presents a "sliding window" view of
   *  another iterator.  The first argument is the window size, and
   *  the second is how far to advance the window on each iteration;
   *  defaults to `1`.  Example usages:
   *  {{{
   *    // Returns List(List(1, 2, 3), List(2, 3, 4), List(3, 4, 5))
   *    (1 to 5).iterator.sliding(3).toList
   *    // Returns List(List(1, 2, 3, 4), List(4, 5))
   *    (1 to 5).iterator.sliding(4, 3).toList
   *    // Returns List(List(1, 2, 3, 4))
   *    (1 to 5).iterator.sliding(4, 3).withPartial(false).toList
   *    // Returns List(List(1, 2, 3, 4), List(4, 5, 20, 25))
   *    // Illustrating that withPadding's argument is by-name.
   *    val it2 = Iterator.iterate(20)(_ + 5)
   *    (1 to 5).iterator.sliding(4, 3).withPadding(it2.next).toList
   *  }}}
   *
   *  @note Reuse: $consumesAndProducesIterator
   */
  def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B] =
    new GroupedIterator[B](self, size, step)

What am I doing wrong?

Upvotes: 3

Views: 3882

Answers (2)

pathikrit
pathikrit

Reputation: 33489

Based on @som-snytt's answer, I found a way to express sliding in terms of slice as follows:

override def sliding(window: Int, step: Int) = {
  require(window > 0 && step > 0, s"window=$window and step=$step, but both must be positive")
  val lag = (window - step) max 0
  Iterator.range(start = 0, end = length - lag, step = step).map(i => slice(i, i + window))
}

Upvotes: 1

som-snytt
som-snytt

Reputation: 39587

It groups the elements and stops when everything is grouped.

It does not group at each possible step.

scala> (1 to 100).sliding(size=100,step=1).toList.size
res0: Int = 1

scala> (1 to 100).sliding(size=99,step=1).toList.size
res1: Int = 2

In your example, you expect it to create an extra group with 9 even though the collection has already been exhaustively grouped.

You also show the example where elements form a partial group:

scala> (1 to 5).sliding(size=4,step=3).toList
res4: List[scala.collection.immutable.IndexedSeq[Int]] = List(Vector(1, 2, 3, 4), Vector(4, 5))

The extra group is required because 5 remains ungrouped.

Edit: a possible rewording of the Scaladoc:

An iterator producing ${coll}s of size size, except the last element (which may be the only element) will be truncated if there are fewer than size elements remaining to be grouped.

Upvotes: 6

Related Questions