0

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

sam
  • 3,441
  • 2
  • 33
  • 42

4 Answers4

1

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

Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • Ok, so the short answer to my question is 'not possible' .... I am quite disappointed honestly but at least it is clear. – sam May 12 '15 at 14:12
  • @sam Yes. My point is: this is not possible **syntactically**, but lot's of things are possible **semantically** by using custom matchers. – Odomontois May 12 '15 at 14:17
  • I am simply surprised that it compiles then. Is it failing at runtime but hidden by the pattern matching process ? – sam May 12 '15 at 14:19
  • @sam Why this is compiles - is another question. I'll try to clarify that. – Odomontois May 12 '15 at 14:36
0

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
}
Carlos Vilchez
  • 2,774
  • 28
  • 30
  • it is in fact a Seq of objects, sorry for not saying it, I edited my post – sam May 12 '15 at 13:20
  • Even as a `Seq[Object]` works. Perhaps you could add an example of the sequence. – Carlos Vilchez May 12 '15 at 13:24
  • I don't see how it could work since :: is only usable on List – sam May 12 '15 at 13:28
  • If you check :: in this [link](http://www.scala-lang.org/api/2.11.5/index.html#scala.collection.immutable.$colon$colon) you can see all the types that can use :: with. – Carlos Vilchez May 12 '15 at 13:33
  • In this link you clearly see that this operator extends List so it is only for Lists , but in your case it is working because the default implementation of a Seq is a List , so indeed you have a List with this code. But mine could be any type of Seq – sam May 12 '15 at 13:40
  • 1
    I think I understand what you mean. But `::` is more than a `List` operator. I have found [this](http://stackoverflow.com/questions/6807540/scala-pattern-matching-on-sequences-other-than-lists) post about it. – Carlos Vilchez May 12 '15 at 13:45
  • True, it is more than that I didn't know :) . But it seems it will not fit my requirements, thanks anyway – sam May 12 '15 at 13:47
0

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 
}
Shyamendra Solanki
  • 8,751
  • 2
  • 31
  • 25
  • unfortunately it doesn't work, probably because there could be any number of elements before `from` – sam May 12 '15 at 13:46
0

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.
Mariusz Nosiński
  • 1,308
  • 9
  • 10