Swift: пользовательский оператор для обновления значения словаря
Есть ли элегантный способ сделать пользовательский оператор, который обновляет значение словаря?
Более конкретно, я хочу префиксный оператор, который увеличивает целочисленное значение, соответствующее данному ключу:
prefix operator +> {}
prefix func +> //Signature
{
...
}
var d = ["first" : 10 , "second" : 33]
+>d["second"] // should update d to ["first" : 10 , "second" : 34]
Это возможно с использованием функционального способа. Например, чтобы вычислить частоты элементов в массиве:
func update<K,V>(var dictionary: [K:V], key: K, value: V) -> [K:V] {
dictionary[key] = value
return dictionary
}
func increment<T>(dictionary: [T:Int], key: T) -> [T:Int] {
return update(dictionary, key: key, value: dictionary[key].map{$0 + 1} ?? 1)
}
func histogram<T>( s: [T]) -> [T:Int] {
return s.reduce([T:Int](), combine: increment)
}
let foo = histogram([1,4,3,1,4,1,1,2,3]) // [2: 1, 3: 2, 1: 4, 4: 2]
Но я пытаюсь сделать то же самое, используя пользовательский оператор
3 ответа
var d = ["first" : 10 , "second" : 33]
d["second"]?++
Оператор может быть реализован так:
prefix operator +> {}
prefix func +> <I : ForwardIndexType>(inout i: I?) {
i?._successorInPlace()
}
var dict = ["a":1, "b":2]
+>dict["b"]
dict // ["b": 3, "a": 1]
Хотя я не уверен, как он даст вам функцию частот - я имею в виду, что если он строит словарь, у него не будет никаких ключей для начала, поэтому не будет ничего увеличивать. Однако есть множество классных способов сделать это. Использование постфикса ++
, вы можете сделать это:
extension SequenceType where Generator.Element : Hashable {
func frequencies() -> [Generator.Element:Int] {
var result: [Generator.Element:Int] = [:]
for element in self {
result[element]?++ ?? {result.updateValue(1, forKey: element)}()
}
return result
}
}
Airspeed Velocity написал еще один крутой способ:
extension Dictionary {
subscript(key: Key, or or: Value) -> Value {
get { return self[key] ?? or }
set { self[key] = newValue }
}
}
extension SequenceType where Generator.Element : Hashable {
func frequencies() -> [Generator.Element:Int] {
var result: [Generator.Element:Int] = [:]
for element in self { ++result[element, or: 0] }
return result
}
}
Или, используя недокументированную функцию:
extension SequenceType where Generator.Element : Hashable {
func frequencies() -> [Generator.Element:Int] {
var result: [Generator.Element:Int] = [:]
for el in self {result[el]?._successorInPlace() ?? {result[el] = 1}()}
return result
}
}
Во-первых, ищите способ сделать это с помощью функций (не пользовательских операторов). Вам нужна функция, которая берет ссылку на элемент (из словаря) и обновляет его значение... которая вызывает inout
тип параметра.
func increment(inout n: Int) {
n++
}
var d = ["first" : 10 , "second" : 33]
increment(&d["first"]!)
print(d) // -> "[first: 11, second: 33]"
Вам не нужно заботиться о значении в словаре - inout
берет любые ссылки и обновляет их напрямую. (Это даже касается вычисленных свойств. Вы можете передать один inout
и он будет правильно проходить через установщик и получатель при чтении и записи значений.) И поскольку вам не нужно заботиться о словаре, вам не нужно быть универсальным - если вы хотите функцию, которая работает на словари с Int
s, просто сделайте функцию, которая работает на Int
и пусть inout
сделай все остальное
Теперь пользовательские операторы - это просто функции, поэтому сделайте оператор своей функции:
prefix operator +> {}
prefix func +>(inout n: Int) {
n++
}
Вы не можете использовать именно тот синтаксис, который вы запрашивали, чтобы вызвать его, хотя: поиск по словарю всегда приводит к необязательным типам, поэтому вам нужно развернуть.
+>d["second"] // error
+>d["second"]! // but this works — operators automatically make params inout as needed
print(d) // -> "[first: 11, second: 34]"
Это немного страшнее, чем вы, вероятно, ищете, но вы можете сделать это, используя небезопасный изменяемый указатель в универсальном перегруженном операторе:
prefix operator +> {}
prefix func +><T>( value:UnsafeMutablePointer<T?> )
{
print( value.memory )
if let intValue = value.memory as? Int {
value.memory = (intValue + 1) as? T
}
}
var d = ["first" : 10 , "second" : 33]
print( d["second"] ) // Optional(33)
+>(&d["second"])
print( d["second"] ) // Optional(34)