Pattern Matching and Regex

In Scala, you can perform pattern matching and work with regular expressions (regex) for string manipulation and validation.

Pattern Matching in Scala

Pattern matching in Scala is similar to switch-case in other languages but more powerful. It allows matching on types, constants, structures, and more.

How to perform pattern matching?

Ex:

def matchPattern(x: Int) = x match {
case 1 => "One"
case 2 => "Two"
case _ => "None of above"
}

x match: This is the pattern matching construct. It evaluates the value of x and compares it with the following cases.

  • case 1 => "One": If x is 1, the function returns the string "One".
  • case 2 => "Two": If x is 2, the function returns the string "Two".
  • case _ => "None of above": The underscore ( _ ) acts as a wildcard that matches any value not explicitly handled in the previous cases. This is similar to the default case in other languages like Java’s switch. If x is neither 1 nor 2, it returns "None of above".

Example Usages:

  • Input: matchPattern(1)
    Output: "One"
    Explanation: Since x = 1, it matches the case 1 and returns "One".
  • Input: matchPattern(2)
    Output: "Two"
    Explanation: Since x = 2, it matches the case 2 and returns "Two".
  • Input: matchPattern(3)
    Output: "None of above"
    Explanation: Since x = 3 doesn’t match 1 or 2, it falls to the wildcard (_) case and returns "None of above".

Ex : pattern matching can check against different types of data.

def matchTest(x: Any): String = x match {
case 1 => "One"
case "Scala" => "The Scala language"
case _: Int => "Some other integer"
case _ => "Unknown"
}

println(matchTest(1)) // Output: One
println(matchTest("Scala")) // Output: The Scala language
println(matchTest(42)) // Output: Some other integer
println(matchTest(true)) // Output: Unknown

Pattern matching with case classes

case classes :

  • by default, the arguments are immutable, they are of type val. We can change them to var.
  • case classes generate some additional functions automatically when you define a case class. It includes methods like equals, hashCode, toString.
case class Car(name:String, cost:Int)

val mercedes = new Car("Mercedes", 500000)
val bmw = new Car("BMW", 700000)
val jaguar = new Car("Jaguar", 1000000)

for (car <- List(mercedes,bmw,jaguar) ) {
car match {
case Car("Mercedes",500000) => println("Car is Mercedes, Congrats!")
case Car("BMW",700000) => println("Car is BMW, Waow!"
case Car(name,cost) => println("Car is" +name + "Cost is " + cost + "Thats Awesome!!!")
}
}

This Scala code demonstrates the use of case classes and pattern matching with instances of the Car case class. It iterates over a list of cars and matches each car’s properties to specific patterns to print custom messages.

  • Car is defined as a case class with two fields:
    • name: A string representing the car’s name.
    • cost: An integer representing the car’s cost.

Case classes in Scala provide useful features such as pattern matching, immutability, and automatic methods like equals, hashCode, and toString.

Three instances of the Car class are created:

  • mercedes: A Car object with the name “Mercedes” and a cost of 500,000.
  • bmw: A Car object with the name “BMW” and a cost of 700,000.
  • jaguar: A Car object with the name “Jaguar” and a cost of 1,000,000.

The for loop iterates over a List containing the three cars (mercedes, bmw, jaguar).For each iteration, the car variable refers to the current Car object from the list.

  • Pattern Matching (match): For each car in the list, pattern matching is applied to check its name and cost.

Matching Cases:

  1. case Car("Mercedes", 500000):
    • If the car has the name “Mercedes” and the cost is 500,000, it matches this case, and the message "Car is Mercedes, Congrats!" is printed.
  2. case Car("BMW", 700000):
    • If the car has the name “BMW” and the cost is 700,000, it matches this case, and the message "Car is BMW, Waow!" is printed.
  3. case Car(name, cost):
    • If the car doesn’t match the first two cases, it falls into this wildcard case, which matches any Car object.
    • The variables name and cost are extracted from the car, and the message "Car is [name] Cost is [cost] Thats Awesome!!!" is printed, where [name] and [cost] are the actual values from the Car object.

Key Concepts:

  • Case Classes: Simplify working with immutable data by providing built-in features like pattern matching.
  • Pattern Matching: Allows checking the structure of data (here, the Car case class) and executing code based on the matched pattern.
  • Wildcard Case: Catches any unmatched patterns, with the ability to extract variable values from the matched data (e.g., name, cost).

Ex:

case class Success(message: String)
case class Failure(reason: String)

def handleResponse(response: Any): Unit = response match {
case Success(message) => println(s"Success: $message")
case Failure(reason) => println(s"Failure: $reason")
}

handleResponse(Success("All good!"))
handleResponse(Failure("Something went wrong"))

Pattern matching with case objects

case object is a singleton object that has many of the same features as a case class. It is mainly used when you want a single instance of a class with pattern matching capabilities, and when you don’t need to maintain any state across different instances.

case object Stop
case object Start

def handleCommand(command: Any): String = command match {
case Start => "Starting the process"
case Stop => "Stopping the process"
case _ => "Unknown command"
}

println(handleCommand(Start)) // Output: Starting the process
println(handleCommand(Stop)) // Output: Stopping the process

Case objects are useful when you need to represent fixed, singleton values that can be used in pattern matching. For example, you could use case objects to represent different states or commands in a finite state machine or actor model.

case object Idle
case object Running
case object Stopped

def checkState(state: Any): Unit = state match {
case Idle => println("System is idle.")
case Running => println("System is running.")
case Stopped => println("System is stopped.")
case _ => println("Unknown state.")
}

checkState(Idle) // Output: System is idle.
checkState(Running) // Output: System is running.

When you need a set of fixed values, such as representing different days of the week or command statuses, case objects are an elegant solution in Scala (similar to enumerations in other languages).

case object Monday
case object Tuesday
case object Wednesday

def whichDay(day: Any): String = day match {
case Monday => "It's Monday."
case Tuesday => "It's Tuesday."
case Wednesday => "It's Wednesday."
case _ => "Unknown day."
}

println(whichDay(Monday)) // Output: It's Monday.

Regular Expressions

Regular expressions in Scala is adopted from Java. (Java Regular expressions adopted from Perl)

Scala provides support for regular expressions via the scala.util.matching.Regex class. You can use it to match, extract, or replace patterns in strings.

Basic Regex Operations

1. Creating a Regex

  • Import scala.util.matching.Regex
  • You have to create an object of class Regex
val pattern = new Regex("<whatever you want to match>")
(or)
val pattern = "<whatever you want to match>".r

In Scala, you can create a regex using the .r method on a string:

Ex:

import scala.util.matching.Regex  // Always import this first

val pattern = new Regex("Hello") // Using the constructor for class Regex
val stringToFind = "Hello How are you? Hello Again" // String where you want to
search the pattern
pattern.findFirstIn(stringToFind) // Syntax to find the pattern in a given string. findFirstIn is the method which will only find the 1st instance of pattern

o/p : Option[String] = Some(Hello) // if you search for something that is not found, it will give you as None

// Find all matches
val matches = pattern.findAllIn(stringToFind).toList // findAllIn: Finds all matches.
println(matches) // Output: List(Hello, Hello)

Ex:

import scala.util.matching.Regex

val pattern = "Scala".r
val text = "Scala is awesome"
val result = pattern.findFirstIn(text)

println(result) // Output: Some(Scala)

.r — this is a method that is defined in a Regex class and it does nothing but calls the constructor.

.r: This is a method call on the string. In Scala, you can call methods on strings using dot notation. The r method is actually an implicit conversion that turns the string into a Regex object.

Matching for words in a String

val pattern: Regex = "([a-zA-Z]+)".r  // A regex pattern that matches words

"([a-zA-Z]+)" matches one or more alphabetic characters (both lowercase and uppercase).

import scala.util.matching.Regex

val pattern: Regex = "([a-zA-Z]+)".r // A regex pattern that matches words
val input: String = "Hello Scala"

val matches = pattern.findAllIn(input) // Find all matches of the pattern in the input string
matches.foreach(println)

Matching for digits in a String

val pattern = "\\d+".r
(or)
val pattern = "[0-9]+".r // find all between 0 to 9 with 1 or more instance

"[0-9]+": This is a string literal that represents the regular expression pattern:

  • [0-9] is a character class that matches any single digit from 0 to 9.
  • The + after [0-9] means “one or more occurrences of the preceding element”.
  • So [0-9]+ means “one or more digits”.

"\\d+": This is a string literal that represents the regular expression pattern:

  • \\d is an escaped version of \d. In regular expressions, \d represents any digit (0-9).
  • The + after \\d means “one or more occurrences of the preceding element”.
  • So \\d+ means “one or more digits”.

Both "[0-9]+".r and "\\d+".r are commonly used in Scala for matching digits. The choice between them often comes down to personal preference or team conventions.

val pattern = "\\d+".r

println(pattern.matches("123")) // true
println(pattern.matches("abc")) // false
println(pattern.matches("123abc")) // false
println(pattern.findFirstIn("The number is 42")) // Some(42)

val pattern = "[0-9]+".r

println(pattern.matches("123")) // true
println(pattern.matches("abc")) // false
println(pattern.matches("123abc")) // false
println(pattern.findFirstIn("The number is 42")) // Some(42)

var stringToFind = "My name is Harish and age is 10 and i study in standard 7"
val pattern = "[0-9]+".r // find all between 0 to 9 with 1 or more instance
(pattern findAllIn stringToFind).mkString(", ") // 10 7
(pattern findAllIn stringToFind).toArray // Array(10, 7)

--------------------------------------------------------------------
val pattern = "[0-9]+".r
val text = "I have 2 apples and 3 oranges."

// Find first match
println(pattern.findFirstIn(text)) // Output: Some(2)

// Find all matches
val matches = pattern.findAllIn(text).toList
println(matches) // Output: List(2, 3)

Matching for floating point values

val numberPattern = "\\d+(?:\\.\\d+)?".r

"\\d+(?:\\.\\d+)?": This is the string representation of the regular expression:

a. \\d+

  • \\d represents any digit (0-9). It’s escaped with an extra \ because it’s in a Scala string.
  • + means “one or more occurrences of the preceding element”.
  • So \\d+ matches one or more digits.

b. (?:\\.\\d+)?

  • (?:...) is a non-capturing group. It groups elements but doesn’t create a capture group.
  • \\. is an escaped period (dot). In regex, a dot usually means “any character”, so we escape it to mean a literal dot.
  • \\d+ again means one or more digits.
  • The ? at the end means “zero or one occurrence of the preceding group”.

Putting it all together:

  • The pattern matches one or more digits, optionally followed by a decimal point and one or more digits.
  • It will match integers (e.g., “42”) and decimal numbers (e.g., “3.14”).

Ex:

val numberPattern = "\\d+(?:\\.\\d+)?".r

println(numberPattern.matches("42")) // true
println(numberPattern.matches("3.14")) // true
println(numberPattern.matches("0.5")) // true
println(numberPattern.matches(".5")) // false (needs a digit before the decimal)
println(numberPattern.matches("abc")) // false
println(numberPattern.findAllIn("The price is $23.45 and the quantity is 5").toList)
// List(23.45, 5)

This pattern is particularly useful for tasks like:

  • Validating numeric input
  • Extracting numbers (both integers and decimals) from text
  • Parsing financial data or scientific notation

It’s a flexible pattern that covers most common number formats, excluding things like scientific notation (e.g., 1e-10) or numbers with leading decimals (e.g., .5).

Case-insensitive matching of a specific word

val pattern = "(H|h)ello".r

This code creates a regular expression pattern that matches the word “Hello” or “hello”. Let’s examine each part:

  1. val pattern: This declares an immutable value named pattern.
  2. "(H|h)ello": This is the string representation of the regular expression: a. (H|h): This is a capturing group that matches either an uppercase ‘H’ or a lowercase ‘h’.
    • The parentheses () create a capturing group.The vertical bar | means “or” in regex. So this group matches either ‘H’ or ‘h’.
    b. ello: This matches the literal string “ello”.
  3. .r: This converts the string into a Regex object.

Putting it all together:

  • The pattern matches “Hello” or “hello”, allowing for variation in the first letter’s case.
  • It will not match if any other letters are capitalized (e.g., “hEllo” would not match).

Here are some examples of how this pattern would behave:

val pattern = "(H|h)ello".r

println(pattern.matches("Hello")) // true
println(pattern.matches("hello")) // true
println(pattern.matches("HELLO")) // false
println(pattern.matches("Hello!")) // false (because of the exclamation mark)
println(pattern.matches("Hi")) // false

// Finding matches in a larger string
val text = "Hello world! hello there. Hellooo!"
pattern.findAllIn(text).foreach(println)
// Output:
// Hello
// hello

val stringToFind = "Hello How are you? hello Again"
(pattern findAllIn stringToFind).toArray // Array(Hello, hello)

This pattern is useful for:

  • Case-insensitive matching of a specific word (in this case, “hello”)
  • Extracting greetings from text where the capitalization may vary
  • Validating user input where you want to allow for slight variations in capitalization

It’s a simple example of how you can use regex to create flexible matching patterns that account for common variations in text.

Validating Email Addresses

val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r

This is a complex regular expression designed to match most common email address formats. Let’s analyze it piece by piece:

  1. val emailRegex: This declares an immutable value named emailRegex.
  2. The """...""" syntax is a Scala multi-line string literal. It allows you to write the regex pattern without escaping special characters like backslashes.
  3. The regex pattern:
    • ^: Matches the start of the string.
    • [a-zA-Z0-9._%+-]+: This matches the local part of the email (before the @):
      • a-zA-Z0-9: Allows any alphanumeric character.
      • ._%+-: Allows these special characters.
      • +: Means “one or more of the preceding character set”.
    • @: Matches the @ symbol literally.
    • [a-zA-Z0-9.-]+: This matches the domain name:
      • Allows alphanumeric characters, dots, and hyphens.
    • \.: Matches a dot literally (for the top-level domain separator).
    • [a-zA-Z]{2,}: Matches the top-level domain:
      • a-zA-Z: Only alphabetic characters allowed.
      • {2,}: At least two characters.
    • $: Matches the end of the string.
  4. .r: Converts the string into a Regex object.

This pattern aims to validate email addresses by ensuring they:

  • Start with one or more allowed characters (alphanumeric and some special characters).
  • Contain exactly one @ symbol.
  • Have a domain name with allowed characters.
  • End with a top-level domain of at least two alphabetic characters.

Here’s how you might use this regex:

val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".r

def isValidEmail(email: String): Boolean = emailRegex.matches(email)

println(isValidEmail("user@example.com")) // true
println(isValidEmail("user.name+tag@example.co.uk")) // true
println(isValidEmail("invalid.email@")) // false
println(isValidEmail("@invalid.com")) // false
println(isValidEmail("user@invalid")) // false

It’s worth noting that while this regex covers many common email formats, it may not catch all valid emails according to the full RFC specification, which is extremely complex. This regex is a good balance between accuracy and simplicity for most practical applications.


Regular expression with getOrElse function

The getOrElse method is often used with Options, which are commonly returned by regular expression operations.

getOrElse is a function that is used with Option types. It provides a way to return a default value in case the Option is None.

An Option[T] in Scala represents a value that could either be present (Some) or absent (None).

optionValue.getOrElse(defaultValue)
  • If optionValue is Some(value), getOrElse returns value.
  • If optionValue is None, getOrElse returns defaultValue.

Ex:

val pattern = "(\\d+)".r
val text = "The number is 42"

val number = pattern.findFirstIn(text).getOrElse("No number found")
println(number) // Output: 42

val textWithoutNumber = "There is no number here"
val result = pattern.findFirstIn(textWithoutNumber).getOrElse("No number found")
println(result) // Output: No number found

-------------

val pattern = "Scala".r
val input = "I love programming"

// `findFirstIn` returns an Option, so we use getOrElse to provide a default message
val result = pattern.findFirstIn(input).getOrElse("No match found")
println(result) // Output: No match found

Using Regular expression with forEach

In Scala, regular expressions can be used to find patterns in strings, and the forEach method is often used to iterate over the matched results of a regular expression.

We use forEach to process each match.

import scala.util.matching.Regex

// Define a regular expression pattern to match words (one or more alphabetic characters)
val pattern: Regex = "([a-zA-Z]+)".r

// Input string containing words
val input: String = "Scala is a powerful language"

// `findAllIn` returns an Iterator over all matches
val matches = pattern.findAllIn(input)

// Use `forEach` to iterate over each match and print it
matches.foreach(println)

o/p:
Scala
is
a
powerful
language
  • Regex: "([a-zA-Z]+)" is a pattern that matches words (sequences of alphabetic characters).
  • findAllIn: Finds all occurrences of the pattern in the string and returns an Iterator[String] over the matches.
  • forEach: Iterates over each match and applies a function to it (in this case, println).

The forEach method is applied to an iterator or collection and executes a function on each element.

// Define a regular expression pattern to match numbers
val numberPattern: Regex = """\d+""".r

// Input string with some numbers
val input: String = "There are 3 apples, 25 oranges, and 100 bananas"

// Find all numbers and process each one
numberPattern.findAllIn(input).foreach { matchFound =>
println(s"Found number: $matchFound")
}

o/p:
Found number: 3
Found number: 25
Found number: 100
  • The findAllIn method returns an Iterator over matches, which you can iterate through using forEach.
  • forEach applies a function to each match, making it easy to process or manipulate the matched data.

Regular expression meta character syntax

Here below is the list of metacharacter syntax:

SubexpressionMatches
^It is used to match starting point of the line.
$It is used to match terminating point of the line.
.It is used to match any one character excluding the newline.
[…]It is used to match any one character within the brackets.
[^…]It is used to match any one character which is not in the brackets.
\\AIt is used to match starting point of the intact string.
\\zIt is used to match terminating point of the intact string.
\\ZIt is used to match end of the whole string excluding the new line, if it exists.
re*It is utilized to match zero or more appearances of the foregoing expressions.
re+It is used to match one or more of the foregoing expressions.
re?It is used to match zero or one appearance of the foregoing expression.
re{ n}It is used to matches precisely n number of appearances of the foregoing expression.
re{ n, }It is used to match n or more appearances of the foregoing expression.
re{ n, m}It is used to match at least n and at most m appearances of the foregoing expression.
q|rIt is utilized to match either q or r.
(re)It is utilized to group the Regular expressions and recollects the text that are matched.
(?: re)It also groups the regular expressions but does not recollects the matched text.
(?> re)It is utilized to match self-reliant pattern in absence of backtracking.
\\wIt is used to match characters of the word.
\\WIt is used to match characters of the non-word.
\\sIt is utilized to match white spaces which are analogous to [\t\n\r\f].
\\SIt is used to match non-white spaces.
\\dIt is used to match the digits i.e, [0-9].
\\DIt is used to match non-digits.
\\GIt is used to match the point where the endmost match overs.
\\nIt is used for back-reference to occupy group number n.
\\bIt is used to match the word frontiers when it is out of the brackets and matches the backspace when it is in the brackets.
\\BIt is used to match non-word frontiers.
\\n, \\t, etc.It is used to match the newlines, tabs, etc.
\\QIt is used to escape (quote) each of the characters till \\E.
\\EIt is used in ends quoting starting with \\Q.

Examples :

ExampleDescription
.Match any character except newline
[Rr]ubyMatch “Ruby” or “ruby”
rub[ye]Match “ruby” or “rube”
[aeiou]Match any one lowercase vowel
[0-9]Match any digit; same as [0123456789]
[a-z]Match any lowercase ASCII letter
[A-Z]Match any uppercase ASCII letter
[a-zA-Z0-9]Match any of the above
[^aeiou]Match anything other than a lowercase vowel
[^0-9]Match anything other than a digit
\\dMatch a digit: [0-9]
\\DMatch a nondigit: [^0-9]
\\sMatch a whitespace character: [ \t\r\n\f]
\\SMatch nonwhitespace: [^ \t\r\n\f]
\\wMatch a single word character: [A-Za-z0-9_]
\\WMatch a nonword character: [^A-Za-z0-9_]
ruby?Match “rub” or “ruby”: the y is optional
ruby*Match “rub” plus 0 or more ys
ruby+Match “rub” plus 1 or more ys
\\d{3}Match exactly 3 digits
\\d{3,}Match 3 or more digits
\\d{3,5}Match 3, 4, or 5 digits
\\D\\d+No group: + repeats \\d
(\\D\\d)+/Grouped: + repeats \\D\d pair
([Rr]uby(, )?)+Match “Ruby”, “Ruby, ruby, ruby”, etc.


Practical Intermediate stage examples

Consider these patterns :

"abl[ae]\\d+".r

This Scala regular expression pattern abl[ae]\\d+.r can be broken down as follows:

  1. abl: Matches the exact string abl.
  2. [ae]: This is a character class. It matches either the letter a or the letter e immediately after abl. So, it will match abla or able.
  3. \\d: In Scala regex, the double backslash (\\) is used to escape special characters. \\d matches a digit (equivalent to [0-9] in regex). This will match any single digit.
  4. +: The + quantifier means “one or more” of the preceding element. In this case, it refers to one or more digits (\\d+), meaning it will match one or more digits in sequence.
  5. .r: In Scala, appending .r converts the string into a regular expression object of type Regex.

Example matches :

abla123
able45
abla9

"abl[ae]\\d*".r

The regular expression pattern abl[ae]\\d*.r in Scala can be broken down as below:

  1. abl: Matches the exact string abl.
  2. [ae]: Matches either a or e immediately after abl.
  3. \\d: Matches a digit ([0-9]).
  4. *: The * quantifier means “zero or more” of the preceding element. In this case, it refers to zero or more digits (\\d*), meaning it will match any number of digits, including no digits at all.
  5. .r: Converts the string into a regular expression object in Scala.

Example matches :

abla (because * allows for zero digits)
able
abla123
able45
able9

"[Aa]bl[ae]\\d*".r 
(or)
"(A|a)bl[ae]\\d*".r
  1. [Aa]: This is a character class that matches either an uppercase A or a lowercase a. (A|a): This is an alternation group, meaning it matches either A or a. This achieves the same as [Aa].
  2. bl: Matches the exact string bl after the A or a.
  3. [ae]: A character class that matches either a or e immediately after bl.
  4. \\d*: Matches zero or more digits (\\d refers to digits, and * allows zero or more occurrences).
  5. .r: Converts the pattern into a regular expression object in Scala.

Examples :

Abla
able
abla123
Able456
able9

Both achieve the same result in this case, but the character class ([Aa]) is more concise and typically preferred for such simple cases. The alternation group ((A|a)) is more flexible for complex alternations (e.g., (cat|dog) matches either “cat” or “dog”).


Pattern to match numbers(optional negative signs & decimal parts)

"(-)?(\\d+)(\\.\\d*)?".r
(or)
"""(-)?(\d+)(\.\d*)?""".r

The Scala regular expression pattern (-)?(\\d+)(\\.\\d*)?.r is designed to match numbers, including optional negative signs and decimal parts. Here’s the breakdown:

1. (-)?:

  • This part matches an optional minus sign (-).
  • The ? means “zero or one occurrence,” so this part allows for numbers to be either positive or negative.

2. (\\d+):

  • \\d+ matches one or more digits (\\d represents a digit and + means “one or more”).
  • This part captures the integer part of the number.

3. (\\.\\d*)?:

  • \\. matches a literal dot (decimal point).
  • \\d* matches zero or more digits following the decimal point (after the .).
  • The entire part (\\.\\d*)? is optional (because of the ? at the end), meaning the number can either have a decimal part or not.

4. .r:

  • This converts the string into a regular expression object in Scala.

Summary:

This regular expression matches:

  • An optional negative sign
  • An integer part
  • An optional decimal part

Examples :

123 (just an integer)
-123 (a negative integer)
123.45 (a positive decimal number)
-123.45 (a negative decimal number)
0.123 (a decimal number starting with zero)
-0.456 (a negative decimal number)

This pattern is useful for matching various kinds of numbers, including both integers and floating-point numbers.


Matching Groups

In Scala, regular expressions can capture parts of a matched string into groups. Groups allow you to extract specific sections from a match, which is particularly useful when you want to analyze different parts of a pattern separately.

Scala uses the parentheses () in a regular expression to define capturing groups. Once a group is captured, you can access it and use it for further processing.

val pattern = "([0-9]+) ([a-z]+)".r

val text = "123 abc"
val pattern(number, word) = text

println(s"Number: $number, Word: $word") // Output: Number: 123, Word: abc

Another example :

val Decimal = """(-)?(\d+)(\.\d*)?""".r
val Decimal(sign, integerpart, decimalpart) = "-1.23"
val stringToFind = "-1.5 divide by 5 is 3 is wrong"

for (Decimal(sign, integerpart, decimalpart) <- Decimal findAllIn stringToFind)
| println("Sign is " + sign + ", Integer Part is " + integerpart + ", Decimal Part is " +
decimalpart)
Sign is -, Integer Part is 1, Decimal Part is .5
Sign is null, Integer Part is 5, Decimal Part is null
Sign is null, Integer Part is 3, Decimal Part is null

val Decimal(sign, integerpart, decimalpart) = "-1.23"

  • In this line, you’re using pattern matching with the regular expression Decimal to extract components from the string "-1.23".
  • The expression Decimal(sign, integerpart, decimalpart) is a deconstruction pattern that attempts to match the string against the regular expression Decimal.

Step-by-Step Breakdown of the Pattern Matching:

  • "-1.23" is matched against the regular expression (-)?(\d+)(\.\d*)?.
    • (-)?: The first capturing group, (-)?, matches the negative sign -, so sign = Some("-").
    • (\d+): The second capturing group, (\d+), matches the integer part 1, so integerpart = "1".
    • (\.\d*)?: The third capturing group, (\.\d*)?, matches the decimal part .23, so decimalpart = ".23".
  • After matching, the extracted values are assigned to the variables sign, integerpart, and decimalpart.

The regular expression object Decimal is applied to the string "-1.23". The string is deconstructed into its respective components based on the pattern. These components are assigned to the variables sign, integerpart, and decimalpart.