首页 > 解决方案 > Swift 的自定义比较器

问题描述

这是我的代码(简化代码):

struct SomeStruct {
    let id: Int
    let age: Int
}

extension SomeStruct: Hashable {
    var hashValue: Int {
        return id.hashValue * age.hashValue
    }

    static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
        return lhs.id == rhs.id && lhs.age == rhs.age
    }
}

struct Calculator {
    let struct1: [SomeStruct]
    let struct2: [SomeStruct]

    func uniqueById() {
        let struct3 = Set(struct2).union(Set(struct1))

        // I want to union it by property 'id' only.
        // If the property 'id' is equal for both objects,
        // the object in struct2 should be used (since that can have a different age property)
    }
}

SomeStruct是生成的struct ,我不想编辑。我想创建一个基于 1 个属性的Setfor :. 为此,我认为我需要一个自定义的. 有什么快速的方法吗?这是我唯一能想到的,但我想知道是否有更好的方法:SomeStructidComparator

struct SomeStructComparatorById: Hashable {
    let someStruct: SomeStruct

    var hashValue: Int {
        return someStruct.id.hashValue
    }

    static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
        return lhs.someStruct.id == rhs.someStruct.id
    }
}

标签: swift

解决方案


首先,我认为这在 Java 中行不通。addAll()不带比较器(也不带contains等)比较器用于排序,而不是相等。从概念上讲,这打破了 Set 在任何语言中的工作方式。两个项目不是“相等的”,除非它们在所有情况下都可以交换。

这告诉我们我们不想要一个 Set 这里。您在这里想要的是基于某些键的唯一性。那是一本字典(正如丹尼尔所讨论的那样)。

您可以将“id -> age”字典或“id -> struct-of-other-properties”字典作为主要数据类型(而不是使用数组)。或者您可以将您的数组变成一个临时字典,如下所示:

extension Dictionary {
    init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
        where S : Sequence, S.Element == Value {
        let keys = values.map { $0[keyPath: keyPath] }
        self.init(uniqueKeysWithValues: zip(keys, values))
    }
}

并像这样合并它们:

let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values

这留下merged[SomeStruct]

请注意,这Dictionary(uniquelyKeyedBy:)与 具有相同的先决条件Dictionary(uniqueKeysWithValues:)。如果有重复的键,这是一个编程错误,会引发前置条件失败。


推荐阅读