【问题标题】:How to compare Equatable in Swift如何在 Swift 中比较 Equatable
【发布时间】:2016-09-16 14:49:21
【问题描述】:

我有多组这样的两个数组。我是从第 3 方得到的。

var array1 : [Any?]
var array2 : [Any?]

我知道这些数组中的对象类型(在编译时)。例如,第一个元素是String,第二个元素是Int

我目前比较每组这样的数组(请注意数组不是同质的)。

array1[0] as? String == array2[0] as? String
array1[1] as? Int == array2[1] as? Int
...

最大的问题是每个集合都有不同的类型。结果,我假设有 10 组数组,每组有 5 个元素。我必须进行 10*5 显式转换为特定类型和比较。

我希望能够编写一个可以比较两个数组的通用方法(无需指定所有类型)

compareFunc(array1, array2)

我开始走在路上。但是,我也不知道我应该投射什么物体。我试过Equatable。但是,swift 不喜欢直接使用它。所以,这样的事情是行不通的

func compare(array1: [Any?], array2: [Any?]) {
    for index in 0..<array1.count {
       if (array1[index] as? Equatable != array2[index] as? Equatable) {
         // Do something
       }
    }
}

此数组中的每个对象都是 Equatable。但是,我不确定在这种情况下如何使用它。

【问题讨论】:

  • 我认为您或许应该退后一步,首先考虑一下您是如何最终得到一组 Any 的。您说您是从 3rd 方库中获取的,但是该数组实际上代表什么,为什么需要比较它?你不能在纯 Swift 中做你要求的事情(至少在没有类型转换的情况下不能做,我想这首先会破坏你想要的目的),因为 == 运算符需要一个具体的类型才能做任何比较。因此,由于 Swift 中的静态类型,试图抽象出类型根本行不通。

标签: arrays swift equatable


【解决方案1】:

基于(尝试)到已知元素类型的类型转换构造一个简单的逐元素比较函数

由于您的目标是比较(可选)Any 元素的数组,您可以通过使用 switch 块来构造一个执行逐个元素比较的函数,以尝试向下转换数组的元素您的“第 3 方数组”中的不同已知类型。请注意,您无需指定对应于特定类型的元素位置(因为这可能在不同的数组集之间有所不同),只需指定任何给定元素可能属于的不同类型的详尽集合。

下面是此类函数的一个示例:

func compareAnyArrays(arr1: [Any?], _ arr2: [Any?]) -> Bool {
    /* element-by-element downcasting (to known types) followed by comparison */
    return arr1.count == arr2.count && !zip(arr1, arr2).contains {
        
        /* note that a 'true' below indicates the arrays differ (i.e., 'false' w.r.t. array equality) */
        if let v1 = $1 {
            
            /* type check for known types */
            switch $0 {
            case .None: return true
            case let v0 as String:
                if let v1 = v1 as? String { return !(v0 == v1) }; return true
            case let v0 as Int:
                if let v1 = v1 as? Int { return !(v0 == v1) }; return true
            /* ...
               expand with the known possible types of your array elements
               ... */
            case _ : return true
                /*  */
            }
        }
        else if let _ = $0 { return true }
        return false
    }
}

或者,或者,通过使用(稍微修改)compare(...) helper function from @Roman Sausarnes:s answer,使 switch 块不那么臃肿

func compareAnyArrays(arr1: [Any?], _ arr2: [Any?]) -> Bool {
    
    /* modified helper function from @Roman Sausarnes:s answer */
    func compare<T: Equatable>(obj1: T, _ obj2: Any) -> Bool {
        return obj1 == obj2 as? T
    }
    
    /* element-by-element downcasting (to known types) followed by comparison */
    return arr1.count == arr2.count && !zip(arr1, arr2).contains {
        
        /* note also that a 'true' below indicates the arrays differ
         (=> false w.r.t. equality) */
        if let v1 = $1 {
            
            /* type check for known types */
            switch $0 {
            case .None: return true
            case let v0 as String: return !compare(v0, v1)
            case let v0 as Int: return !compare(v0, v1)
                /* ...
                 expand with the known possible types of your array elements
                 ... */
            case _ : return true
                /*  */
            }
        }
        else if let _ = $0 { return true }
        return false
    }
}

示例用法:

/* Example usage #1 */
let ex1_arr1 : [Any?] = ["foo", nil, 3, "bar"]
let ex1_arr2 : [Any?] = ["foo", nil, 3, "bar"]
compareAnyArrays(ex1_arr1, ex1_arr2) // true

/* Example usage #2 */
let ex2_arr1 : [Any?] = ["foo", nil, 2, "bar"]
let ex2_arr2 : [Any?] = ["foo", 3, 2, "bar"]
compareAnyArrays(ex2_arr1, ex2_arr2) // false

【讨论】:

  • 这是最有前途的方法。它仍然需要在开关中指定所有类型,但是类型少于数组集 * 数组中的索引。
  • @VictorRonin 我相信很难绕过这个要求,所以你可能不得不接受switch 声明,其中明确列出了各种类型。可以使用一些运行时内省技巧来规避,但这不是我推荐的生产代码。请注意,如果您想短路数组元素比较(如果是 false 元素比较),您可能可以结合我的和 Roman Sausarne:s 的答案;在我上面的回答中,在总结相等条件之前,数组将完全逐个元素进行比较。
  • 其实运行时内省并不是一个坏主意。有一个库 EVReflection,它在序列化/反序列化 JSON 方面做了很多工作,并且工作非常流畅(并且可靠)
【解决方案2】:

这是一个边缘解决方案,但它应该会减少一些您试图避免的重复代码:

func compareAnyArray(a1: [Any?], _ a2: [Any?]) -> Bool {

    // A helper function for casting and comparing.
    func compare<T: Equatable>(obj1: Any, _ obj2: Any, t: T.Type) -> Bool {
        return obj1 as? T == obj2 as? T
    }

    guard a1.count == a2.count else { return false }

    return a1.indices.reduce(true) {

        guard let _a1 = a1[$1], let _a2 = a2[$1] else { return $0 && a1[$1] == nil && a2[$1] == nil }

        switch $1 {
        // Add a case statement for each index in the array:
        case 0:
            return $0 && compare(_a1, _a2, t: Int.self)
        case 1:
            return $0 && compare(_a1, _a2, t: String.self)
        default: 
            return false
        }
    }
}

这不是最漂亮的解决方案,但它会减少你必须编写的代码量,它应该适用于任何两个[Any?],只要你知道索引0 处的类型是一个Int,索引1 处的类型是String,等等...

【讨论】:

  • 我认为这是 dfri 改进的有趣方向。但是,您的方法不适用于我,因为不同的数组对不同的索引有不同的类型。我的意思是集合 1 将具有 Int、String、Int,而集合 2 将具有 String、String、Int。
【解决方案3】:

Aaron Rasmussen 的紧凑型 Swift 5 解决方案:

func compare(a1: [Any?], a2: [Any?]) -> Bool {
    guard a1.count == a2.count else { return false }

    func compare<T: Equatable>(obj1: Any, _ obj2: Any, t: T.Type) -> Bool {
        return obj1 as? T == obj2 as? T
    }

    return a1.indices.reduce(true) {

        guard let _a1 = a1[$1], let _a2 = a2[$1] else { return $0 && a1[$1] == nil && a2[$1] == nil }

        switch $1 {
        case 0:  return $0 && compare(obj1: _a1, _a2, t: Int.self)
        case 1:  return $0 && compare(obj1: _a1, _a2, t: String.self)
        case 2:  return $0 && compare(obj1: _a1, _a2, t: <#TypeOfObjectAtIndex2#>.self)
        default: return false
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-06
    • 2017-06-27
    相关资源
    最近更新 更多