Chapter 20: Pattern Matching

最基本的pattern matching就是switch去mapping不同的case, 這章要學習更進階的mapping方法。

大綱

If and guard

  • If 和 guard可以搭配case進行混搭

func process(point: (x: Int, y: Int, z: Int)) -> String {
  // if 可以搭配 pattern match
  if case (0, 0, 0) = point {
    return "At origin"
  }
  return "Not at origin"
}

func process(point: (x: Int, y: Int, z: Int)) -> String {
  // guard 也可以搭配 pattern match
  guard case (0, 0, 0) = point else {
    return "Not at origin"
  }
  // guaranteed point is at the origin
  return "At origin"
}

let point = (x: 0, y: 0, z: 0)
let response = process(point: point)

Switch

  • 利用switch可以搭多個pattern matching

func process(point: (x: Int, y: Int, z: Int)) -> String {
  // 可以match range, 神奇 !
  let closeRange = -2...2
  let midRange = -5...5
  // 利用switch進行多個case的pattern match
  switch point {
  case (0, 0, 0):
    return "At origin"
  case (closeRange, closeRange, closeRange):
    return "Very close to origin"
  case (midRange, midRange, midRange):
    return "Nearby origin"
  case _:
    return "Not near origin"
  }
}
let point = (x: 15, y: 5, z: 3)
let response = process(point: point)
  • switch使用會比if else來得更安全,因為switch會幫忙檢查所有case。

That’s why you place the midRange condition second. Even though the midRange condition would match a closeRange value, it won’t be evaluated unless the previous condition fails

  • Switch case, 優先case放在上面優先處理。

for

  • Pattern match可以用來filter array。

let groupSizes = [1, 5, 4, 6, 2, 1, 3]
// 利用case來進行filter
for case 1 in groupSizes {
  print("Found an individual")
}

Wildcard pattern

// 利用_進行wildcard, 表示允許任意值
if case (_, 0, 0) = coordinate {
  // x can be any value. y and z must be exactly 0.
  print("On the x-axis")
}

Value-binding pattern

// 利用let, var來找出符合pattern的某個值
if case (let x, 0, 0) = coordinate {
  print("On the x-axis at \(x)")
}
// 也可以找出符合pattern的多個值
if case let (x, y, 0) = coordinate {
  print("On the x-y plane at (\(x), \(y))")
}

Identifier pattern

  • the identifier pattern is a sub-pattern of the value-binding pattern

if case (let x, 0, 0) = coordinate {
  // Identifier pattern: 
  // You’re telling the compiler, “When you find a value of (something, 0, 0), assign the something to x
  print("On the x-axis at \(x)")
}

Tuple pattern

  • Tuple pattern, (something, 0, 0) = (identifier, expression, expression).

  • Tuple pattern combines many patterns into one and helps you write terse code

Enumeration case pattern

  • associated value: 在這裡是指legs

  • 只有當要使用時(print), 此時會透過value-binding把值給取出來。

enum Organism {
  case plant
  case animal(legs: Int)
}
let pet = Organism.animal(legs: 4)
switch pet {
case .animal(let legs):
  print("Potentially cuddly with \(legs) legs")
default:
  print("No chance for cuddles")
}

Optional pattern

  • 用來處理當enumeration case patterns 包含nil的狀況。

let names: [String?] =
  ["Michelle", nil, "Brandon", "Christine", nil, "David"]

for case let name? in names {
  print(name) // 4 times
}

”Is” type-casting pattern

let array: [Any] = [15, "George", 2.0]
for element in array {
  switch element {
    // 利用is來判斷case的型別
  case is String:
    print("Found a string")
  default:
    print("Found something else")
  }
}

”As” type-casting pattern

  • 只有當compiler找到一個object可以轉型成string, 然後compiler會將值binding到text中。

for element in array {
  switch element {
    // 利用as來進行type轉型
  case let text as String:
    print("Found a string: \(text)")
  default:
    print("Found something else")
  }
}

”Qualifying with where

  • 可以利用“where”進行更近一步的filter。

enum LevelStatus {
  case complete
  case inProgress(percent: Double)
  case notStarted
}
let levels: [LevelStatus] = [.complete, .inProgress(percent: 0.9), .notStarted]
for level in levels {
  switch level {
  case .inProgress(let percent) where percent > 0.8 :
    print("Almost there!")
  case .inProgress(let percent) where percent > 0.5 :
    print("Halfway there!")
  case .inProgress(let percent) where percent > 0.2 :
    print("Made it through the beginning!")
  default:
    break
  }
}

Chaining with commas

  • 可以將多個if簡化成單個if完成。

enum Number {
  case integerValue(Int)
  case doubleValue(Double)
  case booleanValue(Bool)
}
let a = 5
let b = 6
let c: Number? = .integerValue(7)
let d: Number? = .integerValue(8)

// 一般寫法,多個if
if a != b {
  if let c = c {
    if let d = d {
      if case .integerValue(let cValue) = c {
        if case .integerValue(let dValue) = d {
          if dValue > cValue {
            print("a and b are different")
            print("d is greater than c")
            print("sum: \(a + b + cValue + dValue)")
          }
        }
      }
    }
  }
}

// 利用Chaining with commas, 將多個if簡化成單個if
if a != b,
  let c = c,
  let d = d,
  case .integerValue(let cValue) = c,
  case .integerValue(let dValue) = d,
  dValue > cValue {
  print("a and b are different")
  print("d is greater than c")
  print("sum: \(a + b + cValue + dValue)")
}

Custom tuple

  • 利用custom tuple進行case轉換

var username: String?
var password: String?
// Custom tuple = (username, password)
switch (username, password) {
case let (username?, password?): // 進行binding
  print("Success! User: \(username) Pass: \(password)")
case let (username?, nil):
  print("Password is missing. User: \(username)")
case let (nil, password?):
  print("Username is missing. Pass: \(password)")
case (nil, nil):
  print("Both username and password are missing")
}

Validate that an optional exists

  • 檢查optional狀態

let user: String? = "Bob"
guard let _ = user else {
  print("There is no user.")
  fatalError()
}
print("User exists, but identity not needed.")

Organize an if-else-if

  • 利用switch搭配pattern matching來替代if-else-if,可以讓code更加清楚。

let view = Rectangle(width: 15, height: 60, color: "Green")
switch view {
case _ where view.height < 50:
  print("Shorter than 50 units")
case _ where view.width > 20:
  print("Over 50 tall, & over 20 wide")
case _ where view.color == "Green":
  print("Over 50 tall, at most 20 wide, & green")
default:
  print("This view can't be described by this example")
}

Expression pattern

  • “The expression pattern compares values with the ~= pattern matching operator”

// expression pattern寫法
let matched = (1...10 ~= 5) // true

if case 1...10 = 5 {
  print("In the range")
}

Overloading ~=

func ~=(pattern: [Int], value: Int) -> Bool {
  for i in pattern {
    if i == value {
      return true
    }
  }
  return false
}

let list = [0, 1, 2, 3]
let integer = 2

let isInArray = (list ~= integer) // true

if case list = integer {
  print("The integer is in the array")
} else {
  print("The integer is not in the array")
}

Key points

  • A pattern represents the structure of a value.

  • Pattern matching can help you write more readable code than the alternative logical conditions.

  • Pattern matching is the only way to extract associated values from enumeration values.

Last updated

Was this helpful?