web-dev-qa-db-fra.com

Comment faire correspondre un motif en utilisant une expression régulière dans Scala?

J'aimerais pouvoir trouver une correspondance entre la première lettre d'un mot et l'une des lettres d'un groupe tel que "ABC". En pseudo-code, cela pourrait ressembler à:

case Process(Word) =>
   Word.firstLetter match {
      case([a-c][A-C]) =>
      case _ =>
   }
}

Mais comment puis-je saisir la première lettre dans Scala au lieu de Java? Comment exprimer correctement l'expression régulière? Est-il possible de le faire dans une classe de cas ?

118
Bruce Ferguson

Vous pouvez le faire car les expressions régulières définissent des extracteurs, mais vous devez d’abord définir le motif regex. Je n'ai pas accès à un Scala REPL pour tester ceci mais quelque chose comme cela devrait marcher).

val Pattern = "([a-cA-C])".r
Word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}
227
asm

Depuis la version 2.10, on peut utiliser la fonction d'interpolation de chaîne de Scala:

implicit class Regex(sc: StringContext) {
  def r = new util.matching.Regex(sc.parts.mkString, sc.parts.tail.map(_ => "x"): _*)
}

scala> "123" match { case r"\d+" => true case _ => false }
res34: Boolean = true

Encore mieux, vous pouvez lier des groupes d’expressions régulières:

scala> "123" match { case r"(\d+)$d" => d.toInt case _ => 0 }
res36: Int = 123

scala> "10+15" match { case r"(\d\d)${first}\+(\d\d)${second}" => first.toInt+second.toInt case _ => 0 }
res38: Int = 25

Il est également possible de définir des mécanismes de liaison plus détaillés:

scala> object Doubler { def unapply(s: String) = Some(s.toInt*2) }
defined module Doubler

scala> "10" match { case r"(\d\d)${Doubler(d)}" => d case _ => 0 }
res40: Int = 20

scala> object isPositive { def unapply(s: String) = s.toInt >= 0 }
defined module isPositive

scala> "10" match { case r"(\d\d)${d @ isPositive()}" => d.toInt case _ => 0 }
res56: Int = 10

Un exemple impressionnant de ce qui est possible avec Dynamic est présenté dans l'article de blog Introduction à Type Dynamic:

object T {

  class RegexpExtractor(params: List[String]) {
    def unapplySeq(str: String) =
      params.headOption flatMap (_.r unapplySeq str)
  }

  class StartsWithExtractor(params: List[String]) {
    def unapply(str: String) =
      params.headOption filter (str startsWith _) map (_ => str)
  }

  class MapExtractor(keys: List[String]) {
    def unapplySeq[T](map: Map[String, T]) =
      Some(keys.map(map get _))
  }

  import scala.language.dynamics

  class ExtractorParams(params: List[String]) extends Dynamic {
    val Map = new MapExtractor(params)
    val StartsWith = new StartsWithExtractor(params)
    val Regexp = new RegexpExtractor(params)

    def selectDynamic(name: String) =
      new ExtractorParams(params :+ name)
  }

  object p extends ExtractorParams(Nil)

  Map("firstName" -> "John", "lastName" -> "Doe") match {
    case p.firstName.lastName.Map(
          Some(p.Jo.StartsWith(fn)),
          Some(p.`.*(\\w)$`.Regexp(lastChar))) =>
      println(s"Match! $fn ...$lastChar")
    case _ => println("nope")
  }
}
115
kiritsuku

Comme Delnan l'a fait remarquer, le mot clé match dans Scala n'a rien à voir avec les expressions rationnelles. Pour savoir si une chaîne correspond à une expression rationnelle, vous pouvez utiliser le paramètre String.matches méthode. Pour savoir si une chaîne commence par a, b ou c en minuscule ou en majuscule, la regex ressemblerait à ceci:

Word.matches("[a-cA-C].*")

Vous pouvez lire cette expression rationnelle comme "un des caractères a, b, c, a, b ou c suivi de rien" (. signifie "n'importe quel caractère" et * signifie "zéro fois ou plus", ainsi ". *" est une chaîne).

48
sepp2k

Pour développer un peu sur réponse d'Andrew : Le fait que les expressions régulières définissent des extracteurs peut être utilisé pour décomposer très gentiment les sous-chaînes mises en correspondance par la regex à l'aide du filtrage par motif de Scala, par exemple:

val Process = """([a-cA-C])([^\s]+)""".r // define first, rest is non-space
for (p <- Process findAllIn "aha bah Cah dah") p match {
  case Process("b", _) => println("first: 'a', some rest")
  case Process(_, rest) => println("some first, rest: " + rest)
  // etc.
}
24
Fabian Steeg

String.matches est le moyen de faire une correspondance de motif au sens de regex.

Mais, mis à part, Word.firstLetter dans le code réel Scala) ressemble à ceci:

Word(0)

Scala considère les chaînes comme une séquence de caractères. Par conséquent, si, pour une raison quelconque, vous souhaitez explicitement obtenir le premier caractère de la chaîne et le faire correspondre, vous pouvez utiliser quelque chose comme celui-ci:

"Cat"(0).toString.matches("[a-cA-C]")
res10: Boolean = true

Je ne propose pas cela comme moyen général de faire une correspondance de motif regex, mais cela correspond à votre approche proposée pour trouver d'abord le premier caractère d'une chaîne, puis le faire correspondre à une regex.

EDIT: Pour être clair, voici comment je procéderais, comme d’autres l’ont dit:

"Cat".matches("^[a-cA-C].*")
res14: Boolean = true

Je voulais juste montrer un exemple aussi proche que possible de votre pseudocode initial. À votre santé!

8
Janx

Notez que l'approche de la réponse de @ AndrewMyers associe la chaîne entière à l'expression régulière, avec pour effet d'ancrer l'expression régulière aux deux extrémités de la chaîne à l'aide de ^ et $. Exemple:

scala> val MY_RE = "(foo|bar).*".r
MY_RE: scala.util.matching.Regex = (foo|bar).*

scala> val result = "foo123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = foo

scala> val result = "baz123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = No match

scala> val result = "abcfoo123" match { case MY_RE(m) => m; case _ => "No match" }
result: String = No match

Et sans .* à la fin:

scala> val MY_RE2 = "(foo|bar)".r
MY_RE2: scala.util.matching.Regex = (foo|bar)

scala> val result = "foo123" match { case MY_RE2(m) => m; case _ => "No match" }
result: String = No match
8
Mikhail at YugaByte

Premièrement, nous devrions savoir que l'expression régulière peut être utilisée séparément. Voici un exemple:

import scala.util.matching.Regex
val pattern = "Scala".r // <=> val pattern = new Regex("Scala")
val str = "Scala is very cool"
val result = pattern findFirstIn str
result match {
  case Some(v) => println(v)
  case _ =>
} // output: Scala

Deuxièmement, nous devrions remarquer que combiner une expression régulière avec une correspondance de motif serait très puissant. Voici un exemple simple.

val date = """(\d\d\d\d)-(\d\d)-(\d\d)""".r
"2014-11-20" match {
  case date(year, month, day) => "hello"
} // output: hello

En fait, l'expression régulière elle-même est déjà très puissante. la seule chose à faire est de le rendre plus puissant avec Scala. Voici d'autres exemples dans Scala Document: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching .Regex

6
Haimei