संबंधित मूल्यों के साथ स्विफ्ट एनमों की समानता का परीक्षण कैसे करें


193

मैं दो स्विफ्ट एनम मूल्यों की समानता का परीक्षण करना चाहता हूं। उदाहरण के लिए:

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

हालाँकि, संकलक समानता अभिव्यक्ति संकलित नहीं करेगा:

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

क्या मैंने समानता ऑपरेटर के अपने स्वयं के अधिभार को परिभाषित किया है? मैं उम्मीद कर रहा था कि स्विफ्ट कंपाइलर इसे अपने आप हैंडल कर लेगा, जैसे स्काला और ओक्लेमल करते हैं।



1
स्विफ्ट 4.1 से SE-0185 के कारण , स्विफ्ट सिंथेसाइजिंग Equatableऔर Hashableसंबंधित मूल्यों के साथ एनमों के लिए भी समर्थन करता है ।
jedwidz

जवाबों:


245

स्विफ्ट 4.1+

जैसा कि @jedwidz ने स्विफ्ट 4.1 से मदद की है, ( SE-0185 के कारण , स्विफ्ट सिंथेसाइजिंग Equatableऔर Hashableसंबंधित मूल्यों के साथ एनम के लिए भी समर्थन करता है ।

इसलिए यदि आप स्विफ्ट 4.1 या नए पर हैं, तो निम्न XCTAssert(t1 == t2)कार्य करने के लिए आवश्यक तरीकों को स्वचालित रूप से संश्लेषित करेगा । कुंजी Equatableको आपके एनम में प्रोटोकॉल जोड़ना है ।

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

स्विफ्ट से पहले 4.1

जैसा कि दूसरों ने उल्लेख किया है, स्विफ्ट आवश्यक समानता ऑपरेटरों को स्वचालित रूप से संश्लेषित नहीं करता है। मुझे एक क्लीनर (IMHO) के कार्यान्वयन का प्रस्ताव देना चाहिए, हालांकि:

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    switch (lhs, rhs) {
    case let (.Name(a),   .Name(b)),
         let (.Number(a), .Number(b)):
      return a == b
    default:
      return false
    }
}

यह आदर्श से बहुत दूर है - बहुत दोहराव है - लेकिन कम से कम आपको अंदर-बयानों के साथ नेस्टेड स्विच करने की आवश्यकता नहीं है।


39
इस बारे में बेकार की बात यह है कि आपको स्विच में डिफ़ॉल्ट स्टेटमेंट का उपयोग करने की आवश्यकता है, इसलिए यदि आप एक नया एनम केस जोड़ते हैं, तो कंपाइलर यह सुनिश्चित नहीं करता है कि आप समानता के लिए उस नए मामले की तुलना करने के लिए क्लॉज जोड़ते हैं - आप जब आप बाद में लाइन में बदलाव करते हैं तो आपको याद रखना और सावधान रहना होगा!
माइकल झरना

20
आप समस्या से छुटकारा पा सकते हैं @MichaelWaterfall के defaultसाथ प्रतिस्थापित करके उल्लिखित case (.Name, _): return false; case(.Number, _): return false
कज़मासोरस

25
बेहतर: case (.Name(let a), .Name(let b)) : return a == bआदि
मार्टिन आर

1
जहां खंड के साथ, प्रत्येक मामले को तब तक परीक्षण नहीं किया जाएगा जब तक कि वह हर के लिए डिफ़ॉल्ट हिट न हो जाए false? यह तुच्छ हो सकता है लेकिन इस तरह की चीज कुछ प्रणालियों में जोड़ सकती है।
क्रिस्टोफर स्वैसी

1
इसके लिए काम करने के लिए enumऔर ==कार्य दोनों को वैश्विक दायरे (आपके दृश्य नियंत्रक के दायरे के बाहर) पर लागू किया जाना चाहिए।
Andrej

75

कार्यान्वयन Equatableएक ओवरकिल IMHO है। कल्पना कीजिए कि आपके पास कई मामलों और कई अलग-अलग मापदंडों के साथ जटिल और बड़ी दुश्मनी है। इन मापदंडों को सभी को Equatableलागू करना होगा, भी। इसके अलावा, किसने कहा कि आप सभी या कुछ भी आधार पर एनम मामलों की तुलना करते हैं? कैसे के बारे में अगर आप मूल्य परीक्षण कर रहे हैं और केवल एक विशेष एनम पैरामीटर को ठोकर दिया है? मैं दृढ़ता से सरल दृष्टिकोण का सुझाव दूंगा, जैसे:

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... या पैरामीटर मूल्यांकन के मामले में:

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

अधिक विस्तृत विवरण यहां देखें: https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/


जब आप परीक्षण के आधार पर इसका उपयोग नहीं करने का प्रयास कर रहे हैं, तो क्या आप अधिक संपूर्ण उदाहरण दे सकते हैं?
टेराडिल

मुझे यकीन नहीं है कि यहाँ क्या सवाल है if caseऔर guard caseबस भाषा का निर्माण कर रहे हैं, आप उन्हें इस मामले में समानता का परीक्षण करते समय कहीं भी उपयोग कर सकते हैं, न कि केवल यूनिट टेस्ट में।
mbpro

3
जबकि तकनीकी रूप से यह उत्तर प्रश्न का उत्तर नहीं देता है, मुझे संदेह है कि यह वास्तव में कई लोगों को खोज के माध्यम से यहां पहुंचने का एहसास कराता है, एहसास है कि वे गलत प्रश्न को शुरू करने के लिए कह रहे थे। धन्यवाद!
निकोले सुवाँधिवेव जू

15

ऐसा लगता है कि कोई कंपाइलर न तो एनमों के लिए और न ही स्ट्रक्चर्स के लिए इक्विटी ऑपरेटर तैयार करता है।

"यदि आप एक जटिल डेटा मॉडल का प्रतिनिधित्व करने के लिए अपनी खुद की कक्षा या संरचना बनाते हैं, उदाहरण के लिए, तो उस वर्ग या संरचना के लिए" बराबर "का अर्थ ऐसा कुछ नहीं है जो स्विफ्ट आपके लिए अनुमान लगा सकता है।" [1]

समानता की तुलना को लागू करने के लिए, कुछ इस तरह से लिखना होगा:

@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
    switch(a) {

    case let .Name(sa):
        switch(b) {
        case let .Name(sb): return sa == sb
        default: return false
        }

    case let .Number(na):
        switch(b) {
        case let .Number(nb): return na == nb
        default: return false
        }
    }
}

[१] https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators##//apple_ref/doc/uid/TP40014097-CH27-XID_43 पर "समान संचालक" देखें।


14

यहाँ एक और विकल्प है। यह मुख्य रूप से दूसरों के समान है सिवाय इसके if caseसिंटैक्स का उपयोग करके नेस्टेड स्विच स्टेटमेंट से बचा जाता है । मुझे लगता है कि यह इसे थोड़ा अधिक पठनीय (/ बीरबल) बनाता है और पूरी तरह से डिफ़ॉल्ट मामले से बचने का फायदा है।

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1): 
            if case .Name(let v2) = st where v1 == v2 { return true }
        case .Number(let i1): 
            if case .Number(let i2) = st where i1 == i2 { return true }
        }
        return false
    }
}

func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

14
enum MyEnum {
    case None
    case Simple(text: String)
    case Advanced(x: Int, y: Int)
}

func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
    switch (lhs, rhs) {
    case (.None, .None):
        return true
    case let (.Simple(v0), .Simple(v1)):
        return v0 == v1
    case let (.Advanced(x0, y0), .Advanced(x1, y1)):
        return x0 == x1 && y0 == y1
    default:
        return false
    }
}

इसे कुछ के साथ भी लिखा जा सकता है। case (.Simple(let v0), .Simple(let v1)) इसके अलावा ऑपरेटर staticएनम के अंदर भी हो सकता है । मेरा जवाब यहां देखें।
एलएसआई

11

मैं यूनिट टेस्ट कोड में इस सरल वर्कअराउंड का उपयोग कर रहा हूं:

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

यह तुलना करने के लिए स्ट्रिंग प्रक्षेप का उपयोग करता है। मैं इसे उत्पादन कोड के लिए अनुशंसित नहीं करूंगा, लेकिन यह संक्षिप्त है और यूनिट परीक्षण के लिए काम करता है।


2
मैं मानता हूं, इकाई परीक्षण के लिए यह एक सभ्य समाधान है।
डैनियल वुड

Apple डॉक्स init (stringInterpolationSegment :) पर कहता है: "इस इनिशियलाइज़र को सीधे कॉल न करें। इसे कंपाइलर द्वारा स्ट्रिंग इंटरपोलेशन की व्याख्या करते समय उपयोग किया जाता है।" बस उपयोग करें "\(lhs)" == "\(rhs)"
स्केडल

आप उपयोग String(describing:...)या समकक्ष भी कर सकते हैं "\(...)"। लेकिन यह काम नहीं करता है यदि संबद्ध मान भिन्न हैं :(
मार्टिन

10

एक अन्य विकल्प यह होगा कि मामलों के स्ट्रिंग निरूपण की तुलना की जाए:

XCTAssert(String(t1) == String(t2))

उदाहरण के लिए:

let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"

String(t1) == String(t2) //true
String(t1) == String(t3) //false

3

if caseअल्पविराम के साथ एक और दृष्टिकोण , जो स्विफ्ट 3 में काम करता है:

enum {
  case kindOne(String)
  case kindTwo(NSManagedObjectID)
  case kindThree(Int)

  static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
    if case .kindOne(let l) = lhs,
        case .kindOne(let r) = rhs {
        return l == r
    }
    if case .kindTwo(let l) = lhs,
        case .kindTwo(let r) = rhs {
        return l == r
    }
    if case .kindThree(let l) = lhs,
        case .kindThree(let r) = rhs {
        return l == r
    }
    return false
  }
}

इसी तरह मैंने अपने प्रोजेक्ट में लिखा है। लेकिन मुझे याद नहीं है कि मुझे यह विचार कहां से मिला। (मैंने अभी-अभी गुगली की लेकिन ऐसा उपयोग नहीं देखा।) किसी भी टिप्पणी की सराहना की जाएगी।


2

t1 और t2 संख्या नहीं हैं, वे SimpleTokens से जुड़े उदाहरणों के उदाहरण हैं।

तुम कह सकते हो

var t1 = SimpleToken.Number(123)

फिर आप कह सकते हैं

t1 = SimpleToken.Name(Smith) 

संकलक त्रुटि के बिना।

T1 से मान प्राप्त करने के लिए, स्विच स्टेटमेंट का उपयोग करें:

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

2

स्वीकृत उत्तर की तुलना में 'लाभ' यह है कि 'मुख्य' स्विच स्टेटमेंट में कोई 'डिफॉल्ट' केस नहीं है, इसलिए यदि आप अन्य मामलों के साथ अपना एनम बढ़ाते हैं, तो कंपाइलर आपको बाकी कोड को अपडेट करने के लिए मजबूर करेगा।

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1):
            switch st {
            case .Name(let v2): return v1 == v2
            default: return false
            }
        case .Number(let i1):
            switch st {
            case .Number(let i2): return i1 == i2
            default: return false
            }
        }
    }
}


func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

2

Mbpro के उत्तर पर विस्तार करते हुए, यहां बताया गया है कि कैसे मैंने उस दृष्टिकोण का उपयोग किया जिसमें कुछ बढ़त के मामलों के साथ जुड़े मूल्यों के साथ तेजी से दुश्मनी की समानता की जांच की जा सके।

बेशक आप एक स्विच स्टेटमेंट कर सकते हैं, लेकिन कभी-कभी सिर्फ एक लाइन में एक मान के लिए जाँच करना अच्छा होता है। आप इसे ऐसा कर सकते हैं:

// NOTE: there's only 1 equal (`=`) sign! Not the 2 (`==`) that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause

if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
  // success
}

यदि आप एक ही खंड में 2 शर्तों की तुलना करना चाहते हैं, तो आपको &&ऑपरेटर के बजाय अल्पविराम का उपयोग करने की आवश्यकता है :

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

2

स्विफ्ट 4.1 से सिर्फ Equatableअपने एनम और प्रोटोकॉल का उपयोग करें XCTAssertया XCTAssertEqual:

enum SimpleToken : Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK

-1

आप स्विच का उपयोग करके तुलना कर सकते हैं

enum SimpleToken {
    case Name(String)
    case Number(Int)
}

let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

switch(t1) {

case let .Number(a):
    switch(t2) {
        case let . Number(b):
            if a == b
            {
                println("Equal")
        }
        default:
            println("Not equal")
    }
default:
    println("No Match")
}

दो तर्कों के साथ एक स्विच के लिए सही जगह। ऊपर देखें कि यह केवल प्रति केस कोड की एक पंक्ति को कैसे लेता है। और आपका कोड दो संख्याओं के लिए विफल रहता है जो समान नहीं हैं।
gnasher729
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.