3

My requirement is to drop every Nth element from a Scala Array (pls note every Nth element). I wrote the below method which does the job. Since, I am new to Scala, I couldn't avoid the Java hangover. Is there a simpler or more efficient alternative?

def DropNthItem(a: Array[String], n: Int): Array[String] = {
 val in = a.indices.filter(_ % n != 0)
 val ab: ArrayBuffer[String] = ArrayBuffer()
 for ( i <- in)
    ab += a(i-1)
 return ab.toArray
}
AKK
  • 31
  • 4
  • You might also want to look on this http://stackoverflow.com/questions/39105121/how-to-remove-every-nth-element-from-the-scala-list – Shankar Dec 04 '16 at 07:04

6 Answers6

3

You made a good start. Consider this simplification.

def DropNthItem(a: Array[String], n: Int): Array[String] =
  a.indices.filter(x => (x+1) % n != 0).map(a).toArray
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Thank you. However, your logic skips the 1st record (a[0] - since 0 % n will be 0). My logic a(i-1) ensures the first element is not lost. – AKK Dec 04 '16 at 02:06
  • No problem. Easily fixed. – jwvh Dec 04 '16 at 03:08
3

How about something like this?

arr.grouped(n).flatMap(_.take(n-1)).toArray
Dima
  • 39,570
  • 6
  • 44
  • 70
  • The only solution so far without an intermediate data structure. – Victor Moroz Dec 04 '16 at 03:23
  • If we were code golfing, I'd suggest using `init`, but I suspect even experienced Scala programmers use it so rarely they'd have to look it up. – Joe Pallas Dec 04 '16 at 17:08
  • @JoePallas nope. That's what I had initially (I had `.dropRight(1)`) actually, but as Victor pointed out, that doesn't work. – Dima Dec 04 '16 at 19:33
2

You can do this in two steps functionally using zipWithIndex to get an array of elements tupled with their indices, and then collect to build a new array consisting of only elements that have indices that aren't 0 = i % n.

def dropNth[A: reflect.ClassTag](arr: Array[A], n: Int): Array[A] =
  arr.zipWithIndex.collect { case (a, i) if (i + 1) % n != 0 => a }
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
1

This will make it

def DropNthItem(a: Array[String], n: Int): Array[String] =
   a.zipWithIndex.filter(_._2 % n != 0).map(_._1)
Mikel San Vicente
  • 3,831
  • 2
  • 21
  • 39
1

If you're looking for performance (since you're using an ArrayBuffer), you might as well track the index with a var, manually increment it, and check it with an if to filter out n-multiple-indexed values.

def dropNth[A: reflect.ClassTag](arr: Array[A], n: Int): Array[A] = {
    val buf = new scala.collection.mutable.ArrayBuffer[A]
    var i = 0
    for(a <- arr) {
        if((i + 1) % n != 0) buf += a
        i += 1
    }
    buf.toArray
}

It's faster still if we traverse the original array as an iterator using a while loop.

def dropNth[A: reflect.ClassTag](arr: Array[A], n: Int): Array[A] = {
    val buf = new scala.collection.mutable.ArrayBuffer[A]
    val it = arr.iterator
    var i = 0
    while(it.hasNext) {
        val a = it.next
        if((i + 1) % n != 0) buf += a
        i += 1
    }
    buf.toArray
}
Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
0

I'd go with something like this;

def dropEvery[A](arr: Seq[A], n: Int) = arr.foldLeft((Seq.empty[A], 0)) {
  case ((acc, idx), _) if idx == n - 1 => (acc, 0)
  case ((acc, idx), el) => (acc :+ el, idx + 1)
}._1

// example: dropEvery(1 to 100, 3)
// res0: Seq[Int] = List(1, 2, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 20, 22, 23, 25, 26, 28, 29, 31, 32, 34, 35, 37, 38, 40, 41, 43, 44, 46, 47, 49, 50, 52, 53, 55, 56, 58, 59, 61, 62, 64, 65, 67, 68, 70, 71, 73, 74, 76, 77, 79, 80, 82, 83, 85, 86, 88, 89, 91, 92, 94, 95, 97, 98, 100)

This is efficient since it requires a single pass over the array and removes every nth element from it – I believe that is easy to see. The first case matches when idx == n - 1 and ignores the element at that index, and passes over the acc and resets the count to 0 for the next element. If the first case doesn't match, it adds the element to the end of the acc and increments the count by 1.

Since you're willing to get rid of the Java hangover, you might want to use implicit classes to use this in a very nice way:

implicit class MoreFuncs[A](arr: Seq[A]) {
  def dropEvery(n: Int) = arr.foldLeft((Seq.empty[A], 0)) {
    case ((acc, idx), _) if idx == n - 1 => (acc, 0)
    case ((acc, idx), el) => (acc :+ el, idx + 1)
  }._1
}

// example: (1 to 100 dropEvery 1) == Nil (: true)
Ashesh
  • 2,978
  • 4
  • 27
  • 47
  • However, you use Sequences not Arrays. You would need to convert your array first to a sequence and then back to array – Thomas Apr 15 '19 at 14:17