Search code examples
scalalistsliceregular-language

Finding all slices delimited by two elements in Scala `List`


In Scala, what would be the right way of selecting elements of a list based on the position of two elements? Suppose I have the list below and I would like to select all the elements between 2 and 7, including them (note: not greater than/smaller than, but the elements that come after 2 and before 7 in the list):

scala> val l = List(1, 14, 2, 17, 35, 9, 12, 7, 9, 40)
l: List[Int] = List(1, 14, 2, 17, 35, 9, 12, 7, 9, 40)

scala> def someMethod(l: List[Int], from: Int, to: Int) : List[Int] = {
     | // some code here
     | }
someMethod: (l: List[Int], from: Int, to: Int)List[Int]

scala> someMethod(l, 2, 7)
res0: List[Int] = List(2, 17, 35, 9, 12, 7)

Expected output:

  • For lists that don't contain 2 and/or 7: an empty list
  • Input: (1, 2, 2, 2, 3, 4, 7, 8); Output: (2, 2, 2, 3, 4, 7)
  • Input: (1, 2, 3, 4, 7, 7, 7, 8); Output: (2, 3, 4, 7)
  • Input: (1, 2, 3, 4, 7, 1, 2, 3, 5, 7, 8); Output: ((2, 3, 4, 7), (2, 3, 5, 7))

Solution

  • Too bad that the regex-engines work only with strings, not with general lists - would be really nice if you could find all matches for something like L.*?R with two arbitrary delimiters L and R. Since it doesn't work with regex, you have to build a little automaton yourself. Here is one way to do it:

    @annotation.tailrec 
    def findDelimitedSlices[A](
      xs: List[A],
      l: A,
      r: A,
      revAcc: List[List[A]] = Nil
    ): List[List[A]] = {
      xs match {
        case h :: t => if (h == l) {
          val idx = xs.indexOf(r)
          if (idx >= 0) {
            val (s, rest) = xs.splitAt(idx + 1)
            findDelimitedSlices(rest, l, r, s :: revAcc)
          } else {
            revAcc.reverse
          }
        } else {
          findDelimitedSlices(t, l, r, revAcc)
        }
        case Nil => revAcc.reverse
      }
    }
    

    Input:

    for (example <- List(
      List(1, 2, 2, 2, 3, 4, 7, 8),
      List(1, 2, 3, 4, 7, 7, 7, 8),
      List(1, 2, 3, 4, 7, 1, 2, 3, 5, 7, 8)
    )) {
      println(findDelimitedSlices(example, 2, 7))
    }
    

    Output:

    List(List(2, 2, 2, 3, 4, 7))
    List(List(2, 3, 4, 7))
    List(List(2, 3, 4, 7), List(2, 3, 5, 7))