代码之家  ›  专栏  ›  技术社区  ›  J. Doe

用于swift的自定义比较器

  •  0
  • J. Doe  · 技术社区  · 7 年前

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

    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个属性: id . 为此,我想我需要一个习惯 Comparator , just 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 回复  |  直到 7 年前
        1
  •  1
  •   Rob Napier    7 年前

    首先,我认为这不会在Java中发挥作用。 addAll() 没有比较器(也没有 contains 比较器用于排序,而不是相等。从概念上讲,这打破了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:) . 如果有重复的键,这是一个编程错误,会导致前置条件失败。

        2
  •  1
  •   ielyamani    7 年前

    你可以这样做:

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

    结果将是 SomeStruct ,所有元素都具有唯一性 id S.

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

    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
    }
    
        3
  •  1
  •   Daniel T.    7 年前

    简短的答案是否定的。Swift集没有任何方法来接受定制的比较器,如果您绝对必须有一个集,那么您的包装器思想是实现这一点的唯一方法。不过,我对一套的要求表示怀疑。

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

    您可以使用字典生成一个数组,其中每个项都有一个唯一的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 
    })