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!"

Live-Demo

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

Live-Demo

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.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow