[swift-evolution] Proposal: Add a sequence-based initializer to Dictionary

Nicola Salmoria nicola.salmoria at gmail.com
Sat Jan 16 04:28:36 CST 2016


+1 for the mutating methods. That's a pretty common use case and would
nicely complement the Dictionary features.

As for the implementation details, I think all the examples we made are
inefficient, requiring two dictionary lookups to combine a value. In e.g.
C++ you can do something like this

auto it = m.find(x);
if (it != end(m)) {
   it->second = value;
}

In Swift we have

func indexForKey(key: Key) -> DictionaryIndex<Key, Value>?

but then there are only

subscript(position: DictionaryIndex<Key, Value>) -> (Key, Value) { get }
mutating func removeAtIndex(index: DictionaryIndex<Key, Value>) -> (Key,
Value)

It would probably be appropriate to have a new method

mutating func updateValue(value: Value, atIndex: DictionaryIndex<Key,
Value>) -> Value

which would allow to efficiently combine the values with a single lookup.

Nicola


On Sat, Jan 16, 2016 at 8:28 AM, Nate Cook <natecook at gmail.com> wrote:

> Adding a `combine` closure with Donnacha's default seems like a pretty
> good solution for this, but giving a `try`-marked closure parameter a
> default  messes with the rethrows behavior and requires a try on every
> call. I think it's important that when used by default, this initializer
> has the same behavior as looping over the sequence and setting values for
> keys. That is, it should replicate:
>
> for (key, value) in sequence {
>     newDictionary[key] = value
> }
>
> and use the last value for any duplicate keys, rather than failing or
> trapping.
>
> To handle this properly we'd need two new initializers:
>
> init<S: SequenceType where S.Generator.Element == Generator.Element>(_
> sequence: S)
>
> init<S: SequenceType where S.Generator.Element == Generator.Element>
>     (_ sequence: S, @noescape combine: (Value, Value) throws -> Value)
> rethrows
>
> Perhaps we also need a mutating `mergeContentsOf` function with the same
> signatures as the initializers:
>
> mutating func mergeContentsOf<S: SequenceType where S.Generator.Element
> == Generator.Element>(_ sequence: S)
>
> mutating func mergeContentsOf<S: SequenceType where S.Generator.Element
> == Generator.Element>
>     (_ sequence: S, @noescape combine: (Value, Value) throws -> Value)
> rethrows
>
> I'm pretty sure I would use all four of those, and that would bring
> Dictionary more into alignment with how you can use Array and Set. Would a
> slightly expanded proposal make sense?
>
> Nate
>
>
> On Jan 15, 2016, at 9:01 AM, Nicola Salmoria via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> I'm ambivalent about the preconditionFailure. Since there would otherwise
> be silent loss of data, I think it fits Swift's "safe by default" paradigm.
> It's also consistent with what the normal initialization from a
> DictionaryLiteral does.
> However, I can also see how it might be more convenient to just pick the
> last value.
>
> Nicola
>
> On Fri, Jan 15, 2016 at 11:53 AM, Alan Skipp <al_skipp at icloud.com> wrote:
>
>> I’ve been absorbed in the world of Monoids lately, so I find the
>> suggestion below to be particularly brilliant. : )
>> It solves the issue of arbitrarily choosing the value for duplicate keys
>> rather nicely. Only thing I’m not too sure about is the idea of failing by
>> default on duplicate keys?
>>
>> On 15 Jan 2016, at 10:18, Nicola Salmoria via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> To handle the case of duplicate keys, why not allow to pass in a
>> 'combine' function?
>> The function could default to a preconditionFailure to be consistent with
>> the DictionaryLiteral behavior, but be overridden by the caller as needed.
>>
>> extension Dictionary {
>>     /// Creates a dictionary with the keys and values in the given
>> sequence.
>>     init<S: SequenceType where S.Generator.Element ==
>> Generator.Element>(_ sequence: S, combine: (existing: Value, other: Value)
>> -> Value = { preconditionFailure("Sequence contains duplicate keys");
>> return $1 } ) {
>>         self.init()
>>         for (key, value) in sequence {
>>             if let existing = updateValue(value, forKey: key) {
>>                 updateValue(combine(existing: existing, other: value),
>> forKey: key)
>>             }
>>         }
>>     }
>> }
>>
>>
>> usage examples:
>>
>> let samples = [("Rome", 40.2), ("New York", 35.1), ("Rome", 42.5), ("New
>> York", 32.8)]
>> let minTemperatures = Dictionary(samples, combine: min)
>> // ["Rome": 40.2, "New York": 32.8]
>> let maxTemperatures = Dictionary(samples, combine: max)
>> // ["Rome": 42.5, "New York": 35.1]
>>
>> let probabilities = [("a", 0.25), ("b", 0.25), ("c", 0.25), ("a", 0.25)]
>> let stateProbabilities = Dictionary(probabilities, combine: +)
>> // ["b": 0.25, "a": 0.5, "c": 0.25]
>>
>>
>> Nicola
>>
>>
>> It’d be great if there was also an init that restricted the Values to
>> Monoids, which would mean the combine function would be taken from the
>> supplied Monoid values (I understand I’ve departed to fantasy island at
>> this point, but one can dream : )
>>
>> Al
>>
>>
>>
>  _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160116/40f387b5/attachment.html>


More information about the swift-evolution mailing list