14

This is a simple exercise I am solving in Scala: given a list l return a new list, which contains every n-th element of l. If n > l.size return an empty list.

def skip(l: List[Int], n: Int) = 
  Range(1, l.size/n + 1).map(i => l.take(i * n).last).toList

My solution (see above) seem to work but I am looking for smth. simpler. How would you simplify it?

Michael
  • 41,026
  • 70
  • 193
  • 341

6 Answers6

21

Somewhat simpler:

scala> val l = (1 to 10).toList
l: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

// n == 3

scala> l.drop(2).grouped(3).map(_.head).toList
res0: List[Int] = List(3, 6, 9)

// n > l.length

scala> l.drop(11).grouped(12).map(_.head).toList
res1: List[Int] = List()

(the toList just to force the iteratot to be evaluated)

Works with infinite lists:

Stream.from(1).drop(2).grouped(3).map(_.head).take(4).toList
res2: List[Int] = List(3, 6, 9, 12)
The Archetypal Paul
  • 41,321
  • 20
  • 104
  • 134
9
scala> def skip[A](l:List[A], n:Int) = 
         l.zipWithIndex.collect {case (e,i) if ((i+1) % n) == 0 => e} // (i+1) because zipWithIndex is 0-based
skip: [A](l: List[A], n: Int)List[A]

scala> val l = (1 to 10).toList
l: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> skip(l,3)
res2: List[Int] = List(3, 6, 9)

scala> skip(l,11)
res3: List[Int] = List()
Marth
  • 23,920
  • 3
  • 60
  • 72
  • 1
    Thank you but this solution does not look much simpler. What is the advantage of this solution ? – Michael Aug 10 '14 at 11:02
  • 2
    Well it does look simpler to me (mostly from the fact that `zipWithIndex` makes it explicit you're doing something with the indices), but the main benefit is that you don't call `l.take(i).last` (which is equivalent (and slower than) `l(i)`), as getting an element this way is O(n) with lists. – Marth Aug 10 '14 at 11:09
  • Also, just tried your solution, calling `skip(l,3)` (with `l = (1 to 10).toList`) returns `List(1,2,3)`, which doesn't seem to be what you want. – Marth Aug 10 '14 at 11:11
4

A bit more readable and the loop size is O(l.length/n):

def skip(l: List[Int], n: Int) = {
  require(n > 0)
  for (step <- Range(start = n - 1, end = l.length, step = n))
    yield l(step)
}
bjfletcher
  • 11,168
  • 4
  • 52
  • 67
  • 2
    Thanks you. I have learned about `step` in `Range` from your answer :) However @Marth is probably right about using `zipWithIndex` instead of `l(step)`. – Michael Aug 10 '14 at 11:46
  • 1
    Doesn't the use of `zipWithIndex` mean more memory footprint and another loop though? This answer does it in only one loop and doesn't create an intermediary list of tuples. :) – bjfletcher Aug 10 '14 at 12:00
  • 4
    I am afraid `l(step)` loops over the list and takes `O(n)`. I don't know about the memory consumption of `zipWithIndex`. Maybe you are right about it. – Michael Aug 10 '14 at 12:25
  • 2
    @Michael you are right that `l(step)` is `O(n)` and if we replace `List` with `Vector` in the code then it will be `O(1)`. http://docs.scala-lang.org/overviews/collections/performance-characteristics.html – bjfletcher Aug 10 '14 at 12:30
  • yes, but the exercise requires a list. Moreover if the exercise requires `Seq` (i.e. either a linked list or array) I would prefer `zipWithIndex` for the same performance reasons. So, `zipWithIndex` looks like really _right_ way to handle indices. – Michael Aug 10 '14 at 12:34
  • I don't think there's any need to use indices (explicitly) at all. See my answer for an alternative – The Archetypal Paul Aug 10 '14 at 15:04
1

Fold left approach O(n)

def skip(xs: List[Int], n: Int) = {
  xs.foldLeft((List[Int](), n)){ case ((acc, counter), x) =>
      if(counter==1)
        (x+:acc,n)
      else
        (acc, counter-1)
  }
  ._1
  .reverse
}

scala > skip(List(1,2,3,4,5,6,7,8,9,10), 3)

Tailrec less readable approach O(n)

import scala.annotation.tailrec

def skipTR(xs: List[Int], n: Int) = {
  @tailrec
  def go(ys: List[Int], acc: List[Int], counter: Int): List[Int] = ys match {
    case k::ks=>
      if(counter==1)
        go(ks, k+:acc , n)
      else
        go(ks, acc, counter-1)
    case Nil => acc
  }
  go(xs, List(), n).reverse

}
skipTR(List(1,2,3,4,5,6,7,8,9,10), 3)
Przemek
  • 7,111
  • 3
  • 43
  • 52
1

Two approaches based in filter on indexes, as follows,

implicit class RichList[A](val list: List[A]) extends AnyVal {

  def nthA(n: Int) = n match {
    case 0 => List()
    case _ => (1 to a.size).filter( _ % n == 0).map { i => list(i-1)}
  }

  def nthB(n: Int) = n match {
    case 0 => List()
    case _ => list.zip(Stream.from(1)).filter(_._2 % n == 0).unzip._1
  }
}

and so for a given list

val a = ('a' to 'z').toList

we have that

a.nthA(5)
res: List(e, j, o, t, y)

a.nthA(123)
res: List()

a.nthA(0)
res: List()

Update

Using List.tabulate as follows,

implicit class RichList[A](val list: List[A]) extends AnyVal {
  def nthC(n: Int) = n match {
    case 0 => List()
    case n => List.tabulate(list.size) {i => 
                if ((i+1) % n == 0) Some(list(i)) 
                else None }.flatten
  }
}
elm
  • 20,117
  • 14
  • 67
  • 113
1

You could omit toList if you don't mind an iterator:

 scala> def skip[A](l:List[A], n:Int) =
      l.grouped(n).filter(_.length==n).map(_.last).toList
 skip: [A](l: List[A], n: Int)List[A]

 scala> skip (l,3)
 res6: List[Int] = List(3, 6, 9)
Mark Lister
  • 1,103
  • 6
  • 16
  • Both the length check and the _.last will be O(n) (that's the n in every-nth, not the size of l). They're not necessary if you shift the starting point so you're taking the first of each group, not the last (see my answer) – The Archetypal Paul Aug 10 '14 at 22:32
  • 1
    Yeah, good point if performance is more important than simplicity. – Mark Lister Aug 11 '14 at 06:09