sam
sam

Reputation: 3511

Patttern matching within collection

I would like to look for a specific pattern inside a Seq. I tried to use at the same time :+ and +: operators but it doesn't seem to work even though it compiles, for now I have to rely on 'dropwhile' operation first and then pattern match on the beginning of the collection.

Is it not possible to write something like that in Scala ? 'from' and 'to' are existing variables

case beginColl :+ `from` +: someElement +: `to` +: tail => true 

Edit : it is a Seq of objects , not a list

Upvotes: 0

Views: 74

Answers (4)

Odomontois
Odomontois

Reputation: 16328

This will never work in that definition as you can not wildcard any subsequence except tail in result of unapplySeq. But let me suggest a workaround.

Lets define this helper:

object Span {
  class Spanner[T](pred: T => Boolean) {
    def unapply(seq: Seq[T]) = for {
      idx <- Some(seq.indexWhere(pred)) if idx >= 0
      (start, elem +: end) = seq.splitAt(idx)
    } yield (start, end)
  }
  def apply[T](pred: T => Boolean) = new Spanner(pred)
}

This allows us to define more interesting matchers like this function:

def splitter[T](from:T, to:T): PartialFunction[Seq[T], Seq[Seq[T]]] = {
  val From = Span[T](_ == from)
  val To = Span[T](_ == to)

  {
    case From(prefix, To(middle, postfix)) => Seq(prefix, middle, postfix)
  }
}

So if we specialize it to:

val mySplitter = splitter("from", "to").lift

We could get appropriate results:

mySplitter(Seq("1", "2", "to", "3", "4", "from", "5", "6")) // None
mySplitter(Seq("1", "2", "from", "3", "4", "to", "5", "6")) // Some(List(List(1, 2), List(3, 4), List(5, 6))) 

Lets try to clarify how compiler understands your syntax, lets define

def splitter2(from: AnyRef, to: AnyRef): PartialFunction[Seq[_], AnyRef] = {
  case beginColl :+ `from` +: someElement +: `to` +: tail => (beginColl, someElement, tail)
}
val mySplitter2 = splitter2("from", "to").lift

So if we try to match

mySplitter2(Seq("1", "2", "from", "3", "4 ", "to", "5", "6"))

We'll surely get None

But if we try

mySplitter2(Seq("1", "2", Seq("from", "3", "to", "4", "5")))

Suddenly we getting Some(...)

So compiler just understood your expression as _match element as

beginColl :+ __last

then match __last as

`from` +: someElement +: `to` +: tail

Which is basically verify this is non-empty Seq last element of which is another Seq that consists of at least three elements, first and third of these are from and to

Upvotes: 1

Shyamendra Solanki
Shyamendra Solanki

Reputation: 8851

This should work:

case Seq(_, `from`, _, `to`, _*) => true

EDIT:

if there are more elements before 'from', dropWhile is a good solution, an alternative (but less efficient) way could be :

def matchSeq[T](s: Seq[T]): Boolean = s match { 
    case Seq(_, `from`, _, `to`, _*) => true
    case Seq(a, b@_*) => matchSeq(b) 
    case _ => false 
}

Upvotes: 0

Carlos Vilchez
Carlos Vilchez

Reputation: 2804

I thought that you may need to recognize a sequence like this:

val test = Seq("x", "from", "y", "to", "z")

test match {
  case _ :: "from" :: _ :: "to" :: _ => true
  case _ => false
}

But as you need to know if the sequence has a particular characteristic, I would try this:

test match {
  case list if (list.indexOf("from") < list.indexOf("to")) => true
  case _ => false
}

Upvotes: 0

Mariusz Nosiński
Mariusz Nosiński

Reputation: 1288

You can use containsSlice to check the sequence contains subsequence, or you can compare indexes of elements you are looking for. i.e:

val test = Seq("x", "from", "y", "to", "z")
val test2 = Seq("u", "w", "x", "from", "y", "to", "z")

test match {
  case s if s.indexOf("to") - s.indexOf("from") == 2 => true
  case _ => false
} //true in both cases.

Upvotes: 0

Related Questions