น้อยกว่าหรือมากกว่าในคำสั่งเปลี่ยนของ Swift


145

ฉันคุ้นเคยกับswitchข้อความใน Swift แต่สงสัยว่าจะแทนที่โค้ดนี้ด้วยswitch:

if someVar < 0 {
    // do something
} else if someVar == 0 {
    // do something else
} else if someVar > 0 {
    // etc
}

แม้ว่านี่จะเป็นคำถามที่น่าสนใจ แต่ฉันคิดว่ารหัสที่ใช้สวิตช์สามารถอ่านได้น้อยกว่าข้อความสั่ง if เพียงเพราะคุณทำได้ไม่ได้หมายความว่าคุณควรทำ
Rog

คำตอบ:


241

นี่คือวิธีหนึ่ง สมมติว่าsomeVarเป็นIntหรืออย่างอื่นComparableคุณสามารถเลือกกำหนดตัวถูกดำเนินการให้กับตัวแปรใหม่ได้ สิ่งนี้จะช่วยให้คุณกำหนดขอบเขตอย่างไรก็ตามคุณต้องการใช้whereคำหลัก:

var someVar = 3

switch someVar {
case let x where x < 0:
    print("x is \(x)")
case let x where x == 0:
    print("x is \(x)")
case let x where x > 0:
    print("x is \(x)")
default:
    print("this is impossible")
}

สิ่งนี้สามารถทำให้ง่ายขึ้นเล็กน้อย:

switch someVar {
case _ where someVar < 0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
case _ where someVar > 0:
    print("someVar is \(someVar)")
default:
    print("this is impossible")
}

คุณยังสามารถหลีกเลี่ยงwhereคำหลักทั้งหมดด้วยการจับคู่ช่วง:

switch someVar {
case Int.min..<0:
    print("someVar is \(someVar)")
case 0:
    print("someVar is 0")
default:
    print("someVar is \(someVar)")
}

9
ฉันแนะนำdefault: fatalError()ให้ตรวจสอบข้อผิดพลาดทางตรรกะที่เป็นไปได้ก่อน
Martin R

1
ขอบคุณ! ตัวอย่างเหล่านี้มีประโยชน์มากและพวกเขาแก้ปัญหาของฉันได้! (ตัวอย่างอื่นก็ดีเช่นกัน แต่คุณก็เป็นประโยชน์กับฉันมากที่สุด)
ปีเตอร์

1
@MartinR assertionFailureดูเหมือนจะเป็นตัวเลือกที่ปลอดภัยยิ่งขึ้นโดยเฉพาะเมื่อทำงานในทีม
Michael Voline

120

ด้วย Swift 5 คุณสามารถเลือกหนึ่งในสวิตช์ต่อไปนี้เพื่อแทนที่คำสั่ง if


# 1 ใช้สวิตช์ด้วยPartialRangeFromและPartialRangeUpTo

let value = 1

switch value {
case 1...:
    print("greater than zero")
case 0:
    print("zero")
case ..<0:
    print("less than zero")
default:
    fatalError()
}

# 2 ใช้สวิตช์ด้วยClosedRangeและRange

let value = 1

switch value {
case 1 ... Int.max:
    print("greater than zero")
case Int.min ..< 0:
    print("less than zero")
case 0:
    print("zero")
default:
    fatalError()
}

# 3 ใช้สวิตช์ที่มีส่วนคำสั่ง

let value = 1

switch value {
case let val where val > 0:
    print("\(val) is greater than zero")
case let val where val == 0:
    print("\(val) is zero")
case let val where val < 0:
    print("\(val) is less than zero")
default:
    fatalError()
}

# 4 ใช้สวิตช์ที่มีส่วนคำสั่งและการมอบหมาย _

let value = 1

switch value {
case _ where value > 0:
    print("greater than zero")
case _ where value == 0:
    print("zero")
case _ where value < 0:
    print("less than zero")
default:
    fatalError()
}

# 5 ใช้สวิตช์กับโอเปอเรเตอร์RangeExpressionของโปรโตคอล~=(_:_:)

let value = 1

switch true {
case 1... ~= value:
    print("greater than zero")
case ..<0 ~= value:
    print("less than zero")
default:
    print("zero")
}

# 6 ใช้สวิตช์กับโอเปอเรเตอร์Equatableของโปรโตคอล~=(_:_:)

let value = 1

switch true {
case value > 0:
    print("greater than zero")
case value < 0:
    print("less than zero")
case 0 ~= value:
    print("zero")
default:
    fatalError()
}

# 7 โดยใช้สวิทช์ที่มีPartialRangeFrom, PartialRangeUpToและRangeExpression's contains(_:)วิธี

let value = 1

switch true {
case (1...).contains(value):
    print("greater than zero")
case (..<0).contains(value):
    print("less than zero")
default:
    print("zero")
}

1
เหตุใดจึงจำเป็นต้องใช้กรณีเริ่มต้นใน # 2 ดูเหมือนว่าไม่ดีถ้า rannge มาจาก Int.min ถึง Int.max จะมีอะไรเหลืออยู่?
μολὼν.λαβέ

ว้าวรายการตัวเลือกที่ดี เป็นการดีที่จะทราบว่ามีหลายวิธีในการทำเช่นนี้
Christopher Pickslay

2
ภาพรวมที่ดี แต่มีข้อบกพร่องเนื่องจากตัวเลขระหว่าง 0 ถึง 1 ไม่ได้ถูกนับ 0.1พ่นข้อผิดพลาดร้ายแรงเพราะ1...ครอบคลุมเฉพาะตัวเลขจาก 1 ดังนั้นวิธีนี้จะทำงานเฉพาะในกรณีที่valueเป็นIntแต่ที่เป็นอันตรายเพราะถ้าประเภทตัวแปรเปลี่ยนการทำงานของตัวแบ่งโดยไม่มีข้อผิดพลาดของคอมไพเลอร์
มานูเอล

1
โซลูชันของคุณทำงานไม่ถูกต้องสำหรับ Double type กรณีที่ 1 ... : print ("มากกว่าศูนย์") ไม่มากกว่า 0 มันคือมากกว่าหรือเท่ากับ 1
Vlad

20

switchคำสั่งภายใต้ประทุน, ใช้~=ประกอบการ ดังนั้นนี่คือ:

let x = 2

switch x {
case 1: print(1)
case 2: print(2)
case 3..<5: print(3..<5)
default: break
}

Desugars ถึงสิ่งนี้:

if 1          ~= x { print(1) }
else if 2     ~= x { print(2) }
else if 3..<5 ~= x { print(3..<5) }
else {  }

หากคุณดูที่การอ้างอิงไลบรารีมาตรฐานมันสามารถบอกคุณได้อย่างชัดเจนว่าสิ่งใดที่~=มากไปที่ต้องทำ : รวมคือการจับคู่ช่วงและการเทียบเคียงสิ่งที่เท่าเทียม (ไม่รวมคือการจับคู่กรณีซึ่งเป็นคุณสมบัติภาษามากกว่าฟังก์ชั่นใน std lib)

คุณจะเห็นว่ามันไม่ตรงกับบูลีนทางด้านซ้ายมือ สำหรับการเปรียบเทียบแบบนั้นคุณต้องเพิ่มคำสั่ง where

ถ้า ... คุณโอเวอร์โหลด~=โอเปอร์เรเตอร์ด้วยตัวเอง (โดยทั่วไปจะไม่แนะนำ) ความเป็นไปได้ทางหนึ่งอาจเป็นดังนี้:

func ~= <T> (lhs: T -> Bool, rhs: T) -> Bool {
  return lhs(rhs)
}

เพื่อให้ตรงกับฟังก์ชั่นที่ส่งกลับบูลีนทางด้านซ้ายไปยังพารามิเตอร์ของมันทางด้านขวา นี่คือประเภทของสิ่งที่คุณสามารถใช้สำหรับ:

func isEven(n: Int) -> Bool { return n % 2 == 0 }

switch 2 {
case isEven: print("Even!")
default:     print("Odd!")
}

สำหรับกรณีของคุณคุณอาจมีคำสั่งที่มีลักษณะดังนี้:

switch someVar {
case isNegative: ...
case 0: ...
case isPositive: ...
}

แต่ตอนนี้คุณต้องกำหนดใหม่isNegativeและisPositiveฟังก์ชั่น นอกจากคุณจะโอเวอร์โหลดโอเปอเรเตอร์บางตัวมากเกินไป ...

คุณสามารถโอเวอร์โหลดโอเปอเรเตอร์ของ infix ปกติเพื่อให้เป็นคำนำหน้า curried หรือโอเปอเรเตอร์ postfix นี่คือตัวอย่าง:

postfix operator < {}

postfix func < <T : Comparable>(lhs: T)(_ rhs: T) -> Bool {
  return lhs < rhs
}

สิ่งนี้จะทำงานเช่นนี้:

let isGreaterThanFive = 5<

isGreaterThanFive(6) // true
isGreaterThanFive(5) // false

รวมกับฟังก์ชั่นก่อนหน้านี้และคำสั่งเปลี่ยนของคุณสามารถมีลักษณะเช่นนี้:

switch someVar {
case 0< : print("Bigger than 0")
case 0  : print("0")
default : print("Less than 0")
}

ตอนนี้คุณอาจไม่ควรใช้สิ่งนี้ในทางปฏิบัติ: มันค่อนข้างหลบ คุณน่าจะดีกว่าที่จะติดกับwhereคำแถลง ที่กล่าวว่ารูปแบบงบเปลี่ยนของ

switch x {
case negative:
case 0:
case positive:
}

หรือ

switch x {
case lessThan(someNumber):
case someNumber:
case greaterThan(someNumber):
}

ดูเหมือนกันมากพอที่มันจะคุ้มค่าที่จะพิจารณา


1
คำตอบสำหรับคำถามของคุณอยู่ที่ไหน ฉันหามันไม่เจอ
ฮั

1
กรณีที่ 3 .. <5: พิมพ์ (3 .. <5) - ตามตัวอักษรในวรรคแรก คำตอบนี้ถูกประเมินค่าน้อยเกินไป ช่วยฉันรหัสมาก
Karim

14

คุณสามารถ:

switch true {
case someVar < 0:
    print("less than zero")
case someVar == 0:
    print("eq 0")
default:
    print("otherwise")
}

6

เนื่องจากมีคนเอาไว้แล้วcase let x where x < 0:ที่นี่เป็นทางเลือกสำหรับผู้ที่เป็นsomeVarInt

switch someVar{
case Int.min...0: // do something
case 0: // do something
default: // do something
}

และนี่ก็เป็นอีกทางเลือกหนึ่งสำหรับผู้ที่someVarเป็นDouble:

case -(Double.infinity)...0: // do something
// etc

6

นี่คือลักษณะของช่วง

switch average {
case 0..<40: //greater or equal than 0 and less than 40
    return "T"
case 40..<55: //greater or equal than 40 and less than 55
    return "D"
case 55..<70: //greater or equal than 55 and less than 70
    return "P"
case 70..<80: //greater or equal than 70 and less than 80
    return "A"
case 80..<90: //greater or equal than 80 and less than 90
    return "E"
case 90...100: //greater or equal than 90 and less or equal than 100
    return "O"
default:
    return "Z"
}

3

การ<0แสดงออกไม่ทำงาน (อีกต่อไป?) ดังนั้นฉันจึงลงเอยด้วยสิ่งนี้:

สวิฟท์ 3.0:

switch someVar {
    case 0:
        // it's zero
    case 0 ..< .greatestFiniteMagnitude:
        // it's greater than zero
    default:
        // it's less than zero
    }

1
ในสวิฟท์ 3.0 X_MAXได้ถูกแทนที่ด้วย.greatestFiniteMagnitudeเช่นDouble.greatestFiniteMagnitude, CGFloat.greatestFiniteMagnitudeฯลฯ ดังนั้นโดยปกติคุณก็สามารถทำได้case 0..< .greatestFiniteMagnitudeตั้งแต่ประเภทของการsomeVarเป็นที่รู้จักกันอยู่แล้ว
Guig

@Dorian Roy var timeLeft = 100 switch timeLeft {case 0...<=7200: print("ok") default:print("nothing") }เหตุใดตัว <=ดำเนินการจึงไม่ได้รับการยอมรับ ถ้าฉันเขียนโดยไม่เท่ากันมันใช้งานได้ ขอบคุณ
bibscy

@bibscy คุณต้องการใช้ตัวดำเนินการช่วงปิด: case 0...7200:ตัวดำเนินการ<=เป็นตัวดำเนินการเปรียบเทียบ ในสวิตช์คุณสามารถใช้โอเปอเรเตอร์ช่วง(ดูเอกสาร)
Dorian Roy

มันเยี่ยมมาก ผมได้รับข้อผิดพลาดนี้รูปแบบการแสดงออกประเภท 'ช่วง <ดับเบิล>' ไม่สามารถตรงกับค่าชนิด 'Int'เพราะฉันsomeVarเป็นIntและที่ผมต้องทำDouble(Somevar) `ที่จะทำให้มันทำงาน ...
น้ำผึ้ง

2

ดีใจที่ Swift 4 แก้ไขปัญหา:

เป็นวิธีแก้ปัญหาใน 3 ฉันได้:

switch translation.x  {
case  0..<200:
    print(translation.x, slideLimit)
case  -200..<0:
    print(translation.x, slideLimit)
default:
    break
}

ใช้งานได้ แต่ไม่เหมาะ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.