Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use same variable multiple times within one pattern

I'd like to be able to use a single variable multiple times within one pattern, so that it will only match if the same value is present in all places, such as

list match {
  case x :: x :: xs => // recurse
}

which would match List(1,1,2) but would not match List(1,2,1). But this does not compile with error: x is already defined as value x.

In researching this question, I found out that I can also include a guard in the case clause, so I can do

list match {
  case x1 :: x2 :: xs if x1==x2 => // recurse
}

which seems to work the same way (it does, right?). This is good, but it wouldn't look as clean if I wanted the same value in many places, like

list match {
  case x1::x2::x3::x4::xs if x1==x2 && x2==x3 && x3==x4 => // recurse
}

Is there any more elegant way I can do this?


A few notes: Yes, I am just learning scala, if that wasn't clear, so I'm not sure this is something I'd ever really want to do, but I'm just interested in what's possible. In that regard, I'm not really looking for a completely different solution, like takeWhile or filter or something, but more so am specifically interested in pattern matching.

like image 968
ceyko Avatar asked Dec 20 '12 00:12

ceyko


3 Answers

Scala doesn't provide quite that much flexibility with its matches (which may be a good thing, as one has to be aware of errors arising from unintentional variable re-use).

If you have a large number of identical items, you might want to consider a nested match (but note that you won't fail out of the inner match to be completed later down the outer match, so you have to handle everything locally):

list match {
  case x :: rest => rest match {
    case `x` :: `x` :: `x` :: xs => println("Four of the same")
    case _ => println("Well, nonempty at least")
  }
  case _ => println("Boring, there's nothing here!")
}

Note the backticks which mean "we've already got this variable, check against it, don't set it!".

Alternatively, if you have specialized functionality that you use repeatedly, you can create a custom matcher:

object FourOf {
  def unapplySeq(xs: List[Int]): Option[(Int, List[Int])] = xs match {
    case x :: y :: z :: a :: rest if x==y && y==z && z==a => Some((x,rest))
    case _ => None
  }
}

and then use it whenever you need that complicated pattern:

list match {
  case FourOf(x,rest) => println("four of the same")
  case x :: more => println("Nonempty")
  case _ => println("Yawn")
}

Neither of these are quite as tidy and flexible as what you were apparently hoping for, but then again, I'm not sure flipping between assigning and testing the same variable in a match statement is a good way to write clear code anyway.

like image 150
Rex Kerr Avatar answered Nov 03 '22 13:11

Rex Kerr


For many repeats you might use stable identifiers to do a comparison (instead of catching a value):

val x = list.head 
list match {
  case `x`::`x`::`x`::`x`::xs => ....
}

But note that this won't work on empty list (you just cannot get head of it).

like image 36
om-nom-nom Avatar answered Nov 03 '22 12:11

om-nom-nom


I think Rex's answer rocks. I am a fan of unapplySeq. But here's a not-so-clever-and-maybe-wasteful alternative, if your main bother is just with the sequence of =='s in each guard. So in the TMTOWTDI spirit:

def same[A](xs: A*) = xs forall (xs.head==)

// Then in your pattern match,

list match {
  // case x1::x2::x3::x4::xs if x1==x2 && x2==x3 && x3==x4 => // recurse
  case x1::x2::x3::x4::xs if same(x1,x2,x3,x4) => // recurse
}

I like Om's answer as well, so here's an adaptation:

 list.headOption map (x => list match { 
   case `x`::`x`::`x`::`x`::xs => //...; 
   case _ => // ... 
 }) getOrElse {
   // do what you'd have done for an empty list...
 }
like image 22
Faiz Avatar answered Nov 03 '22 11:11

Faiz