[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