【问题标题】:How can I translate this utility function into an extension function?如何将此实用功能转换为扩展功能?
【发布时间】:2017-08-30 19:27:43
【问题描述】:

我在 Swift 4 中编写了这个实用函数:

func insert<Key, Element>(_ value: Element, into dictionary: inout [Key : Set<Element>], at key: Key) {
    if let _ = dictionary[key] {
        dictionary[key]?.insert(value)
    }
    else {
        var set = Set<Element>()
        set.insert(value)
        dictionary[key] = set
    }
}

这样使用:

insert("foo", into: &myDictionary, at: "bar")

...但我想这样使用它:

myDictionary.insert("foo", at: "bar")

我尝试这样声明:

extension Dictionary where Value == Set<AnyHashable> {
    mutating func insert(_ value: Value.Element, at key: Key) { // Error here
        if let _ = self[key] {
            self[key]?.insert(value)
        } else {
            var set = Set<Value.Element>() // Error here
            set.insert(value)
            self[key] = set
        }
    }
}

...但我收到以下错误:

/path/to/Sequence Extensions.swift:2:41: error: 'Element' is not a member type of 'Dictionary.Value'
    mutating func insert(_ value: Value.Element, at key: Key) {
                                  ~~~~~ ^
Swift.Set:608:22: note: did you mean 'Element'?
    public typealias Element = Element
                     ^
Swift._IndexableBase:3:22: note: did you mean '_Element'?
    public typealias _Element = Self.Element

/path/to/Sequence Extensions.swift:6:23: error: type 'Value.Element' does not conform to protocol 'Hashable'
            var set = Set<Value.Element>()
                      ^

【问题讨论】:

    标签: swift dictionary compiler-errors set swift-extensions


    【解决方案1】:

    不幸的是,Swift 目前不支持parameterised extensions(在扩展声明中引入类型变量的能力),因此您目前不能直接表达“对某些Set&lt;T&gt; 有约束的扩展”的概念。但是,它是泛型宣言的一部分,因此希望它能够进入该语言的未来版本。

    即使您的 Value 扩展被限制为 Set&lt;AnyHashable&gt; 已编译,它也不会非常有用。您需要先将所需的字典转换为临时的 [Key: Set&lt;AnyHashable&gt;],然后对其调用 mutating 方法,然后将其转换回其原始类型(使用 as!)。

    这是因为扩展位于具有异构 Set 值的 Dictionary 上。扩展方法将 arbitrary Hashable 元素插入字典的值之一是完全合法的。但这不是你想要表达的。

    在简单的情况下,我认为首先不需要扩展。你可以说:

    var dict = [String: Set<String>]()
    dict["key", default: []].insert("someValue")
    

    使用Dictionary 采用默认值的下标重载,如SE-0165 中所述。

    如果您仍然想要扩展,我建议您简单地使其更通用。例如,不是将Value 约束为Set;将其约束到协议SetAlgebraSet 符合)。

    它表示可以执行类似集合操作的类型,并且还派生自ExpressibleByArrayLiteral,这意味着您可以使用与上述完全相同的语法来实现您的方法:

    extension Dictionary where Value : SetAlgebra {
    
        mutating func insert(_ value: Value.Element, at key: Key) {
            self[key, default: []].insert(value)
        }
    }
    

    虽然这里要考虑的另一件事是 Swift 集合类型的写时复制行为,例如 Set。在上述方法中,将为给定键查询字典,返回该键的现有集合或新的空集合。然后您的value 将被插入到这个临时集合中,并且它会被重新插入到字典中。

    这里使用临时的意思是如果集合已经在字典中,value 将不会被原地插入,集合的缓冲区将首先被复制以保留值语义;这可能是性能问题(在this Q&Athis Q&A 中有更详细的探讨)。

    话虽如此,我目前正在为Dictionarysubscript(_:default:) in this pull request 解决此问题,以便可以就地改变集合。

    在修复之前,解决方案是先从字典中删除集合,然后再进行变异:

    extension Dictionary where Value : SetAlgebra {
    
        mutating func insert(_ value: Value.Element, at key: Key) {
            var set = removeValue(forKey: key) ?? []
            set.insert(value)
            self[key] = set
        }
    }
    

    在这种情况下,使用扩展是完全合理的。

    值得注意的是,这里使用协议约束是解决没有参数化扩展问题的一般解决方案(或在某些情况下的解决方法)。它允许您实现所需的占位符作为该协议的关联类型。请参阅 this Q&A 了解如何创建自己的协议来实现该目的的示例。

    【讨论】:

      【解决方案2】:

      您可以使用协议来识别集合:

      protocol SetType
      {
         associatedtype Element:Hashable
         init()
         mutating func insert(_ : Element) ->  (inserted: Bool, memberAfterInsert: Element)
      }
      
      extension Set:SetType 
      {}
      
      extension Dictionary where Value : SetType 
      {
         mutating func insert(_ value:Value.Element, at key:Key)
         {
            var valueSet:Value = self[key] ?? Value()
            valueSet.insert(value)
            self[key] = valueSet
         }
      }
      
      var oneToMany:[String:Set<String>] = [:]
      
      oneToMany.insert("Dog", at: "Animal")
      oneToMany.insert("Cat", at: "Animal")
      oneToMany.insert("Tomato", at: "Vegetable")
      

      这将产生一个集合字典:

      ["Animal": Set(["Dog", "Cat"]), "Vegetable": Set(["Tomato"])]
      

      然而,更合适的实现将使用与 Set 的 insert() 函数相同的返回值:

      extension Dictionary where Value : SetType 
      {
         @discardableResult
         mutating func insert(_ value:Value.Element, at key:Key) ->  (inserted: Bool, memberAfterInsert: Value.Element)
         {
            var valueSet:Value = self[key] ?? Value()
            let result = valueSet.insert(value)
            if result.inserted 
            { self[key] = valueSet }
            return result
         }
      }
      

      [编辑] 我刚刚阅读了 Hamish 的所有回复,并意识到他已经给出了相同的答案(基本上)并使用了与 SetType I 做同样事情的 SetAlgebra(我不知道)重新发明”。您应该接受 Hamish 的回答。

      【讨论】:

        猜你喜欢
        • 2015-08-13
        • 2021-03-06
        • 2017-02-12
        • 2018-02-12
        • 1970-01-01
        • 1970-01-01
        • 2022-01-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多