Swift : Custom operator to update dictionary value

1.4k Views Asked by At

Is there an elegant way to make a custom operator that updates a dictionary value?

More specifically, I want a prefix operator that increments the integer value corresponding to a given key:

prefix operator +> {}

prefix func +> //Signature 
{
    ...
}

var d = ["first" : 10 , "second" : 33]
+>d["second"] // should update d to ["first" : 10 , "second" : 34]

This is feasible using the functional way. For example, to calculate the frequencies of elements in an array:

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]

But I am trying to do the same thing using a custom operator

3

There are 3 best solutions below

1
On BEST ANSWER
var d = ["first" : 10 , "second" : 33]

d["second"]?++

The operator could be implemented like this:

prefix operator +> {}
prefix func +> <I : ForwardIndexType>(inout i: I?) {
  i?._successorInPlace()
}

var dict = ["a":1, "b":2]

+>dict["b"]

dict // ["b": 3, "a": 1]

Although I'm not sure how it would give you a frequencies function - I mean, if it's building a dictionary, it's not going to have any keys to begin with, so there won't be anything to increment. There are a bunch of cool ways to do it, though. Using the postfix ++, you can do this:

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 tweeted another cool way:

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
  }
}

Or, using an undocumented function:

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
  }
}
1
On

This is a little uglier than you probably are looking for, but you can accomplish it using an unsafe mutable pointer in a generic overloaded operator:

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)
3
On

First, look for a way to do it using functions (not custom operators). You want a function that takes a reference to an item (from a dictionary) and updates its value... that calls for an inout parameter type.

func increment(inout n: Int) {
    n++
}

var d = ["first" : 10 , "second" : 33]
increment(&d["first"]!)
print(d) // -> "[first: 11, second: 33]"

You don't have to care about the value being in a dictionary — inout takes any kind of reference and updates it directly. (This even goes for computed properties. You can pass one inout and it'll correctly go through the setter and getter as it reads and writes values.) And because you don't have to care about the dictionary, you don't really need to be generic — if you want a function that works on dictionaries with Ints, just make a function that works on Ints and let inout do the rest.

Now, custom operators are just functions, so make an operator of your function:

prefix operator +> {}
prefix func +>(inout n: Int) {
    n++
}

You can't use exactly the syntax you were asking for to invoke it, though: dictionary lookups always result in Optional types, so you have to unwrap.

+>d["second"]  // error
+>d["second"]! // but this works — operators automatically make params inout as needed
print(d) // -> "[first: 11, second: 34]"