[swift-evolution] Proposal: Add a sequence-based initializer to Dictionary
Andrew Bennett
cacoyi at gmail.com
Mon Jan 18 02:32:26 CST 2016
+1 I've been wishing the protocol had something like this.
It would be nice if a protocol extension had default implementations
similar to these:
init<S: SequenceType where S.Generator.Element == Generator.Element>(_
sequence: S)
{
self.init(minimumCapacity: sequence.underestimateCount())
self.mergeContentsOf(sequence)
}
init<S: SequenceType where S.Generator.Element == Generator.Element>
(_ sequence: S, @noescape combine: (Value, Value) throws -> Value = {
(_,x) in x }) rethrows
{
self.init(minimumCapacity: sequence.underestimateCount())
try self.mergeContentsOf(sequence, combine: combine)
}
mutating func mergeContentsOf<S: SequenceType where S.Generator.Element ==
Generator.Element>
(_ sequence: S)
{
for (key, value) in sequence {
self[key] = value
}
}
mutating func mergeContentsOf<S: SequenceType where S.Generator.Element ==
Generator.Element>
(_ sequence: S, @noescape combine: (Value, Value) throws -> Value = {
(_,x) in x }) rethrows
{
for (key, value) in sequence {
if let oldValue = self[key] {
self[key] = try combine(oldValue, value)
}
else {
self[key] = value
}
}
}
On Mon, Jan 18, 2016 at 5:04 PM, Thorsten Seitz via swift-evolution <
swift-evolution at swift.org> wrote:
> Alternatively the update method could take a combine function.
> But I like the idea with using the dictionary index as opposed to the key.
>
> -Thorsten
>
> Am 16.01.2016 um 11:28 schrieb Nicola Salmoria via swift-evolution <
> swift-evolution at swift.org>:
>
> +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
>>
>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
> _______________________________________________
> 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/20160118/e45edd45/attachment.html>
More information about the swift-evolution
mailing list