Comment faire correspondre des modèles à l'aide d'une expression régulière dans Scala?


124

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 pseudocode, cela pourrait ressembler à quelque chose comme:

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

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


9
Attention: dans les langages Scala (et * ML), le pattern matching a une autre signification, très différente des regexes.

1
Vous voulez probablement [a-cA-C]cette expression régulière.

2
dans scala 2.8, les chaînes sont converties en Traversable(comme Listet Array), si vous voulez les 3 premiers caractères, essayez "my string".take(3), pour le premier"foo".head
shellholic

Réponses:


237

Vous pouvez le faire car les expressions régulières définissent des extracteurs, mais vous devez d'abord définir le modèle d'expression régulière. Je n'ai pas accès à un REPL Scala pour tester cela mais quelque chose comme ça devrait fonctionner.

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

5
méfiez-vous que vous ne pouvez pas déclarer un groupe de capture et ne pas l'utiliser (c'est-à-dire que la casse Pattern () ne correspondra pas ici)
Jeremy Leipzig

34
Attention, vous devez utiliser des groupes dans votre expression régulière: val Pattern = "[a-cA-C]".rne fonctionnera pas. En effet, la casse de correspondance utilise unapplySeq(target: Any): Option[List[String]], qui renvoie les groupes correspondants.
rakensi

2
C'est une méthode sur StringLike qui renvoie un Regex .
asm

11
@rakensi No val r = "[A-Ca-c]".r ; 'a' match { case r() => } .. scala-lang.org/api/current/#scala.util.matching.Regex
som-snytt

3
@JeremyLeipzig groupes ignorant: val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
som-snytt

120

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

implicit class RegexOps(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

Mieux encore, on peut 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 Dynamicest montré dans l'article de blog Introduction to 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")
  }
}

J'ai beaucoup aimé la réponse, mais quand on a essayé de l'utiliser en dehors de REPL, elle s'est verrouillée (c'est-à-dire que le même code qui fonctionnait dans REPL ne fonctionnait pas dans l'application en cours d'exécution). Il y a aussi un problème avec l'utilisation du $signe comme motif de fin de ligne: le compilateur se plaint du manque de terminaison de chaîne.
Rajish

@Rajish: Je ne sais pas quel peut être le problème. Tout dans ma réponse est un code Scala valide depuis la version 2.10.
kiritsuku du

@sschaef: ce case p.firstName.lastName.Map(...modèle - comment diable puis -je lire cela?
Erik Kaplun

1
@ErikAllik l'a lu comme quelque chose comme "quand 'firstName' commence par 'Jo' et 'secondName' correspond à l'expression régulière donnée, que la correspondance est réussie". Ceci est plus un exemple de puissance Scalas, je n'écrirais pas ce cas d'utilisation dans l'exemple de cette façon dans le code de production. Btw, l'utilisation d'une carte devrait être remplacée par une liste, car une carte n'est pas ordonnée et pour plus de valeurs, il n'est plus garanti que la bonne variable correspond au bon matcher.
kiritsuku

1
C'est très pratique pour un prototypage rapide, mais notez que cela crée une nouvelle instance de Regexchaque fois que la correspondance est vérifiée. Et c'est une opération assez coûteuse qui implique la compilation du modèle regex.
HRJ

51

Comme l'a souligné delnan, le matchmot - clé dans Scala n'a rien à voir avec les expressions régulières. Pour savoir si une chaîne correspond à une expression régulière, vous pouvez utiliser la String.matchesméthode. Pour savoir si une chaîne commence par un a, b ou c en minuscule ou en majuscule, l'expression régulière ressemblerait à ceci:

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

Vous pouvez lire cette expression régulière comme "l'un des caractères a, b, c, A, B ou C suivi de n'importe quoi" ( .signifie "n'importe quel caractère" et *signifie "zéro fois ou plus", donc ". *" Est une chaîne) .


25

Pour développer un peu la réponse d'Andrew : Le fait que les expressions régulières définissent des extracteurs peut être utilisé pour décomposer très bien les sous-chaînes correspondant à l'expression régulière en utilisant la correspondance de motifs 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.
}

Je suis vraiment confus par le chapeau haut de forme ^. Je pensais que "^" voulait dire "Correspond au début de la ligne". Cela ne correspond pas au début de la ligne.
Michael Lafayette le

@MichaelLafayette: À l'intérieur d'une classe de caractères ( []), le signe curseur indique la négation, donc [^\s]signifie «sans espace».
Fabian Steeg le

9

String.matches est le moyen de faire la correspondance de motifs dans le sens de l'expression régulière.

Mais en passant, word.firstLetter en vrai code Scala ressemble à:

word(0)

Scala traite les chaînes comme une séquence de caractères, donc si pour une raison quelconque vous vouliez explicitement obtenir le premier caractère de la chaîne et le faire correspondre, vous pouvez utiliser quelque chose comme ceci:

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

Je ne propose pas cela comme moyen général de faire la correspondance de motifs 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 expression régulière.

EDIT: Pour être clair, la façon dont je ferais cela est, 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 pseudo-code initial. À votre santé!


3
"Cat"(0).toStringpourrait être plus clairement écrit comme "Cat" take 1, à mon humble avis.
David Winslow le

Aussi (bien que ce soit une vieille discussion - je suis probablement en train de creuser des tombes): vous pouvez supprimer le '. *' De la fin car il n'ajoute aucune valeur à l'expression régulière. Just "Cat" .matches ("^ [a-cA-C]")
akauppi

Aujourd'hui , sur 2,11, val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt

Que signifie le charleston (^)?
Michael Lafayette

C'est une ancre signifiant «début de ligne» ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Donc, tout ce qui suit le charleston correspondra au modèle si c'est la première chose sur la ligne.
Janx

9

Notez que l'approche de la réponse de @ AndrewMyers fait correspondre 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 en utilisant ^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

1
Idiomatiques, val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Plus idiomatiquement, val resans toutes les majuscules.
som-snytt

9

Tout d'abord, nous devons 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 modèle 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 que nous devons faire est de le rendre plus puissant par Scala. Voici d'autres exemples dans le document Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.