[Pre Draft Discussion] Further Dictionary Enhancements

Hey everyone,

I’m not entirely sure which of these warrant an evolution proposal (I personally think they’d all be useful).
Just give +/- to the ones you like or dislike and how they could be improved, if there’s enough interest I’ll make a followup proposal to Nate Cook’s Dictionary and Set Enhancements of Swift 4.0.

public extension Dictionary {
    /// Returns a dictionary where the keys are the first elements of
    /// the tuple and the values are the second elements, grouped in
    /// arrays associated with the given key.
    ///
    ///     let question = [(1, "Hello"), (2, "How"), (3, "Are"), (1, "You")]
    ///     let g = Dictionary(grouping: question)
    ///     // g == [1: ["Hello", "You"], 2: ["How"], 3: ["Are"]]
    ///
    /// - Returns: A dictionary containing the grouped elements of this sequence.
    public init<S: Sequence, E>(grouping elements: S) where Value == [E], S.Element == (Key, E) {
        self = elements.reduce(into: [:]) { result, element in
            let (key, value) = element
            result[key, default: []] += [value]
        }
    }
    
    /// Returns a dictionary where the keys are the groupings returned by
    /// the given closure and the values are arrays of the elements that
    /// returned each specific key.
    ///
    ///     let question = ["", "Hello", "", "How", "", "Are", "", "You", ""]
    ///     let g = Dictionary(compactGrouping: question, by: { $0.first })
    ///     // g == ["H": ["Hello", "How"], "A": ["Are"], "Y": ["You"]]
    ///
    /// - Parameter transform: `transform` accepts an element of this
    ///   sequence as its parameter and returns a hashable value, or `nil`.
    /// - Returns: A dictionary containing the grouped elements of this sequence, dropping those whose `by` returned `nil`.
    public init<S: Sequence, E>(compactGrouping elements: S, by transform: (E) throws -> Key?) rethrows where Value == [E], S.Element == E {
        self.init(grouping: try elements.compactMap { value -> (Key, E)? in
            guard let transformed = try transform(value) else { return nil }
            return (transformed, value)
        })
    }
    
    /// Returns the first non-`nil` value for a `Sequence` of keys.
    ///
    /// - Parameter keys: A `Sequence` of keys.
    /// - Returns: The first non-`nil` value encountered, or `nil`.
    public subscript<S: Sequence>(first keys: S) -> Value? where S.Element == Key {
        return keys
            .first(where: self.keys.contains)
            .flatMap { self[$0] }
    }
    
    public func mapKeys<T: Hashable>(_ transform: (Key) throws -> T, uniquingKeysWith combine: (Value, Value) throws -> Value) rethrows -> [T: Value] {
        return try .init(map {
            (try transform($0.key), $0.value)
        }, uniquingKeysWith: combine)
    }
    
    public func mapKeys<T: Hashable>(_ transform: (Key) throws -> T) rethrows -> [T: Value] {
        return try .init(uniqueKeysWithValues: map {
            (try transform($0.key), $0.value)
        })
    }
}
3 Likes