0

I have a list

val l = List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2)

and I want to remove every instance of a particular sequence such as (2,3)

So the desired output is...

List(1,2,6,4,4,2,1,3,6,3,2)

What is the easiest/most idiomatic way to accomplish this in Scala?

I've tried doing this so far..

l.sliding(2).filter{ _!=List(2,3) }

but then I can't figure out to go from there, which made me wonder if I'm on the right track.

Ryan Stull
  • 1,056
  • 14
  • 35
  • 1
    I'm not even sure this question is well-defined. What do I do if I have `List(1,2,1,2,1)` and I want to remove the sequence `1,2,1`. Do I end up with `List(1,2)` or `List(2,1)`? – Alec Jul 30 '16 at 04:04
  • @cricket_007 I've updated my question @Alec In general you are correct. In this particular instance I'd need the remove to start at the left and go to the right, so in your example the result would be `List(2,1)` – Ryan Stull Jul 30 '16 at 04:07
  • Thanks for the edit, but where are you trying to go from there? Does that not work for you? – OneCricketeer Jul 30 '16 at 04:08
  • @cricket_007 The problem is I can't figure out how to merge all of the sublists created by `sliding` back into one list since the other sublists will contain remnants of the `(2,3)` – Ryan Stull Jul 30 '16 at 04:11
  • I'm not too knowledge about Scala, but I'd group every pair of two numbers, then filter / flatMap and collect those into a list. I believe the sliding function over 1,2,3 would pass over (1,2) and (2,3) which isn't what you want – OneCricketeer Jul 30 '16 at 04:16
  • So what should happen if there's an "embedded" pattern? For example, say you want to remove `2,3` from `1,2,2,3,3,4`. Which result do you want, `1,2,3,4` or `1,4`? – jwvh Jul 30 '16 at 06:04

2 Answers2

1
def stripFrom[A](lst: List[A], x: List[A]): List[A] =
  if (lst.containsSlice(x) && x.length > 0)
    stripFrom(lst.patch(lst.indexOfSlice(x), List(), x.length), x)
  else lst

Proof of concept:

scala> stripFrom(List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2), List(2,3))
res3: List[Int] = List(1, 2, 6, 4, 4, 2, 1, 3, 6, 3, 2)

scala> stripFrom(List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2), List(4,2))
res4: List[Int] = List(1, 2, 3, 2, 6, 3, 1, 3, 6, 3, 2)

scala> stripFrom(List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2), List(4,2,3,4))
res5: List[Int] = List(1, 2, 3, 2, 6, 2, 1, 3, 6, 3, 2)

scala> stripFrom(List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2), List(2))
res6: List[Int] = List(1, 3, 6, 4, 3, 4, 1, 3, 6, 3)
jwvh
  • 50,871
  • 7
  • 38
  • 64
  • Beware that this will end up being very slow if there are many instances of `x` to remove from `lst`, as the list will need to be traversed that many times, putting this in the neighborhood of O(n^2). – Michael Zajac Jul 30 '16 at 04:43
1

You can iterate through the list recursively, consuming elements from the head of the list one at a time and accumulating the desired ones into a result list, while discarding the matching undesirable sequence. A simple tail-recursive example could work like this:

@annotation.tailrec
def filterList[A](list: List[A], acc: List[A] = Nil): List[A] = list match {
    case 2 :: 3 :: tail => filterList(tail, acc)
    case head :: tail => filterList(tail, head :: acc)
    case Nil => acc.reverse
}

scala> val l = List(1,2,3,2,6,4,2,3,4,2,1,3,6,3,2)
scala> filterList(l)
res0: List[Int] = List(1, 2, 6, 4, 4, 2, 1, 3, 6, 3, 2)

Or more generally, you can use startsWith to check that the current iteration of List starts with the sequence you want to remove.

@annotation.tailrec
def filterList[A](list: List[A], subList: List[A], acc: List[A] = Nil): List[A] = list match {
    case l if(list startsWith subList) => filterList(l.drop(subList.length), subList, acc)
    case head :: tail => filterList(tail, subList, head :: acc)
    case Nil => acc.reverse
}

scala> filterList(l, List(2, 3))
res4: List[Int] = List(1, 2, 6, 4, 4, 2, 1, 3, 6, 3, 2)

If performance is an issue, you can make the acc mutable.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138