Swift的自定义比较器

时间:2018-12-22 12:26:22

标签: 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 ,我不想编辑。我想为Set创建一个基于以下1个属性的SomeStructid。为此,我认为我需要一个自定义的Comparatorjust as Java has。有什么迅捷的方法吗?这是我唯一能想到的,但是我想知道是否有更好的方法:

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
    }
}

3 个答案:

答案 0 :(得分:1)

简短的回答是“否”。 Swift集没有任何方法可以接受自定义比较器,并且如果您绝对必须具有Set,则包装器想法是唯一的实现方法。我对一套的要求表示怀疑。

建议不要使用计算器中的Set,而建议使用字典。

您可以使用字典来生成一个数组,其中每个项目都有唯一的ID ...

let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
        .compactMap { $0.value.max(by: { $0.age < $1.age })}

或者您可以将元素保留在[Int:SomeStruct]字典中:

let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in 
    lhs.age > rhs.age ? lhs : rhs 
})

答案 1 :(得分:1)

您可以执行以下操作:

var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }

结果将是SomeStruct的数组,所有元素都具有唯一的id

您可以将其定义为自定义运算符:

infix operator *>

func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
    var setOfIds: Set<Int> = []
    var union = lhs.filter { setOfIds.insert($0.id).inserted }
    union += rhs.filter { setOfIds.insert($0.id).inserted }
    return union
}

您的代码将如下所示:

func uniqueById() {
    let struct3 = struct2 *> struct1
    //use struct3
}

答案 2 :(得分:1)

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

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

您可以只使用“ id-> age”字典或“ id-> struct-of-other-properties”字典作为主要数据类型(而不是使用Array)。或者,您可以将Array变成这样的临时字典:

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:)具有相同的前提条件。如果存在重复的密钥,则是编程错误,并且会引发前提条件失败。