[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