amao
10/7/2015 - 3:43 AM

Initializers for Dictionary like NSDictionary +dictionaryWithObjects:forKeys:

Initializers for Dictionary like NSDictionary +dictionaryWithObjects:forKeys:

// (c) 2015 Nate Cook, licensed under the MIT license
//
// Initializers for Dictionary like NSDictionary +dictionaryWithObjects:forKeys:

public extension Dictionary {
    /// Creates a new Dictionary from the given `keys` and `values` collections.
    ///
    /// More efficient than the sequence version because it can reserve the correct
    /// capacity before filling the dictionary. Returns `nil` if the lengths of the
    /// two collections differ.
    init?<ValueCollection: CollectionType, KeyCollection: CollectionType
        where ValueCollection.Generator.Element == Value, KeyCollection.Generator.Element == Key>
        (values: ValueCollection, forKeys keys: KeyCollection)
    {
        // Return nil if the two collections aren't the same length.
        guard values.count == numericCast(keys.count)
            else { return nil }
        
        self = Dictionary(minimumCapacity: numericCast(keys.count))
        for (key, value) in zip(keys, values) {
            self[key] = value
        }
    }
    
    /// Creates a new Dictionary from the given `keys`
    /// and `values` sequences.
    ///
    /// Returns `nil` if the lengths of the two sequences differ.
    init?<ValueSequence: SequenceType, KeySequence: SequenceType
        where ValueSequence.Generator.Element == Value, KeySequence.Generator.Element == Key>
        (values: ValueSequence, forKeys keys: KeySequence)
    {
        self = Dictionary()
        
        // If we use zip with sequences, we won't know if there's a
        // length mismatch, so we need to use their generators manually.
        var keyGenerator = keys.generate()
        var valueGenerator = values.generate()
        
        while true {
            // If we use optional binding here, we won't be able to
            // distinguish the case where both run out together (i.e.,
            // the same length) from where one runs out first.
            let key = keyGenerator.next()
            let value = valueGenerator.next()
            
            switch (key, value) {
            
            // If key and value both bind, we can add the pair to 
            // the dictionary.
            case let (.Some(key), .Some(value)):
                self[key] = value
                
            // If the two sequences run out at the same time, they
            // were the same length: return cleanly.
            case (.None, .None):
                return
                
            // Otherwise, one sequence bound and the other didn't,
            // so return nil.
            default:
                return nil
            }
        }
    }
}

let keys = ["one", "two", "three"]
let values = [1, 2, 3]
let moreValues = values + [4]

// these succeed
let dict1 = Dictionary(values: values, forKeys: keys)
let dict2 = Dictionary(values: AnySequence(values), forKeys: keys)

// these fail, since moreValues.count > keys.count
let dict3 = Dictionary(values: moreValues, forKeys: keys)
let dict4 = Dictionary(values: AnySequence(moreValues), forKeys: keys)