52

Is it possible to match a range of values in Scala?

For example:

val t = 5
val m = t match {
    0 until 10 => true
    _ => false
}

m would be true if t was between 0 and 10, but false otherwise. This little bit doesn't work of course, but is there any way to achieve something like it?

David J.
  • 31,569
  • 22
  • 122
  • 174
Justin Poliey
  • 16,289
  • 7
  • 37
  • 48
  • 4
    Note that by writing "0 until 10" you mean 0, 1, 2, ..., 9 (including 0, excluding 10). If you want to include 10, use "0 to 10". – Jesper Aug 28 '09 at 11:11
  • See a related stackoverflow question: [How can I pattern match on a range in Scala?](http://stackoverflow.com/questions/3160888/how-can-i-pattern-match-on-a-range-in-scala) – David J. Sep 26 '11 at 01:35
  • The title asks for how to match a value of type `Range` against several possibilities, e.g. "Do I have `(0..5)` or `(1..6)`?" – Raphael Sep 26 '11 at 18:33
  • `val m = 0 until 10 contains t` is effectively the same but shorter. it will get you the true/false answer. If a boolean answer is all you are after. – Peter Perháč May 14 '17 at 18:59

5 Answers5

81

Guard using Range:

val m = t match {
  case x if 0 until 10 contains x => true
  case _ => false
}
Alexander Azarov
  • 12,971
  • 2
  • 50
  • 54
34

You can use guards:

val m = t match {
    case x if (0 <= x && x < 10) => true
    case _ => false
}
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 3
    In terms of performance this solution is better than @alexander-azarov solution. There the Range needs to be initialised followed by a range scan. Especially for large ranges this can become a problem. – Oosterman May 20 '16 at 06:54
  • 1
    `Range.contains` is of course overridden so it doesn't need to scan anything! It's still a bit of extra code, but Hotspot should inline and optimize it without problem. – Alexey Romanov May 20 '16 at 08:33
4

With these definitions:

  trait Inspector[-C, -T] {
    def contains(collection: C, value: T): Boolean
  }

  implicit def seqInspector[T, C <: SeqLike[Any, _]] = new Inspector[C, T]{
    override def contains(collection: C, value: T): Boolean = collection.contains(value)
  }

  implicit def setInspector[T, C <: Set[T]] = new Inspector[C, T] {
    override def contains(collection: C, value: T): Boolean = collection.contains(value)
  }

  implicit class MemberOps[T](t: T) {
    def in[C](coll: C)(implicit inspector: Inspector[C, T]) =
      inspector.contains(coll, t)
  }

You can do checks like these:

2 in List(1, 2, 4)      // true
2 in List("foo", 2)     // true
2 in Set("foo", 2)      // true
2 in Set(1, 3)          // false
2 in Set("foo", "foo")  // does not compile
2 in List("foo", "foo") // false (contains on a list is not the same as contains on a set)
2 in (0 to 10)          // true

So the code you need would be:

val m = x in (0 to 10)
Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69
3

Here's another way to match using a range:

val m = t match {
  case x if ((0 to 10).contains(x)) => true
  case _ => false
}
swartzrock
  • 729
  • 5
  • 6
2

Another option would be to actually add this to the language using implicits, i added two variations for int and Range

object ComparisonExt {
  implicit class IntComparisonOps(private val x : Int) extends AnyVal {
    def between(range: Range) = x >= range.head && x < range.last
    def between(from: Int, to: Int) = x >= from && x < to
  }

}

object CallSite {
  import ComparisonExt._

  val t = 5
  if (t between(0 until 10)) println("matched")
  if (!(20 between(0 until 10))) println("not matched")
  if (t between(0, 10)) println("matched")
  if (!(20 between(0, 10))) println("not matched")
}
Noam
  • 1,022
  • 8
  • 8