Wie kann man eine Übereinstimmung mit regulären Ausdrücken in Scala herstellen?

124

Ich möchte in der Lage sein, eine Übereinstimmung zwischen dem ersten Buchstaben eines Wortes und einem der Buchstaben in einer Gruppe wie "ABC" zu finden. Im Pseudocode könnte dies ungefähr so ​​aussehen:

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

Aber wie kann ich den ersten Buchstaben in Scala anstelle von Java abrufen? Wie drücke ich den regulären Ausdruck richtig aus? Ist dies innerhalb einer Fallklasse möglich ?

Bruce Ferguson
quelle
9
Seien Sie gewarnt: In Scala (und * ML-Sprachen) hat der Mustervergleich eine andere Bedeutung, die sich stark von den regulären Ausdrücken unterscheidet.
1
Sie möchten wahrscheinlich [a-cA-C]diesen regulären Ausdruck.
2
In Scala 2.8 werden Zeichenfolgen in Traversable(wie Listund Array) konvertiert. Wenn Sie die ersten 3 Zeichen möchten, versuchen Sie es "my string".take(3)für den ersten"foo".head
Shellholic

Antworten:

237

Sie können dies tun, weil reguläre Ausdrücke Extraktoren definieren, Sie jedoch zuerst das Regex-Muster definieren müssen. Ich habe keinen Zugriff auf eine Scala REPL, um dies zu testen, aber so etwas sollte funktionieren.

val Pattern = "([a-cA-C])".r
word.firstLetter match {
   case Pattern(c) => c bound to capture group here
   case _ =>
}
asm
quelle
5
Beachten Sie, dass Sie eine Erfassungsgruppe nicht deklarieren und dann nicht verwenden können (dh Fallmuster () stimmt hier nicht überein)
Jeremy Leipzig
34
Beachten Sie, dass Sie in Ihrem regulären Ausdruck Gruppen verwenden müssen : val Pattern = "[a-cA-C]".rfunktioniert nicht. Dies liegt daran, dass Match-Case verwendet wird unapplySeq(target: Any): Option[List[String]], wodurch die passenden Gruppen zurückgegeben werden.
Rakensi
2
Es ist eine Methode unter StringLike, die einen Regex zurückgibt .
Asm
11
@rakensi Nr val r = "[A-Ca-c]".r ; 'a' match { case r() => } . scala-lang.org/api/current/#scala.util.matching.Regex
som-snytt
3
@JeremyLeipzig Gruppen ignorieren : val r = "([A-Ca-c])".r ; "C" match { case r(_*) => }.
Som-Snytt
120

Seit Version 2.10 kann die String-Interpolationsfunktion von Scala verwendet werden:

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

Noch besser kann man reguläre Ausdrucksgruppen binden:

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

Es ist auch möglich, detailliertere Bindungsmechanismen festzulegen:

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

Ein beeindruckendes Beispiel dafür, was mit möglich Dynamicist, finden Sie im Blog-Beitrag Einführung in 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")
  }
}
kiritsuku
quelle
Die Antwort hat mir sehr gut gefallen, aber als ich versuchte, sie außerhalb von REPL zu verwenden, war sie gesperrt (dh genau der gleiche Code, der in REPL funktioniert hat, hat in der laufenden App nicht funktioniert). Es gibt auch ein Problem bei der Verwendung des $Vorzeichens als Zeilenendmuster: Der Compiler beschwert sich über das Fehlen einer Zeichenfolgenbeendigung.
Rajish
@ Rajish: Weiß nicht, was das Problem sein kann. Alles in meiner Antwort ist seit 2.10 gültiger Scala-Code.
Kiritsuku
@sschaef: dieses case p.firstName.lastName.Map(...Muster - wie um alles in der Welt lese ich das?
Erik Kaplun
1
@ErikAllik las es so etwas wie "Wenn 'Vorname' mit 'Jo' beginnt und 'Nachname' mit dem angegebenen regulären Ausdruck übereinstimmt, ist die Übereinstimmung erfolgreich". Dies ist eher ein Beispiel für die Leistung von Scalas. Ich würde diesen Anwendungsfall in diesem Beispiel nicht so in Produktionscode schreiben. Übrigens sollte die Verwendung einer Karte durch eine Liste ersetzt werden, da eine Karte ungeordnet ist und für mehr Werte nicht mehr garantiert werden kann, dass die richtige Variable mit dem richtigen Matcher übereinstimmt.
Kiritsuku
1
Dies ist sehr praktisch für das schnelle Prototyping. Beachten Sie jedoch, dass bei Regexjeder Überprüfung der Übereinstimmung eine neue Instanz erstellt wird. Und das ist eine ziemlich kostspielige Operation, bei der das Regex-Muster kompiliert wird.
HRJ
51

Wie Delnan betonte, hat das matchSchlüsselwort in Scala nichts mit regulären Ausdrücken zu tun. Um herauszufinden, ob eine Zeichenfolge mit einem regulären Ausdruck übereinstimmt, können Sie die String.matchesMethode verwenden. Um herauszufinden, ob eine Zeichenfolge mit einem a, b oder c in Klein- oder Großbuchstaben beginnt, sieht der reguläre Ausdruck folgendermaßen aus:

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

Sie können diesen regulären Ausdruck als "eines der Zeichen a, b, c, A, B oder C gefolgt von irgendetwas" lesen ( .bedeutet "beliebiges Zeichen" und *bedeutet "null oder mehrmals", also ". *" Ist eine beliebige Zeichenfolge). .

sepp2k
quelle
25

Um Andrews Antwort ein wenig zu erweitern : Die Tatsache, dass reguläre Ausdrücke Extraktoren definieren, kann verwendet werden, um die vom regulären Ausdruck übereinstimmenden Teilzeichenfolgen mithilfe von Scalas Mustervergleich sehr gut zu zerlegen, z.

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.
}
Fabian Steeg
quelle
Ich bin wirklich verwirrt von dem hohen Hut. Ich dachte "^" meinte "Match the begin of the line". Es stimmt nicht mit dem Zeilenanfang überein.
Michael Lafayette
@MichaelLafayette: Innerhalb einer Zeichenklasse ( []) zeigt das Caret Negation an, [^\s]bedeutet also "Nicht-Leerzeichen".
Fabian Steeg
9

String.matches ist der Weg, um einen Mustervergleich im Sinne eines regulären Ausdrucks durchzuführen.

Abgesehen davon sieht word.firstLetter im echten Scala-Code folgendermaßen aus:

word(0)

Scala behandelt Strings als eine Folge von Chars. Wenn Sie also aus irgendeinem Grund explizit das erste Zeichen des Strings abrufen und es abgleichen möchten, können Sie Folgendes verwenden:

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

Ich schlage dies nicht als allgemeine Methode für den Abgleich von Regex-Mustern vor, aber es entspricht Ihrem vorgeschlagenen Ansatz, zuerst das erste Zeichen eines Strings zu finden und es dann mit einem Regex abzugleichen.

EDIT: Um klar zu sein, die Art und Weise, wie ich dies tun würde, ist, wie andere gesagt haben:

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

Ich wollte nur ein Beispiel zeigen, das Ihrem ursprünglichen Pseudocode so nahe wie möglich kommt. Prost!

Janx
quelle
3
"Cat"(0).toStringkönnte klarer geschrieben werden als "Cat" take 1, imho.
David Winslow
Auch (obwohl dies eine alte Diskussion ist - ich grabe wahrscheinlich ernsthaft): Sie können das '. *' Am Ende entfernen, da es dem regulären Ausdruck keinen Wert hinzufügt. Nur "Cat" .matches ("^ [a-cA-C]")
akauppi
Heute am 2.11 val r = "[A-Ca-c]".r ; "cat"(0) match { case r() => }.
Som-Snytt
Was bedeutet die Hi-Hat (^)?
Michael Lafayette
Es ist ein Anker, der "Anfang der Linie" bedeutet ( cs.duke.edu/csl/docs/unix_course/intro-73.html ). Alles, was der Hi-Hat folgt, stimmt also mit dem Muster überein, wenn es das erste ist, was auf der Linie steht.
Janx
9

Beachten Sie, dass der Ansatz aus der Antwort von @ AndrewMyers die gesamte Zeichenfolge mit dem regulären Ausdruck übereinstimmt, wodurch der reguläre Ausdruck an beiden Enden der Zeichenfolge mit ^und verankert wird $. Beispiel:

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

Und ohne .*am Ende:

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
Mikhail bei YugaByte
quelle
1
Idiomatisch , val MY_RE2 = "(foo|bar)".r.unanchored ; "foo123" match { case MY_RE2(_*) => }. Idiomatischer, val reohne alle Kappen.
Som-Snytt
9

Zunächst sollten wir wissen, dass reguläre Ausdrücke separat verwendet werden können. Hier ist ein Beispiel:

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

Zweitens sollten wir beachten, dass die Kombination von regulären Ausdrücken mit Mustervergleich sehr leistungsfähig wäre. Hier ist ein einfaches Beispiel.

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

In der Tat ist der reguläre Ausdruck selbst bereits sehr mächtig; Das einzige, was wir tun müssen, ist, es von Scala mächtiger zu machen. Weitere Beispiele finden Sie im Scala-Dokument: http://www.scala-lang.org/files/archive/api/current/index.html#scala.util.matching.Regex

Haimei
quelle