我在 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>()
^
答案 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
;将其约束到协议SetAlgebra
(Set
符合)。
它表示可以执行类似集合的操作的类型,也派生自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&A和this 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做同样的事情“改造”。你应该接受哈米什的回答。