如何将此实用程序功能转换为扩展功能?

时间:2017-08-30 19:27:44

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

我在 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>()
                      ^

2 个答案:

答案 0 :(得分:3)

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

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

这是因为扩展程序位于具有异类Dictionary值的Set上。扩展方法将任意 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中更详细地探讨了这一点。)

但话虽如此,我目前正在考虑为Dictionary的{​​{1}} in this pull request解决这个问题,以便该集可以就地变异。

直到修复,解决方案是在变异之前首先从字典中删除集合:

subscript(_:default:)

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

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

答案 1 :(得分:1)

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

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的回复,并意识到他已经给出了相同的答案(基本上)并使用了SetAlgebra(我不知道)与SetType I做同样的事情“改造”。你应该接受哈米什的回答。