Salim Fadhley
Salim Fadhley

Reputation: 8195

Does Scala provide an idiomatic way to partially match strings other than regular expressions?

This bit of code implements a mini string matching language - which understands two symbols (?=match any char, *=match one or more char). This code seems to work just fine, but it seems a little bit inelegant.

object Re {
  def check(search: String, input: String):Boolean = {
    search match {
      case `input` => true
      case x if x.startsWith("?") =>
        check(x.stripPrefix("?"), input.tail)
      case x if x.startsWith("*") => input match {
        case "" => false
        case i => check(x.stripPrefix("*"), i.tail) | check(x, i.tail)
      }
      case _ => false
    }
  }
}

Specifically, I dislike the cases where I say x if x.startswith(something) and then have to strip that something.

Does scala have a more idiomatic way to do this? Something along the lines of how the Seq matcher works so that I don't need startsWith or stripPrefix.

Upvotes: 2

Views: 236

Answers (2)

tkachuko
tkachuko

Reputation: 1986

No to my best knowledge. So i would rather stick to sequences pattern matching:

  def check(search: String, input: String): Boolean = check(search.toList, input.toList)

  def check(search: List[Char], input: List[Char]): Boolean = {
    search match {
      case _ if search == `input` => true
      case '?' :: tail => check(tail, input.tail)
      case '*' :: tail => if (input.isEmpty) false else check(tail, input.tail) | check(search, input.tail)
      case _ => false
    }
  }

It might be not so elegant solution as in @0__ answer but in case this function is going to change in the future IMO it would be better to have capabilities of sequence pattern matching.

Hope it helps!

Upvotes: 2

0__
0__

Reputation: 67280

It would be great if +: worked as extractor, but unfortunately the type conversion fails:

val head +: tail = "hello"

<console>:54: error: scrutinee is incompatible with pattern type;
 found   : Coll with scala.collection.SeqLike[T,Coll]
 required: String
       val head +: tail = "hello"
                ^

But you could define an analogous extractor:

object ~> {
  def unapply(s: String): Option[(Char, String)] = 
    if (s.isEmpty) None else Some((s.charAt(0), s.substring(1)))
}

def check(search: String, input: String): Boolean = (search, input) match {
  case (`input`, _) => true
  case ('?' ~> stail, _ ~> itail) => check(stail, itail)
  case ('*' ~> stail, _ ~> itail) => check(stail, itail) || check(search, itail)
  case _ => false
}

assert(!check("gaga", "baba"))
assert( check("gaga", "gaga"))
assert(!check("?gaga", "gaga"))
assert( check("?gaga", "Xgaga"))
assert(!check("?gaga", "XYgaga"))
assert( check("*gaga", "Xgaga"))
assert( check("*gaga", "XYgaga"))
assert(!check("*gaga", "gaga"))

Upvotes: 6

Related Questions