Scala Language
Musterabgleich
Suche…
Syntax
- Selektor stimmen mit PartialFunction überein
- Selektorübereinstimmung {Liste der Fallalternativen) // Dies ist die häufigste Form der obigen
Parameter
Parameter | Einzelheiten |
---|---|
Wähler | Der Ausdruck, dessen Wert mit einem Muster abgeglichen wird. |
Alternativen | eine Liste von Alternativen, case denen der case begrenzt ist. |
Einfache Musterübereinstimmung
Dieses Beispiel zeigt, wie eine Eingabe mit mehreren Werten abgeglichen wird:
def f(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
case _ => "Unknown!"
}
f(2) // "Two"
f(3) // "Unknown!"
Hinweis: _
ist der Fall oder Standardfall , ist aber nicht erforderlich.
def g(x: Int): String = x match {
case 1 => "One"
case 2 => "Two"
}
g(1) // "One"
g(3) // throws a MatchError
Um das Auslösen einer Ausnahme zu vermeiden, ist es hier am besten, die Standardprogrammierung zu behandeln ( case _ => <do something>
). Beachten Sie, dass der Vergleich über eine Fallklasse helfen kann, dass der Compiler eine Warnung ausgibt, wenn ein Fall fehlt. Dasselbe gilt für benutzerdefinierte Typen, die eine versiegelte Eigenschaft erweitern. Wenn die Übereinstimmung insgesamt ist, ist möglicherweise kein Standardfall erforderlich
Es ist auch möglich, mit Werten zu vergleichen, die nicht inline definiert sind. Dies müssen stabile Bezeichner sein , die entweder durch Verwendung eines Großbuchstaben oder durch Einschließen von Backticks erhalten werden.
Mit One
und two
die anderswo definiert oder als Funktionsparameter übergeben werden:
val One: Int = 1
val two: Int = 2
Sie können auf folgende Weise gegeneinander abgeglichen werden:
def g(x: Int): String = x match {
case One => "One"
case `two` => "Two"
}
Im Gegensatz zu anderen Programmiersprachen wie Java gibt es keinen Durchbruch. Wenn ein Fallblock mit einer Eingabe übereinstimmt, wird er ausgeführt und der Abgleich ist abgeschlossen. Daher sollte der kleinste spezifische Fall der letzte Fallblock sein.
def f(x: Int): String = x match {
case _ => "Default"
case 1 => "One"
}
f(5) // "Default"
f(1) // "Default"
Musterabgleich mit stabiler Kennung
Bei der Standardmusterübereinstimmung spiegelt der verwendete Bezeichner jeden Bezeichner im umschließenden Bereich. Manchmal ist es notwendig, die Variable des umgebenden Bereichs abzugleichen.
Die folgende Beispielfunktion nimmt ein Zeichen und eine Liste von Tupeln und gibt eine neue Liste von Tupeln zurück. Wenn das Zeichen als erstes Element in einem der Tupel vorhanden war, wird das zweite Element inkrementiert. Wenn es noch nicht in der Liste vorhanden ist, wird ein neues Tupel erstellt.
def tabulate(char: Char, tab: List[(Char, Int)]): List[(Char, Int)] = tab match {
case Nil => List((char, 1))
case (`char`, count) :: tail => (char, count + 1) :: tail
case head :: tail => head :: tabulate(char, tail)
}
Das obige Beispiel zeigt einen Mustervergleich, bei dem die Eingabe char
der Methode im Mustervergleich "stabil" gehalten wird. Wenn Sie also tabulate('x', ...)
aufrufen, wird die erste case-Anweisung folgendermaßen interpretiert:
case('x', count) => ...
Scala interpretiert jede mit einem Häkchen markierte Variable als stabile Kennung: Sie interpretiert auch jede Variable, die mit einem Großbuchstaben beginnt, auf dieselbe Weise.
Pattern Matching auf einer Seq
Nach einer genauen Anzahl von Elementen in der Sammlung suchen
def f(ints: Seq[Int]): String = ints match {
case Seq() =>
"The Seq is empty !"
case Seq(first) =>
s"The seq has exactly one element : $first"
case Seq(first, second) =>
s"The seq has exactly two elements : $first, $second"
case s @ Seq(_, _, _) =>
s"s is a Seq of length three and looks like ${s}" // Note individual elements are not bound to their own names.
case s: Seq[Int] if s.length == 4 =>
s"s is a Seq of Ints of exactly length 4" // Again, individual elements are not bound to their own names.
case _ =>
"No match was found!"
}
Das erste (s) Element (e) extrahieren und den Rest als Sammlung behalten:
def f(ints: Seq[Int]): String = ints match {
case Seq(first, second, tail @ _*) =>
s"The seq has at least two elements : $first, $second. The rest of the Seq is $tail"
case Seq(first, tail @ _*) =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
// alternative syntax
// here of course this one will never match since it checks
// for the same thing as the one above
case first +: tail =>
s"The seq has at least one element : $first. The rest of the Seq is $tail"
case _ =>
"The seq didn't match any of the above, so it must be empty"
}
Im Allgemeinen kann jedes Formular, das zum Erstellen einer Sequenz verwendet werden kann, zum Musterabgleich mit einer vorhandenen Sequenz verwendet werden.
Beachten Sie, dass Nil
und ::
funktionieren, wenn ein Pattern mit einer Sequenz übereinstimmt, es jedoch in eine List
konvertiert und unerwartete Ergebnisse erzielen kann. Beschränken Sie sich auf Seq( ...)
und +:
um dies zu vermeiden.
Beachten Sie, dass die Verwendung von ::
für WrappedArray
, Vector
usw. nicht funktioniert, siehe:
scala> def f(ints:Seq[Int]) = ints match {
| case h :: t => h
| case _ => "No match"
| }
f: (ints: Seq[Int])Any
scala> f(Array(1,2))
res0: Any = No match
Und mit +:
scala> def g(ints:Seq[Int]) = ints match {
| case h+:t => h
| case _ => "No match"
| }
g: (ints: Seq[Int])Any
scala> g(Array(1,2).toSeq)
res4: Any = 1
Wachen (wenn Ausdrücke)
Case-Anweisungen können mit if-Ausdrücken kombiniert werden, um beim Pattern-Matching zusätzliche Logik bereitzustellen.
def checkSign(x: Int): String = {
x match {
case a if a < 0 => s"$a is a negative number"
case b if b > 0 => s"$b is a positive number"
case c => s"$c neither positive nor negative"
}
}
Es ist wichtig sicherzustellen, dass Ihre Wachen keine nicht erschöpfenden Übereinstimmungen erstellen (der Compiler fängt das oft nicht an)
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case None => doSomethingIfNone
}
Dies wirft einen MatchError
auf ungerade Zahlen. Sie müssen entweder für alle Fälle ein Konto verwenden oder einen Platzhalter für Übereinstimmungen mit Platzhaltern verwenden:
def f(x: Option[Int]) = x match {
case Some(i) if i % 2 == 0 => doSomething(i)
case _ => doSomethingIfNoneOrOdd
}
Musterabgleich mit Fallklassen
Jede Fallklasse definiert einen Extraktor, mit dem die Mitglieder der Fallklasse beim Mustervergleich erfasst werden können:
case class Student(name: String, email: String)
def matchStudent1(student: Student): String = student match {
case Student(name, email) => s"$name has the following email: $email" // extract name and email
}
Es gelten alle normalen Regeln des Musterabgleichs. Sie können Wachen und konstante Ausdrücke verwenden, um den Abgleich zu steuern:
def matchStudent2(student: Student): String = student match {
case Student("Paul", _) => "Matched Paul" // Only match students named Paul, ignore email
case Student(name, _) if name == "Paul" => "Matched Paul" // Use a guard to match students named Paul, ignore email
case s if s.name == "Paul" => "Matched Paul" // Don't use extractor; use a guard to match students named Paul, ignore email
case Student("Joe", email) => s"Joe has email $email" // Match students named Joe, capture their email
case Student(name, email) if name == "Joe" => s"Joe has email $email" // use a guard to match students named Joe, capture their email
case Student(name, email) => s"$name has email $email." // Match all students, capture name and email
}
Übereinstimmung mit einer Option
Wenn Sie mit einem Optionstyp übereinstimmen:
def f(x: Option[Int]) = x match {
case Some(i) => doSomething(i)
case None => doSomethingIfNone
}
Dies entspricht funktional der Verwendung von fold
oder map
/ getOrElse
:
def g(x: Option[Int]) = x.fold(doSomethingIfNone)(doSomething)
def h(x: Option[Int]) = x.map(doSomething).getOrElse(doSomethingIfNone)
Musterübereinstimmung mit versiegelten Eigenschaften
Wenn ein Muster mit einem Objekt übereinstimmt, dessen Typ eine versiegelte Eigenschaft ist, prüft Scala zur Kompilierzeit, ob alle Fälle "vollständig übereinstimmen":
sealed trait Shape
case class Square(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
def matchShape(shape: Shape): String = shape match {
case Square(height, width) => "It's a square"
case Circle(radius) => "It's a circle"
//no case for Point because it would cause a compiler warning.
}
Wenn später eine neue case class
für Shape
hinzugefügt wird, werden alle match
in Shape
mit einer Compiler-Warnung ausgelöst. Dies erleichtert das gründliche Refactoring: Der Compiler weist den Entwickler auf den gesamten Code hin, der aktualisiert werden muss.
Musterabgleich mit Regex
val emailRegex: Regex = "(.+)@(.+)\\.(.+)".r
"[email protected]" match {
case emailRegex(userName, domain, topDomain) => println(s"Hi $userName from $domain")
case _ => println(s"This is not a valid email.")
}
In diesem Beispiel versucht der Regex, die angegebene E-Mail-Adresse abzugleichen. Wenn dies der Fall ist, werden userName
und domain
extrahiert und gedruckt. topDomain
wird ebenfalls extrahiert, aber in diesem Beispiel wird nichts damit gemacht. Das Aufrufen von .r
für einen String str
ist gleichbedeutend mit dem new Regex(str)
. Die Funktion r
steht über eine implizite Konvertierung zur Verfügung .
Musterordner (@)
Das @
-Zeichen bindet eine Variable während eines Mustervergleichs an einen Namen. Die gebundene Variable kann entweder das gesamte übereinstimmende Objekt oder ein Teil des übereinstimmenden Objekts sein:
sealed trait Shape
case class Rectangle(height: Int, width: Int) extends Shape
case class Circle(radius: Int) extends Shape
case object Point extends Shape
(Circle(5): Shape) match {
case Rectangle(h, w) => s"rectangle, $h x $w."
case Circle(r) if r > 9 => s"large circle"
case c @ Circle(_) => s"small circle: ${c.radius}" // Whole matched object is bound to c
case Point => "point"
}
> res0: String = small circle: 5
Der gebundene Bezeichner kann in bedingten Filtern verwendet werden. Somit:
case Circle(r) if r > 9 => s"large circle"
kann geschrieben werden als:
case c @ Circle(_) if c.radius > 9 => s"large circle"
Der Name kann nur an einen Teil des übereinstimmenden Musters gebunden sein:
Seq(Some(1), Some(2), None) match {
// Only the first element of the matched sequence is bound to the name 'c'
case Seq(c @ Some(1), _*) => head
case _ => None
}
> res0: Option[Int] = Some(1)
Musterübereinstimmungsarten
Pattern Matching kann auch verwendet werden, um den Typ einer Instanz zu prüfen, anstatt isInstanceOf[B]
:
val anyRef: AnyRef = ""
anyRef match {
case _: Number => "It is a number"
case _: String => "It is a string"
case _: CharSequence => "It is a char sequence"
}
//> res0: String = It is a string
Die Reihenfolge der Fälle ist wichtig:
anyRef match {
case _: Number => "It is a number"
case _: CharSequence => "It is a char sequence"
case _: String => "It is a string"
}
//> res1: String = It is a char sequence
Auf diese Weise ähnelt es einer klassischen "switch" -Anweisung ohne die Durchfallfunktionalität. Sie können jedoch auch Werte aus dem betreffenden Typ mit Mustern anpassen und "extrahieren". Zum Beispiel:
case class Foo(s: String)
case class Bar(s: String)
case class Woo(s: String, i: Int)
def matcher(g: Any):String = {
g match {
case Bar(s) => s + " is classy!"
case Foo(_) => "Someone is wicked smart!"
case Woo(s, _) => s + " is adventerous!"
case _ => "What are we talking about?"
}
}
print(matcher(Foo("Diana"))) // prints 'Diana is classy!'
print(matcher(Bar("Hadas"))) // prints 'Someone is wicked smart!'
print(matcher(Woo("Beth", 27))) // prints 'Beth is adventerous!'
print(matcher(Option("Katie"))) // prints 'What are we talking about?'
Beachten Sie, dass wir im Fall von Foo
und Woo
den Unterstrich ( _
) verwenden, um eine ungebundene Variable zu finden. Das heißt, dass der Wert (in diesem Fall Hadas
bzw. 27
) an keinen Namen gebunden ist und daher für diesen Fall nicht im Handler verfügbar ist. Dies ist eine nützliche Abkürzung, um einen beliebigen Wert abzugleichen, ohne sich um den Wert zu kümmern.
Pattern Matching als Tableswitch oder Lookupswitch kompiliert
Die @switch
Annotation teilt dem Compiler mit, dass die match
durch eine einzige tableswitch
auf Bytecode-Ebene ersetzt werden kann. Dies ist eine geringfügige Optimierung, durch die unnötige Vergleiche und variable Ladevorgänge während der Laufzeit entfernt werden können.
Die @switch
Annotation funktioniert nur für die Spiele gegen wörtliche Konstanten und final val
Bezeichner. Wenn die tableswitch
nicht als tableswitch
/ lookupswitch
kompiliert werden lookupswitch
, gibt der Compiler eine Warnung aus.
import annotation.switch
def suffix(i: Int) = (i: @switch) match {
case 1 => "st"
case 2 => "nd"
case 3 => "rd"
case _ => "th"
}
Die Ergebnisse sind die gleichen wie bei einer normalen Musterübereinstimmung:
scala> suffix(2)
res1: String = "2nd"
scala> suffix(4)
res2: String = "4th"
Aus der Scala-Dokumentation (2.8+) - @switch
:
Eine Anmerkung, die auf einen Übereinstimmungsausdruck angewendet werden soll. Wenn vorhanden, überprüft der Compiler, ob die Übereinstimmung mit einem Tabellenschalter oder einem Suchschalter erstellt wurde, und gibt einen Fehler aus, wenn er stattdessen in eine Reihe von bedingten Ausdrücken kompiliert wird.
Aus der Java-Spezifikation:
- Tabellenumschaltung : "Zugriff auf Sprungtabelle über Index und Sprung"
- Lookupswitch : "Zugriff auf Sprungtabelle durch Schlüsselübereinstimmung und Sprung"
Mehrere Muster gleichzeitig abgleichen
Die |
kann verwendet werden, um eine Übereinstimmung der einzelnen Case-Anweisungen mit mehreren Eingaben zu erzielen, um dasselbe Ergebnis zu erzielen:
def f(str: String): String = str match {
case "foo" | "bar" => "Matched!"
case _ => "No match."
}
f("foo") // res0: String = Matched!
f("bar") // res1: String = Matched!
f("fubar") // res2: String = No match.
Beachten Sie, dass der Abgleich von Werten auf diese Weise gut funktioniert, der folgende Abgleich von Typen führt jedoch zu Problemen:
sealed class FooBar
case class Foo(s: String) extends FooBar
case class Bar(s: String) extends FooBar
val d = Foo("Diana")
val h = Bar("Hadas")
// This matcher WILL NOT work.
def matcher(g: FooBar):String = {
g match {
case Foo(s) | Bar(s) => print(s) // Won't work: s cannot be resolved
case Foo(_) | Bar(_) => _ // Won't work: _ is an unbound placeholder
case _ => "Could not match"
}
}
Wenn Sie im letzteren Fall (mit _
) nicht den Wert der ungebundenen Variablen benötigen und einfach etwas anderes tun möchten, können Sie Folgendes tun:
def matcher(g: FooBar):String = {
g match {
case Foo(_) | Bar(_) => "Is either Foo or Bar." // Works fine
case _ => "Could not match"
}
}
Ansonsten müssen Sie Ihre Fälle aufteilen:
def matcher(g: FooBar):String = {
g match {
case Foo(s) => s
case Bar(s) => s
case _ => "Could not match"
}
}
Musterabgleich auf Tupeln
Gegeben die folgende List
von Tupeln:
val pastries = List(("Chocolate Cupcake", 2.50),
("Vanilla Cupcake", 2.25),
("Plain Muffin", 3.25))
Mit Pattern Matching kann jedes Element unterschiedlich behandelt werden:
pastries foreach { pastry => pastry match { case ("Plain Muffin", price) => println(s"Buying muffin for $price") case p if p._1 contains "Cupcake" => println(s"Buying cupcake for ${p._2}") case _ => println("We don't sell that pastry") } }
Der erste Fall zeigt, wie Sie mit einer bestimmten Zeichenfolge abgleichen und den entsprechenden Preis erhalten. Im zweiten Fall wird die Extraktion von if und tuple verwendet , um mit Elementen des Tupels übereinzustimmen.