Come modellare la corrispondenza usando l'espressione regolare in Scala?


124

Vorrei poter trovare una corrispondenza tra la prima lettera di una parola e una delle lettere di un gruppo come "ABC". In pseudocodice, questo potrebbe assomigliare a:

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

Ma come afferrare la prima lettera in Scala anziché Java? Come esprimo correttamente l'espressione regolare? È possibile farlo all'interno di una classe di casi ?


9
Attenzione: in Scala (e nelle lingue * ML), il pattern matching ha un altro significato molto diverso dalle regex.

1
Probabilmente vuoi [a-cA-C]quell'espressione regolare.

2
in scala 2.8, le stringhe vengono convertite in Traversable(like Liste Array), se vuoi i primi 3 caratteri, prova "my string".take(3)per il primo"foo".head
shellholic

Risposte:


237

Puoi farlo perché le espressioni regolari definiscono gli estrattori ma devi prima definire il modello regex. Non ho accesso a un REPL Scala per testarlo, ma qualcosa del genere dovrebbe funzionare.

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

5
attenzione che non è possibile dichiarare un gruppo di acquisizione e quindi non utilizzarlo (ad esempio, il caso Pattern () non corrisponderà qui)
Jeremy Leipzig

34
Attenzione che è necessario utilizzare i gruppi nell'espressione regolare: val Pattern = "[a-cA-C]".rnon funzionerà. Ciò è dovuto al fatto che utilizza le maiuscole e minuscole unapplySeq(target: Any): Option[List[String]], che restituisce i gruppi corrispondenti.
rakensi,

2
È un metodo su StringLike che restituisce un Regex .
asm

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

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

120

Dalla versione 2.10, è possibile utilizzare la funzione di interpolazione delle stringhe di 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

Ancora meglio si possono associare gruppi di espressioni regolari:

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

È anche possibile impostare meccanismi di associazione più dettagliati:

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 esempio impressionante di ciò che è possibile Dynamicè mostrato nel post sul blog Introduzione al tipo dinamico :

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")
  }
}

Mi è piaciuta molto la risposta, ma quando ha provato a usarla al di fuori di REPL è bloccata (cioè esattamente lo stesso codice che ha funzionato in REPL non ha funzionato nell'app in esecuzione). Inoltre, esiste un problema con l'utilizzo del $segno come modello di fine riga: il compilatore si lamenta della mancanza di terminazione della stringa.
Rajish,

@Rajish: non so quale possa essere il problema. Tutto nella mia risposta è un codice Scala valido dal 2.10.
Kiritsuku,

@sschaef: quel case p.firstName.lastName.Map(...modello: come mai lo leggo?
Erik Kaplun,

1
@ErikAllik lo legge come qualcosa di simile "quando" firstName "inizia con" Jo "e" secondName "corrisponde alla regex specificata, quindi la corrispondenza ha esito positivo". Questo è più un esempio del potere di Scalas, non scriverei questo caso d'uso nell'esempio in questo modo nel codice di produzione. A proposito, l'uso di una mappa dovrebbe essere sostituito da un elenco, perché una mappa non è ordinata e per più valori non è più garantito che la variabile giusta corrisponda al corrispondente corrispondente.
Kiritsuku,

1
Questo è molto comodo per la prototipazione rapida, ma nota che questo crea una nuova istanza Regexogni volta che viene verificata la corrispondenza. E questa è un'operazione piuttosto costosa che comporta la compilazione del modello regex.
HRJ,

51

Come ha sottolineato Delnan, la matchparola chiave in Scala non ha nulla a che fare con le regex. Per scoprire se una stringa corrisponde a una regex, puoi usare il String.matchesmetodo Per scoprire se una stringa inizia con a, b o c in lettere minuscole o maiuscole, la regex dovrebbe apparire così:

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

Puoi leggere questa regex come "uno dei caratteri a, b, c, A, B o C seguito da qualsiasi cosa" ( .significa "qualsiasi carattere" e *significa "zero o più volte", quindi ". *" È qualsiasi stringa) .


25

Per espandere un po ' la risposta di Andrew : il fatto che le espressioni regolari definiscano gli estrattori può essere usato per scomporre le sottostringhe abbinate dal regex in modo molto piacevole usando la corrispondenza del modello di Scala, ad esempio:

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.
}

Sono davvero confuso dal cappello alto ^. Pensavo che "^" significasse "Abbina l'inizio della riga". Non corrisponde all'inizio della riga.
Michael Lafayette,

@MichaelLafayette: all'interno di una classe di caratteri ( []), il cursore indica negazione, quindi [^\s]significa "non-spazi bianchi".
Fabian Steeg,

9

String.matches è il modo di eseguire la corrispondenza dei pattern in senso regex.

Ma a parte questo, word.firstLetter nel vero codice Scala sembra:

word(0)

Scala tratta le stringhe come una sequenza di caratteri, quindi se per qualche motivo volessi ottenere esplicitamente il primo carattere della stringa e abbinarlo, potresti usare qualcosa del genere:

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

Non sto proponendo questo come il modo generale per fare la corrispondenza del pattern regex, ma è in linea con il tuo approccio proposto per trovare prima il primo carattere di una stringa e poi confrontarlo con un regex.

EDIT: Per essere chiari, il modo in cui lo farei è, come altri hanno detto:

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

Volevo solo mostrare un esempio il più vicino possibile al tuo pseudocodice iniziale. Saluti!


3
"Cat"(0).toStringpotrebbe essere più chiaramente scritto come "Cat" take 1, imho.
David Winslow,

Inoltre (anche se questa è una vecchia discussione - probabilmente sto scavando in profondità): puoi rimuovere '. *' Dalla fine poiché non aggiunge alcun valore alla regex. Just "Cat" .matches ("^ [a-cA-C]")
akauppi

Oggi su 2.11, val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
som-snytt

Cosa significa hi hat (^)?
Michael Lafayette,

È un'ancora che significa "inizio della linea" ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Quindi tutto ciò che segue il cappello hi corrisponderà al modello se è la prima cosa sulla linea.
Janx,

9

Si noti che l'approccio dalla risposta di @ AndrewMyers abbina l' intera stringa all'espressione regolare, con l'effetto di ancorare l'espressione regolare ad entrambe le estremità della stringa usando ^e $. Esempio:

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

E con no .*alla fine:

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
Idiomaticamente, val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Più idiomaticamente, val resenza maiuscole.
som-snytt

9

Innanzitutto dovremmo sapere che l'espressione regolare può essere utilizzata separatamente. Ecco un esempio:

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

In secondo luogo, dovremmo notare che combinare l'espressione regolare con la corrispondenza dei modelli sarebbe molto potente. Qui c'è un semplice esempio.

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

In effetti, l'espressione regolare stessa è già molto potente; l'unica cosa che dobbiamo fare è renderlo più potente da Scala. Ecco altri esempi nel documento Scala: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.