From e2328885c17475decc5dd19f5274f648feffd8a0 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Mon, 26 Sep 2016 11:20:37 -0500 Subject: [PATCH] [stdlib] Dictionary/Set enhancements A variety of enhancements from SE-154 and SE-165: - Custom Keys and Values collections for Dictionary - Two sequence-based Dictionary initializers - Merging methods for Dictionary - Capacity checking and reserving for Dictionary and Set - Type-specific filter(_:) methods for Dictionary and Set - A mapValues(_:) method for Dictionary - A grouping Dictionary initializer --- .../public/core/HashedCollections.swift.gyb | 633 +++++++++++++++++- .../source-stability.swift.expected | 10 + validation-test/stdlib/Dictionary.swift | 402 +++++++++++ .../stdlib/HashedCollectionFilter3.swift | 21 + .../stdlib/HashedCollectionFilter4.swift | 41 ++ validation-test/stdlib/Set.swift | 22 + 6 files changed, 1120 insertions(+), 9 deletions(-) create mode 100644 validation-test/stdlib/HashedCollectionFilter3.swift create mode 100644 validation-test/stdlib/HashedCollectionFilter4.swift diff --git a/stdlib/public/core/HashedCollections.swift.gyb b/stdlib/public/core/HashedCollections.swift.gyb index 313426e91ea6a..da60bf64ba683 100644 --- a/stdlib/public/core/HashedCollections.swift.gyb +++ b/stdlib/public/core/HashedCollections.swift.gyb @@ -778,6 +778,38 @@ public struct Set : } } + /// Returns a new set containing the elements of the set that satisfy the + /// given predicate. + /// + /// In this example, `filter(_:)` is used to include only names shorter than + /// five characters. + /// + /// let cast: Set = ["Vivien", "Marlon", "Kim", "Karl"] + /// let shortNames = cast.filter { $0.characters.count < 5 } + /// + /// shortNames.isSubset(of: cast) + /// // true + /// shortNames.contains("Vivien") + /// // false + /// + /// - Parameter isIncluded: A closure that takes an element as its argument + /// and returns a Boolean value indicating whether the element should be + /// included in the returned set. + /// - Returns: A set of the elements that `isIncluded` allows. + @_inlineable + @available(swift, introduced: 4.0) + public func filter( + _ isIncluded: (Element) throws -> Bool + ) rethrows -> Set { + var result = Set() + for element in self { + if try isIncluded(element) { + result.insert(element) + } + } + return result + } + /// Returns a Boolean value that indicates whether the set is a subset of the /// given sequence. /// @@ -1681,11 +1713,120 @@ public struct Dictionary : .native(_NativeBuffer(minimumCapacity: minimumCapacity)) } + /// Creates a new dictionary from the key-value pairs in the given sequence. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples with unique keys. If your sequence might have + /// duplicate keys, use the `Dictionary(_:uniquingKeysWith:)` initializer + /// instead. + /// + /// The following example creates a new dictionary using an array of strings + /// as the keys and the integers in a countable range as the values: + /// + /// let digitWords = ["one", "two", "three", "four", "five"] + /// let wordToValue = Dictionary(uniqueKeysWithValues: zip(digitWords, 1...5)) + /// print(wordToValue["three"]!) + /// // Prints "3" + /// print(wordToValue) + /// // Prints "["three": 3, "four": 4, "five": 5, "one": 1, "two": 2]" + /// + /// - Parameter keysAndValues: A sequence of `(Key, Value)` tuples to use for + /// the new dictionary. Every key in `keysAndValues` must be unique. + /// - Returns: A new dictionary initialized with the elements of + /// `keysAndValues`. + public init( + uniqueKeysWithValues keysAndValues: S + ) where S.Iterator.Element == (Key, Value) { + if let d = keysAndValues as? Dictionary { + self = d + } else { + self = Dictionary(minimumCapacity: keysAndValues.underestimatedCount) + // '_MergeError.keyCollision' is caught and handled with an appropriate + // error message one level down, inside _variantBuffer.merge(_:...). + try! _variantBuffer.merge(keysAndValues, uniquingKeysWith: { _ in + throw _MergeError.keyCollision + }) + } + } + + /// Creates a new dictionary from the key-value pairs in the given sequence, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// You use this initializer to create a dictionary when you have a sequence + /// of key-value tuples that might have duplicate keys. As the dictionary is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// selects which value to use in the returned dictionary, or to combine the + /// values as the dictionary is initialized. + /// + /// The following example shows how to choose the first and last values for + /// any duplicate keys: + /// + /// let pairsWithDuplicateKeys = [("a", 1), ("b", 2), ("a", 3), ("b", 4)] + /// + /// let firstValues = Dictionary(pairsWithDuplicateKeys, + /// uniquingKeysWith: { (first, _) in first }) + /// // ["b": 2, "a": 1] + /// + /// let lastValues = Dictionary(pairsWithDuplicateKeys, + /// uniquingKeysWith: { (_, last) in last }) + /// // ["b": 4, "a": 3] + /// + /// - Parameters: + /// - keysAndValues: A sequence of `(Key, Value)` tuples to use for the new + /// dictionary. + /// - combine: A closure that is called with the values for any duplicate + /// keys that are encountered. The closure returns the desired value for + /// the final dictionary. + public init( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Iterator.Element == (Key, Value) { + self = Dictionary(minimumCapacity: keysAndValues.underestimatedCount) + try _variantBuffer.merge(keysAndValues, uniquingKeysWith: combine) + } + + /// Creates a new 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. + /// + /// The arrays in the "values" position of the new dictionary each contain at + /// least one element, with the elements in the same order as the source + /// sequence. + /// + /// The following example declares an array of names, and then creates a + /// dictionary from that array by grouping the names by their first letter: + /// + /// let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"] + /// let studentsByLetter = Dictionary(grouping: students, by: { $0.first! }) + /// // ["E": ["Efua"], "K": ["Kofi", "Kweku"], "A": ["Abena", "Akosua"]] + /// + /// The new `studentsByLetter` dictionary has three entries, with students' + /// names grouped by the keys `"E"`, `"K"`, and `"A"`. + /// + /// - Parameters: + /// - values: A sequence of values to group into a dictionary. + /// - keyForValue: A closure that returns a key for each element in + /// `values`. + public init( + grouping values: S, + by keyForValue: (S.Iterator.Element) throws -> Key + ) rethrows where Value == [S.Iterator.Element] { + self = [:] + for value in values { + self[try keyForValue(value), default: []].append(value) + } + } + internal init(_nativeBuffer: _NativeDictionaryBuffer) { _variantBuffer = .native(_nativeBuffer) } + internal init(_variantBuffer: _VariantBuffer) { + self._variantBuffer = _variantBuffer + } + #if _runtime(_ObjC) /// Private initializer used for bridging. /// @@ -1846,6 +1987,57 @@ public struct Dictionary : } } + /// Accesses the element with the given key, or the specified default value, + /// if the dictionary doesn't contain the given key. + public subscript( + key: Key, default defaultValue: @autoclosure () -> Value + ) -> Value { + @inline(__always) + get { + return _variantBuffer.maybeGet(key) ?? defaultValue() + } + set(newValue) { + // FIXME(performance): this loads and discards the old value. + _variantBuffer.updateValue(newValue, forKey: key) + } + } + + /// Returns a new dictionary containing the key-value pairs of the dictionary + /// that satisfy the given predicate. + /// + /// - Parameter isIncluded: A closure that takes a key-value pair as its + /// argument and returns a Boolean value indicating whether the pair + /// should be included in the returned dictionary. + /// - Returns: A dictionary of the key-value pairs that `isIncluded` allows. + @_inlineable + @available(swift, introduced: 4.0) + public func filter( + _ isIncluded: (Key, Value) throws -> Bool + ) rethrows -> [Key: Value] { + var result = Dictionary() + for (key, value) in self { + if try isIncluded(key, value) { + result[key] = value + } + } + return result + } + + /// Returns a new dictionary containing the keys of this dictionary with the + /// values transformed by the given closure. + /// + /// - Parameter transform: A closure that transforms a value. `transform` + /// accepts each value of the dictionary as its parameter and returns a + /// transformed value of the same or of a different type. + /// - Returns: A dictionary containing the keys and transformed values of + /// this dictionary. + public func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> Dictionary { + return try Dictionary( + _variantBuffer: _variantBuffer.mapValues(transform)) + } + /// Updates the value stored in the dictionary for the given key, or adds a /// new key-value pair if the key does not exist. /// @@ -1886,6 +2078,80 @@ public struct Dictionary : return _variantBuffer.updateValue(value, forKey: key) } + /// Merges the key-value pairs in the given sequence into the dictionary, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select which value to use in the updated + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// var dictionary = ["a": 1, "b": 2] + /// + /// // Keeping existing value for key "a": + /// dictionary.merge(["a": 3, "c": 4]) + /// { (current, _) in current } + /// // ["b": 2, "a": 1, "c": 4] + /// + /// // Taking the new value for key "a": + /// dictionary.merge(["a": 5, "d": 6]) + /// { (_, new) in new } + /// // ["b": 2, "a": 5, "c": 4, "d": 6] + /// + /// - Parameters: + /// - other: A sequence of `(Key, Value)` tuples. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + public mutating func merge( + _ other: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Iterator.Element == (Key, Value) { + try _variantBuffer.merge(other, uniquingKeysWith: combine) + } + + /// Returns a new dictionary created by merging the key-value pairs in the + /// given sequence into the dictionary, using a combining closure to + /// determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select which value to use in the returned + /// dictionary, or to combine existing and new values. As the key-value + /// pairs are merged with the dictionary, the `combine` closure is called + /// with the current and new values for any duplicate keys that are + /// encountered. + /// + /// This example shows how to choose the current or new values for any + /// duplicate keys: + /// + /// let dictionary = ["a": 1, "b": 2] + /// let otherDictionary = ["a": 3, "b": 4] + /// let keepingCurrent = dictionary.merging(otherDictionary) + /// { (current, _) in current } + /// // ["b": 2, "a": 1] + /// let replacingCurrent = dictionary.merging(otherDictionary) + /// { (_, new) in new } + /// // ["b": 4, "a": 3] + /// + /// - Parameters: + /// - other: A sequence of `(Key, Value)` tuples. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// dictionary. + /// - Returns: A new dictionary with the combined keys and values of this + /// dictionary and `other`. + public func merging( + _ other: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows -> [Key: Value] where S.Iterator.Element == (Key, Value) { + var result = self + try result.merge(other, uniquingKeysWith: combine) + return result + } + /// Removes and returns the key-value pair at the specified index. /// /// Calling this method invalidates any existing indices for use with this @@ -2027,37 +2293,46 @@ public struct Dictionary : /// A collection containing just the keys of the dictionary. /// - /// When iterated over, keys appear in this collection in the same order as they - /// occur in the dictionary's key-value pairs. Each key in the keys + /// When iterated over, keys appear in this collection in the same order as + /// they occur in the dictionary's key-value pairs. Each key in the keys /// collection has a unique value. /// /// let countryCodes = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"] + /// print(countryCodes) + /// // Prints "["BR": "Brazil", "JP": "Japan", "GH": "Ghana"]" + /// /// for k in countryCodes.keys { /// print(k) /// } /// // Prints "BR" /// // Prints "JP" /// // Prints "GH" - public var keys: LazyMapCollection { - return self.lazy.map { $0.key } + public var keys: Keys { + return Keys(self) } /// A collection containing just the values of the dictionary. /// - /// When iterated over, values appear in this collection in the same order as they - /// occur in the dictionary's key-value pairs. + /// When iterated over, values appear in this collection in the same order as + /// they occur in the dictionary's key-value pairs. /// /// let countryCodes = ["BR": "Brazil", "GH": "Ghana", "JP": "Japan"] /// print(countryCodes) /// // Prints "["BR": "Brazil", "JP": "Japan", "GH": "Ghana"]" + /// /// for v in countryCodes.values { /// print(v) /// } /// // Prints "Brazil" /// // Prints "Japan" /// // Prints "Ghana" - public var values: LazyMapCollection { - return self.lazy.map { $0.value } + public var values: Values { + get { + return Values(self) + } + set { + self = Dictionary(_variantBuffer: newValue._variantBuffer) + } } // @@ -2076,6 +2351,131 @@ public struct Dictionary : return count == 0 } + /// A view of a dictionary's keys. + public struct Keys : Collection, Equatable { + public typealias Element = Key + + internal var _variantBuffer: Dictionary._VariantBuffer + + internal init(_ _dictionary: Dictionary) { + self._variantBuffer = _dictionary._variantBuffer + } + + // Collection Conformance + // ---------------------- + + public var startIndex: Index { + return _variantBuffer.startIndex + } + + public var endIndex: Index { + return _variantBuffer.endIndex + } + + public func index(after i: Index) -> Index { + return _variantBuffer.index(after: i) + } + + public subscript(position: Index) -> Element { + return _variantBuffer.assertingGet(position).key + } + + // Customization + // ------------- + + /// The number of keys in the dictionary. + /// + /// - Complexity: O(1). + public var count: Int { + return _variantBuffer.count + } + + public var isEmpty: Bool { + return count == 0 + } + + public func _customContainsEquatableElement(_ element: Element) -> Bool? { + return _variantBuffer.index(forKey: element) != nil + } + + public func _customIndexOfEquatableElement(_ element: Element) -> Index?? { + return Optional(_variantBuffer.index(forKey: element)) + } + + public static func ==(lhs: Keys, rhs: Keys) -> Bool { + // Equal if the two dictionaries share storage. + if case (.native(let lhsNative), .native(let rhsNative)) = + (lhs._variantBuffer, rhs._variantBuffer), + lhsNative._storage === rhsNative._storage { + return true + } + + // Not equal if the dictionaries are different sizes. + if lhs.count != rhs.count { + return false + } + + // Perform unordered comparison of keys. + for key in lhs { + if !rhs.contains(key) { + return false + } + } + + return true + } + } + + /// A view of a dictionary's values. + public struct Values : MutableCollection { + public typealias Element = Value + + internal var _variantBuffer: Dictionary._VariantBuffer + + internal init(_ _dictionary: Dictionary) { + self._variantBuffer = _dictionary._variantBuffer + } + + // Collection Conformance + // ---------------------- + + public var startIndex: Index { + return _variantBuffer.startIndex + } + + public var endIndex: Index { + return _variantBuffer.endIndex + } + + public func index(after i: Index) -> Index { + return _variantBuffer.index(after: i) + } + + public subscript(position: Index) -> Element { + get { + return _variantBuffer.assertingGet(position).value + } + mutableAddressWithNativeOwner { + let address = _variantBuffer.pointerToValue(at: position) + return (address, Builtin.castToNativeObject( + _variantBuffer.asNative._storage)) + } + } + + // Customization + // ------------- + + /// The number of values in the dictionary. + /// + /// - Complexity: O(1). + public var count: Int { + return _variantBuffer.count + } + + public var isEmpty: Bool { + return count == 0 + } + } } extension Dictionary where Value : Equatable { @@ -2193,6 +2593,10 @@ extension Dictionary : CustomStringConvertible, CustomDebugStringConvertible { } } +internal enum _MergeError : Error { + case keyCollision +} + #if _runtime(_ObjC) /// Equivalent to `NSDictionary.allKeys`, but does not leave objects on the /// autorelease pool. @@ -2450,6 +2854,8 @@ public func _dictionaryBridgeFromObjectiveCConditional< # # a_self: Type name when using a generic noun # +# element: English description of an element +# # TypeParametersDecl: Generic parameters appearing in top-level declarations # # TypeParameters: Generic parameters appearing in typealiases, etc. @@ -2462,6 +2868,7 @@ public func _dictionaryBridgeFromObjectiveCConditional< collections = [ ('Set', 'set', + 'element', 'Element : Hashable', 'Element', 'AnyObject', @@ -2470,6 +2877,7 @@ collections = [ ('Dictionary', 'dictionary', + 'key-value pair', 'Key : Hashable, Value', 'Key, Value', 'AnyObject, AnyObject', @@ -2478,7 +2886,7 @@ collections = [ ] }% -% for (Self, a_self, TypeParametersDecl, TypeParameters, AnyTypeParameters, Sequence, AnySequenceType) in collections: +% for (Self, a_self, element, TypeParametersDecl, TypeParameters, AnyTypeParameters, Sequence, AnySequenceType) in collections: /// An instance of this class has all `${Self}` data tail-allocated. /// Enough bytes are allocated to hold the bitmap for marking valid entries, @@ -4074,6 +4482,36 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { } #endif + /// Reserves enough space for the specified number of elements to be stored + /// without reallocating additional storage. + internal mutating func reserveCapacity(_ capacity: Int) { + let minCapacity = NativeBuffer.minimumCapacity( + minimumCount: capacity, + maxLoadFactorInverse: _hashContainerDefaultMaxLoadFactorInverse) + _ = ensureUniqueNativeBuffer(minCapacity) + } + + /// The number of elements that can be stored without expanding the current + /// storage. + /// + /// For bridged storage, this is equal to the current count of the + /// collection, since any addition will trigger a copy of the elements into + /// newly allocated storage. For native storage, this is the element count + /// at which adding any more elements will exceed the load factor. + internal var effectiveCapacity: Int { + switch self { + case .native: + return Int(Double(asNative.capacity) / + _hashContainerDefaultMaxLoadFactorInverse) + case .cocoa(let cocoaBuffer): +#if _runtime(_ObjC) + return cocoaBuffer.count +#else + _sanityCheckFailure("internal error: unexpected cocoa ${Self}") +#endif + } + } + // // _HashBuffer conformance // @@ -4305,6 +4743,42 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { } } +%if Self == 'Dictionary': + internal mutating func nativePointerToValue(at i: Index) + -> UnsafeMutablePointer { + _ = ensureUniqueNativeBuffer(asNative.capacity) + return asNative.values + i._nativeIndex.offset + } + + internal mutating func pointerToValue(at i: Index) + -> UnsafeMutablePointer { + if _fastPath(guaranteedNative) { + return nativePointerToValue(at: i) + } + + switch self { + case .native: + return nativePointerToValue(at: i) + case .cocoa(let cocoaStorage): +#if _runtime(_ObjC) + // We have to migrate the data to native storage before we can return a + // mutable pointer. But after we migrate, the Cocoa index becomes + // useless, so get the key first. + let cocoaIndex = i._cocoaIndex + let anyObjectKey: AnyObject = + cocoaIndex.allKeys[cocoaIndex.currentKeyIndex] + migrateDataToNativeBuffer(cocoaStorage) + let key = _forceBridgeFromObjectiveC(anyObjectKey, Key.self) + let nativeIndex = asNative.index(forKey: key)! + + return nativePointerToValue(at: ._native(nativeIndex)) +#else + _sanityCheckFailure("internal error: unexpected cocoa ${Self}") +#endif + } + } +%end + internal mutating func nativeInsert( _ value: Value, forKey key: Key ) -> (inserted: Bool, memberAfterInsert: Value) { @@ -4361,6 +4835,110 @@ internal enum _Variant${Self}Buffer<${TypeParametersDecl}> : _HashBuffer { } } +%if Self == 'Dictionary': + internal func nativeMapValues( + _ transform: (Value) throws -> T + ) rethrows -> _Variant${Self}Buffer { + var buffer = _Native${Self}Buffer(capacity: asNative.capacity) + + // Because the keys in the current and new buffer are the same, we can + // initialize to the same locations in the new buffer, skipping hash value + // recalculations. + var i = asNative.startIndex + while i != asNative.endIndex { + let (k, v) = asNative.assertingGet(i) + try buffer.initializeKey(k, value: transform(v), at: i.offset) + asNative.formIndex(after: &i) + } + buffer.count = asNative.count + + return .native(buffer) + } + + internal func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> _Variant${Self}Buffer { + if _fastPath(guaranteedNative) { + return try nativeMapValues(transform) + } + + switch self { + case .native: + return try nativeMapValues(transform) + case .cocoa(let cocoaStorage): +#if _runtime(_ObjC) + var storage: _Variant${Self}Buffer = .native( + _Native${Self}Buffer(capacity: cocoaStorage.count)) + + var i = cocoaStorage.startIndex + while i != cocoaStorage.endIndex { + let (anyObjectKey, anyObjectValue) = cocoaStorage.assertingGet(i) + let nativeKey = _forceBridgeFromObjectiveC(anyObjectKey, Key.self) + let nativeValue = _forceBridgeFromObjectiveC(anyObjectValue, Value.self) + _ = try storage.nativeInsert(transform(nativeValue), forKey: nativeKey) + cocoaStorage.formIndex(after: &i) + } + + return storage +#else + _sanityCheckFailure("internal error: unexpected cocoa ${Self}") +#endif + } + } + + internal mutating func nativeMerge( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Iterator.Element == (Key, Value) { + for (key, value) in keysAndValues { + var (i, found) = asNative._find(key, startBucket: asNative._bucket(key)) + + if found { + _ = ensureUniqueNativeBuffer(asNative.capacity) + do { + let newValue = try combine(asNative.value(at: i.offset), value) + asNative.setKey(key, value: newValue, at: i.offset) + } catch _MergeError.keyCollision { + fatalError("Duplicate values for key: '\(key)'") + } + } else { + let minCapacity = NativeBuffer.minimumCapacity( + minimumCount: asNative.count + 1, + maxLoadFactorInverse: _hashContainerDefaultMaxLoadFactorInverse) + + let (_, capacityChanged) = ensureUniqueNativeBuffer(minCapacity) + if capacityChanged { + i = asNative._find(key, startBucket: asNative._bucket(key)).pos + } + + asNative.initializeKey(key, value: value, at: i.offset) + asNative.count += 1 + } + } + } + + internal mutating func merge( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Iterator.Element == (Key, Value) { + if _fastPath(guaranteedNative) { + try nativeMerge(keysAndValues, uniquingKeysWith: combine) + } + + switch self { + case .native: + try nativeMerge(keysAndValues, uniquingKeysWith: combine) + case .cocoa(let cocoaStorage): +#if _runtime(_ObjC) + migrateDataToNativeBuffer(cocoaStorage) + try nativeMerge(keysAndValues, uniquingKeysWith: combine) +#else + _sanityCheckFailure("internal error: unexpected cocoa ${Self}") +#endif + } + } +%end + /// - parameter idealBucket: The ideal bucket for the element being deleted. /// - parameter offset: The offset of the element that will be deleted. /// Precondition: there should be an initialized entry at offset. @@ -5164,6 +5742,43 @@ extension ${Self} { guard !isEmpty else { return nil } return remove(at: startIndex) } + + @_inlineable + @available(swift, obsoleted: 4.0) + public func filter( + _ isIncluded: (Element) throws -> Bool + ) rethrows -> [Element] { + var result: [Element] = [] + for x in self { + if try isIncluded(x) { + result.append(x) + } + } + return result + } + + /// The total number of ${element}s that the ${a_self} can contain without + /// allocating new storage. + public var capacity: Int { + return _variantBuffer.effectiveCapacity + } + + /// Reserves enough space to store the specified number of ${element}s. + /// + /// If you are adding a known number of ${element}s to a ${a_self}, use this + /// method to avoid multiple reallocations. This method ensures that the + /// ${a_self} has unique, mutable, contiguous storage, with space allocated + /// for at least the requested number of ${element}s. + /// + /// Calling the `reserveCapacity(_:)` method on a ${a_self} with bridged + /// storage triggers a copy to contiguous storage even if the existing + /// storage has room to store `minimumCapacity` ${element}s. + /// + /// - Parameter minimumCapacity: The requested number of ${element}s to + /// store. + public mutating func reserveCapacity(_ minimumCapacity: Int) { + _variantBuffer.reserveCapacity(minimumCapacity) + } } //===--- Bridging ---------------------------------------------------------===// diff --git a/test/api-digester/source-stability.swift.expected b/test/api-digester/source-stability.swift.expected index 49d933800f288..3d113b5d58bc0 100644 --- a/test/api-digester/source-stability.swift.expected +++ b/test/api-digester/source-stability.swift.expected @@ -148,8 +148,18 @@ Protocol Integer has been renamed to Protocol BinaryInteger Func FloatingPoint.adding(_:) has been renamed to Func FloatingPoint.addingProduct(_:_:) Var BidirectionalCollection.indices has declared type change from DefaultBidirectionalIndices to Self.Indices Var Dictionary.endIndex has declared type change from DictionaryIndex to Dictionary.Index + +/* Added new type */ +Var Dictionary.keys has declared type change from LazyMapCollection, Key> to Dictionary.Keys + +/* FIXME: Bogus */ Var Dictionary.startIndex has declared type change from DictionaryIndex to Dictionary.Index Var RandomAccessCollection.indices has declared type change from DefaultRandomAccessIndices to Self.Indices + +/* Added new type */ +Var Dictionary.values has declared type change from LazyMapCollection, Value> to Dictionary.Values + +/* FIXME: Bogus */ Var Set.endIndex has declared type change from SetIndex to Set.Index Var Set.first has declared type change from Element? to (Element)? Var Set.startIndex has declared type change from SetIndex to Set.Index diff --git a/validation-test/stdlib/Dictionary.swift b/validation-test/stdlib/Dictionary.swift index a21beedc71005..e3f71f99c61a1 100644 --- a/validation-test/stdlib/Dictionary.swift +++ b/validation-test/stdlib/Dictionary.swift @@ -478,6 +478,190 @@ DictionaryTestSuite.test("COW.Slow.AddDoesNotReallocate") { } } +DictionaryTestSuite.test("COW.Fast.MergeDoesNotReallocate") { + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + // Merge some new values. + d1.merge([(40, 2040), (50, 2050)]) { _, y in y } + assert(identity1 == d1._rawIdentifier()) + assert(d1.count == 5) + assert(d1[50]! == 2050) + + // Merge and overwrite some existing values. + d1.merge([(10, 2010), (60, 2060)]) { _, y in y } + assert(identity1 == d1._rawIdentifier()) + assert(d1.count == 6) + assert(d1[10]! == 2010) + assert(d1[60]! == 2060) + + // Merge, keeping existing values. + d1.merge([(30, 2030), (70, 2070)]) { x, _ in x } + assert(identity1 == d1._rawIdentifier()) + assert(d1.count == 7) + assert(d1[30]! == 1030) + assert(d1[70]! == 2070) + } + + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + var d2 = d1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // Merge some new values. + d2.merge([(40, 2040), (50, 2050)]) { _, y in y } + assert(identity1 == d1._rawIdentifier()) + assert(identity1 != d2._rawIdentifier()) + + assert(d1.count == 3) + assert(d1[10]! == 1010) + assert(d1[20]! == 1020) + assert(d1[30]! == 1030) + assert(d1[40] == nil) + + assert(d2.count == 5) + assert(d2[10]! == 1010) + assert(d2[20]! == 1020) + assert(d2[30]! == 1030) + assert(d2[40]! == 2040) + assert(d2[50]! == 2050) + + // Keep variables alive. + _fixLifetime(d1) + _fixLifetime(d2) + } + + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + var d2 = d1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // Merge and overwrite some existing values. + d2.merge([(10, 2010)]) { _, y in y } + assert(identity1 == d1._rawIdentifier()) + assert(identity1 != d2._rawIdentifier()) + + assert(d1.count == 3) + assert(d1[10]! == 1010) + assert(d1[20]! == 1020) + assert(d1[30]! == 1030) + + assert(d2.count == 3) + assert(d2[10]! == 2010) + assert(d2[20]! == 1020) + assert(d2[30]! == 1030) + + // Keep variables alive. + _fixLifetime(d1) + _fixLifetime(d2) + } + + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + var d2 = d1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // Merge, keeping existing values. + d2.merge([(10, 2010)]) { x, _ in x } + assert(identity1 == d1._rawIdentifier()) + assert(identity1 != d2._rawIdentifier()) + + assert(d1.count == 3) + assert(d1[10]! == 1010) + assert(d1[20]! == 1020) + assert(d1[30]! == 1030) + + assert(d2.count == 3) + assert(d2[10]! == 1010) + assert(d2[20]! == 1020) + assert(d2[30]! == 1030) + + // Keep variables alive. + _fixLifetime(d1) + _fixLifetime(d2) + } +} + +DictionaryTestSuite.test("COW.Fast.DefaultedSubscriptDoesNotReallocate") { + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + // No mutation on access. + assert(d1[10, default: 0] + 1 == 1011) + assert(d1[40, default: 0] + 1 == 1) + assert(identity1 == d1._rawIdentifier()) + assert(d1[10]! == 1010) + + // Increment existing in place. + d1[10, default: 0] += 1 + assert(identity1 == d1._rawIdentifier()) + assert(d1[10]! == 1011) + + // Add incremented default value. + d1[40, default: 0] += 1 + assert(identity1 == d1._rawIdentifier()) + assert(d1[40]! == 1) + } + + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + var d2 = d1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // No mutation on access. + assert(d2[10, default: 0] + 1 == 1011) + assert(d2[40, default: 0] + 1 == 1) + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // Increment existing in place. + d2[10, default: 0] += 1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 != d2._rawIdentifier()) + + assert(d1[10]! == 1010) + assert(d2[10]! == 1011) + + // Keep variables alive. + _fixLifetime(d1) + _fixLifetime(d2) + } + + do { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + var d2 = d1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 == d2._rawIdentifier()) + + // Add incremented default value. + d2[40, default: 0] += 1 + assert(identity1 == d1._rawIdentifier()) + assert(identity1 != d2._rawIdentifier()) + + assert(d1[40] == nil) + assert(d2[40]! == 1) + + // Keep variables alive. + _fixLifetime(d1) + _fixLifetime(d2) + } +} DictionaryTestSuite.test("COW.Fast.IndexForKeyDoesNotReallocate") { var d = getCOWFastDictionary() @@ -978,6 +1162,84 @@ DictionaryTestSuite.test("COW.Slow.EqualityTestDoesNotReallocate") { assert(identity2 == d2._rawIdentifier()) } +//===--- +// Keys and Values collection tests. +//===--- + +DictionaryTestSuite.test("COW.Fast.ValuesAccessDoesNotReallocate") { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + assert([1010, 1020, 1030] == d1.values.sorted()) + assert(identity1 == d1._rawIdentifier()) + + var d2 = d1 + assert(identity1 == d2._rawIdentifier()) + + let i = d2.index(forKey: 10)! + assert(d1.values[i] == 1010) + assert(d1[i] == (10, 1010)) + + d2.values[i] += 1 + assert(d2.values[i] == 1011) + assert(d2[10]! == 1011) + assert(identity1 != d2._rawIdentifier()) + + assert(d1[10]! == 1010) + assert(identity1 == d1._rawIdentifier()) + + checkCollection( + [1010, 1020, 1030], + d1.values, + stackTrace: SourceLocStack()) + { $0 == $1 } +} + +DictionaryTestSuite.test("COW.Fast.KeysAccessDoesNotReallocate") { + var d1 = getCOWFastDictionary() + var identity1 = d1._rawIdentifier() + + assert([10, 20, 30] == d1.keys.sorted()) + + let i = d1.index(forKey: 10)! + assert(d1.keys[i] == 10) + assert(d1[i] == (10, 1010)) + assert(identity1 == d1._rawIdentifier()) + + checkCollection( + [10, 20, 30], + d1.keys, + stackTrace: SourceLocStack()) + { $0 == $1 } + + do { + var d2: [MinimalHashableValue : Int] = [ + MinimalHashableValue(10): 1010, + MinimalHashableValue(20): 1020, + MinimalHashableValue(30): 1030, + MinimalHashableValue(40): 1040, + MinimalHashableValue(50): 1050, + MinimalHashableValue(60): 1060, + MinimalHashableValue(70): 1070, + MinimalHashableValue(80): 1080, + MinimalHashableValue(90): 1090, + ] + + // Find the last key in the dictionary + var lastKey: MinimalHashableValue = d2.first!.key + for i in d2.indices { lastKey = d2[i].key } + + MinimalHashableValue.timesEqualEqualWasCalled = 0 + let j = d2.index(forKey: lastKey)! + expectEqual(1, MinimalHashableValue.timesEqualEqualWasCalled) + + MinimalHashableValue.timesEqualEqualWasCalled = 0 + let k = d2.keys.index(of: lastKey)! + expectEqual(1, MinimalHashableValue.timesEqualEqualWasCalled) + + expectEqual(j, k) + } +} //===--- // Native dictionary tests. @@ -1165,6 +1427,146 @@ DictionaryTestSuite.test("init(dictionaryLiteral:)") { } } +DictionaryTestSuite.test("init(uniqueKeysWithValues:)") { + do { + var d = Dictionary(uniqueKeysWithValues: [(10, 1010), (20, 1020), (30, 1030)]) + assert(d.count == 3) + assert(d[10]! == 1010) + assert(d[20]! == 1020) + assert(d[30]! == 1030) + assert(d[1111] == nil) + } + do { + var d = Dictionary(uniqueKeysWithValues: EmptyCollection<(Int, Int)>()) + assert(d.count == 0) + assert(d[1111] == nil) + } + do { + expectCrashLater() + var d = Dictionary(uniqueKeysWithValues: [(10, 1010), (20, 1020), (10, 2010)]) + } +} + +DictionaryTestSuite.test("init(_:uniquingKeysWith:)") { + do { + var d = Dictionary( + [(10, 1010), (20, 1020), (30, 1030), (10, 2010)], uniquingKeysWith: min) + assert(d.count == 3) + assert(d[10]! == 1010) + assert(d[20]! == 1020) + assert(d[30]! == 1030) + assert(d[1111] == nil) + } + do { + var d = Dictionary( + [(10, 1010), (20, 1020), (30, 1030), (10, 2010)] as [(Int, Int)], + uniquingKeysWith: +) + assert(d.count == 3) + assert(d[10]! == 3020) + assert(d[20]! == 1020) + assert(d[30]! == 1030) + assert(d[1111] == nil) + } + do { + var d = Dictionary([(10, 1010), (20, 1020), (30, 1030), (10, 2010)]) { + (a, b) in Int("\(a)\(b)")! + } + assert(d.count == 3) + assert(d[10]! == 10102010) + assert(d[20]! == 1020) + assert(d[30]! == 1030) + assert(d[1111] == nil) + } + do { + var d = Dictionary([(10, 1010), (10, 2010), (10, 3010), (10, 4010)]) { $1 } + assert(d.count == 1) + assert(d[10]! == 4010) + assert(d[1111] == nil) + } + do { + var d = Dictionary(EmptyCollection<(Int, Int)>(), uniquingKeysWith: min) + assert(d.count == 0) + assert(d[1111] == nil) + } + + struct TE: Error {} + do { + // No duplicate keys, so no error thrown. + var d1 = try Dictionary([(10, 1), (20, 2), (30, 3)]) { _ in throw TE() } + assert(d1.count == 3) + // Duplicate keys, should throw error. + var d2 = try Dictionary([(10, 1), (10, 2)]) { _ in throw TE() } + assertionFailure() + } catch { + assert(error is TE) + } +} + +DictionaryTestSuite.test("init(grouping:by:)") { + let r = 0..<10 + + let d1 = Dictionary(grouping: r, by: { $0 % 3 }) + expectEqual(3, d1.count) + expectEqual([0, 3, 6, 9], d1[0]!) + expectEqual([1, 4, 7], d1[1]!) + expectEqual([2, 5, 8], d1[2]!) + + let d2 = Dictionary(grouping: r, by: { $0 }) + expectEqual(10, d2.count) + + let d3 = Dictionary(grouping: 0..<0, by: { $0 }) + expectEqual(0, d3.count) +} + +DictionaryTestSuite.test("mapValues(_:)") { + let d1 = [10: 1010, 20: 1020, 30: 1030] + let d2 = d1.mapValues(String.init) + + expectEqual(d1.count, d2.count) + expectEqual(d1.keys.first, d2.keys.first) + + for (key, value) in d1 { + expectEqual(String(d1[key]!), d2[key]!) + } + + do { + var d3: [MinimalHashableValue : Int] = Dictionary( + uniqueKeysWithValues: d1.lazy.map { (MinimalHashableValue($0), $1) }) + expectEqual(d3.count, 3) + MinimalHashableValue.timesEqualEqualWasCalled = 0 + MinimalHashableValue.timesHashValueWasCalled = 0 + + // Calling mapValues shouldn't ever recalculate any hashes. + let d4 = d3.mapValues(String.init) + expectEqual(d4.count, d3.count) + expectEqual(0, MinimalHashableValue.timesEqualEqualWasCalled) + expectEqual(0, MinimalHashableValue.timesHashValueWasCalled) + } +} + +DictionaryTestSuite.test("capacity/reserveCapacity(_:)") { + var d1 = [10: 1010, 20: 1020, 30: 1030] + expectEqual(3, d1.capacity) + d1[40] = 1040 + expectEqual(6, d1.capacity) + + // Reserving new capacity jumps up to next limit. + d1.reserveCapacity(7) + expectEqual(12, d1.capacity) + + // Can reserve right up to a limit. + d1.reserveCapacity(24) + expectEqual(24, d1.capacity) + + // Fill up to the limit, no reallocation. + d1.merge(stride(from: 50, through: 240, by: 10).lazy.map { ($0, 1000 + $0) }, + uniquingKeysWith: { _ in fatalError() }) + expectEqual(24, d1.count) + expectEqual(24, d1.capacity) + d1[250] = 1250 + expectEqual(48, d1.capacity) +} + #if _runtime(_ObjC) //===--- // NSDictionary -> Dictionary bridging tests. diff --git a/validation-test/stdlib/HashedCollectionFilter3.swift b/validation-test/stdlib/HashedCollectionFilter3.swift new file mode 100644 index 0000000000000..07735c561fabe --- /dev/null +++ b/validation-test/stdlib/HashedCollectionFilter3.swift @@ -0,0 +1,21 @@ +// RUN: %target-run-stdlib-swift +// REQUIRES: executable_test + +import StdlibUnittest + +var FilterTestSuite = TestSuite("HashedCollectionFilter") + +FilterTestSuite.test("Dictionary.filter(_:) -> [(Key, Value)]") { + let d = [10: 1010, 20: 1020, 30: 1030, 40: 1040] + let f: Any = d.filter { (k, v) in k > 20 } + expectTrue(f is [(Int, Int)]) +} + +FilterTestSuite.test("Set.filter(_:) -> [Element]") { + let s: Set = [10, 20, 30, 40] + let f: Any = s.filter { $0 > 20 } + expectTrue(f is [Int]) +} + +runAllTests() + diff --git a/validation-test/stdlib/HashedCollectionFilter4.swift b/validation-test/stdlib/HashedCollectionFilter4.swift new file mode 100644 index 0000000000000..21ae8393012d7 --- /dev/null +++ b/validation-test/stdlib/HashedCollectionFilter4.swift @@ -0,0 +1,41 @@ +// RUN: %target-run-stdlib-swift -swift-version 4 +// REQUIRES: executable_test + +import StdlibUnittest + +var FilterTestSuite = TestSuite("HashedCollectionFilter") + +FilterTestSuite.test("Dictionary.filter(_:) -> [Key: Value]") + .xfail(.always("Not actually running under Swift 4")).code +{ + let d = [10: 1010, 20: 1020, 30: 1030, 40: 1040] + // filter(_:) should produce a dictionary in Swift 4 + let f: Any = d.filter { (k, v) in k > 20 } + expectTrue(f is [Int: Int]) +} + +FilterTestSuite.test("Dictionary.filter(_:) -> [(Key, Value)] available") { + let d = [10: 1010, 20: 1020, 30: 1030, 40: 1040] + // The Array-returning version from Sequence should still be accessible + let f: [(Int, Int)] = d.filter { (k, v) in k > 20 } + expectEqual(2, f.count) +} + +FilterTestSuite.test("Set.filter(_:) -> Set") + .xfail(.always("Not actually running under Swift 4")).code +{ + let s: Set = [10, 20, 30, 40] + // filter(_:) should produce a set in Swift 4 + let f: Any = s.filter { $0 > 20 } + expectTrue(f is Set) +} + +FilterTestSuite.test("Set.filter(_:) -> [Element] available") { + let s: Set = [10, 20, 30, 40] + // The Array-returning version from Sequence should still be accessible + let f: [Int] = s.filter { $0 > 20 } + expectEqual(2, f.count) +} + +runAllTests() + diff --git a/validation-test/stdlib/Set.swift b/validation-test/stdlib/Set.swift index f5acc15190653..18fb2da879355 100644 --- a/validation-test/stdlib/Set.swift +++ b/validation-test/stdlib/Set.swift @@ -3488,6 +3488,28 @@ SetTestSuite.test("first") { expectNil(emptySet.first) } +SetTestSuite.test("capacity/reserveCapacity(_:)") { + var s1: Set = [10, 20, 30] + expectEqual(3, s1.capacity) + s1.insert(40) + expectEqual(6, s1.capacity) + + // Reserving new capacity jumps up to next limit. + s1.reserveCapacity(7) + expectEqual(12, s1.capacity) + + // Can reserve right up to a limit. + s1.reserveCapacity(24) + expectEqual(24, s1.capacity) + + // Fill up to the limit, no reallocation. + s1.formUnion(stride(from: 50, through: 240, by: 10)) + expectEqual(24, s1.count) + expectEqual(24, s1.capacity) + s1.insert(250) + expectEqual(48, s1.capacity) +} + SetTestSuite.test("isEmpty") { let s1 = Set([1010, 2020, 3030]) expectFalse(s1.isEmpty)