From b80033701b1ae2d2662fb9aa3cebfb38ab85c733 Mon Sep 17 00:00:00 2001 From: Edward Marchant Date: Wed, 11 Aug 2021 21:00:22 +0100 Subject: [PATCH 1/5] Initial sparse set implementation --- .../xcschemes/SparseSetModule.xcscheme | 67 ++ .../swift-collections-Package.xcscheme | 24 + .../swift-collections-benchmark.xcscheme | 10 + .../Benchmarks/SparseSetBenchmarks.swift | 522 ++++++++ .../swift-collections-benchmark/main.swift | 1 + Package.swift | 12 + Sources/SparseSetModule/CMakeLists.txt | 33 + .../SparseSetModule/SparseSet+Codable.swift | 71 ++ ...arseSet+CustomDebugStringConvertible.swift | 44 + .../SparseSet+CustomStringConvertible.swift | 29 + .../SparseSet+DenseStorage.swift | 77 ++ .../SparseSet+Elements+SubSequence.swift | 322 +++++ .../SparseSetModule/SparseSet+Elements.swift | 641 ++++++++++ .../SparseSetModule/SparseSet+Equatable.swift | 23 + ...seSet+ExpressibleByDictionaryLiteral.swift | 29 + .../SparseSetModule/SparseSet+Hashable.swift | 25 + .../SparseSet+Initializers.swift | 302 +++++ .../SparseSet+Invariants.swift | 32 + .../SparseSet+Partial MutableCollection.swift | 215 ++++ ...t+Partial RangeReplaceableCollection.swift | 189 +++ .../SparseSetModule/SparseSet+Sequence.swift | 65 + .../SparseSet+SparseStorage.swift | 179 +++ .../SparseSetModule/SparseSet+Values.swift | 380 ++++++ Sources/SparseSetModule/SparseSet.swift | 642 ++++++++++ Tests/SparseSetTests/SparseSet Tests.swift | 1065 +++++++++++++++++ Tests/SparseSetTests/SparseSet Utils.swift | 30 + .../SparseSet+Elements Tests.swift | 546 +++++++++ .../SparseSet+Values Tests.swift | 206 ++++ 28 files changed, 5781 insertions(+) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/SparseSetModule.xcscheme create mode 100644 Benchmarks/Benchmarks/SparseSetBenchmarks.swift create mode 100644 Sources/SparseSetModule/CMakeLists.txt create mode 100644 Sources/SparseSetModule/SparseSet+Codable.swift create mode 100644 Sources/SparseSetModule/SparseSet+CustomDebugStringConvertible.swift create mode 100644 Sources/SparseSetModule/SparseSet+CustomStringConvertible.swift create mode 100644 Sources/SparseSetModule/SparseSet+DenseStorage.swift create mode 100644 Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift create mode 100644 Sources/SparseSetModule/SparseSet+Elements.swift create mode 100644 Sources/SparseSetModule/SparseSet+Equatable.swift create mode 100644 Sources/SparseSetModule/SparseSet+ExpressibleByDictionaryLiteral.swift create mode 100644 Sources/SparseSetModule/SparseSet+Hashable.swift create mode 100644 Sources/SparseSetModule/SparseSet+Initializers.swift create mode 100644 Sources/SparseSetModule/SparseSet+Invariants.swift create mode 100644 Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift create mode 100644 Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift create mode 100644 Sources/SparseSetModule/SparseSet+Sequence.swift create mode 100644 Sources/SparseSetModule/SparseSet+SparseStorage.swift create mode 100644 Sources/SparseSetModule/SparseSet+Values.swift create mode 100644 Sources/SparseSetModule/SparseSet.swift create mode 100644 Tests/SparseSetTests/SparseSet Tests.swift create mode 100644 Tests/SparseSetTests/SparseSet Utils.swift create mode 100644 Tests/SparseSetTests/SparseSet+Elements Tests.swift create mode 100644 Tests/SparseSetTests/SparseSet+Values Tests.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/SparseSetModule.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/SparseSetModule.xcscheme new file mode 100644 index 000000000..eb10c1d6a --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/SparseSetModule.xcscheme @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme index bf3d7efa4..068929ade 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme @@ -146,6 +146,20 @@ ReferencedContainer = "container:"> + + + + + + + + + + + + init(uniqueKeysWithValues:)", + input: [Int].self + ) { input in + let keysAndValues = input.map { (Key($0), 2 * $0) } + return { timer in + blackHole(SparseSet(uniqueKeysWithValues: keysAndValues)) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> init(uncheckedUniqueKeysWithValues:)", + input: [Int].self + ) { input in + let keysAndValues = input.map { (Key($0), 2 * $0) } + return { timer in + blackHole( + SparseSet(uncheckedUniqueKeysWithValues: keysAndValues)) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> init(uncheckedUniqueKeys:values:)", + input: [Int].self + ) { input in + let keys = input.map { Key($0) } + let values = input.map { 2 * Int($0) } + return { timer in + blackHole( + SparseSet(uncheckedUniqueKeys: keys, values: values)) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> sequential iteration", + input: [Int].self + ) { input in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, values: values) + return { timer in + for item in s { + blackHole(item) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int>.Keys sequential iteration", + input: [Int].self + ) { input in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, values: values) + return { timer in + for item in s.keys { + blackHole(item) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int>.Values sequential iteration", + input: [Int].self + ) { input in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, values: values) + return { timer in + for item in s.values { + blackHole(item) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, successful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, values: values) + return { timer in + for i in lookups { + precondition(s[Key(i)] == 2 * i) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, unsuccessful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, values: values) + let c = input.count + return { timer in + for i in lookups { + precondition(s[Key(i + c)] == nil) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, noop setter", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + let c = input.count + timer.measure { + for i in lookups { + s[Key(i + c)] = nil + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, set existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s[Key(i)] = 0 + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, _modify", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s[Key(i)]! *= 2 + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.addSimple( + title: "SparseSet<\(Key.self), Int> subscript, append", + input: [Int].self + ) { input in + var s: SparseSet = [:] + for i in input { + s[Key(i)] = 2 * i + } + precondition(s.count == input.count) + blackHole(s) + } + + self.addSimple( + title: "SparseSet<\(Key.self), Int> subscript, append, reserving capacity", + input: [Int].self + ) { input in + let universeSize: Int = input.max().map { $0 + 1 } ?? 0 + var s: SparseSet = SparseSet(minimumCapacity: input.count, universeSize: universeSize) + for i in input { + s[Key(i)] = 2 * i + } + precondition(s.count == input.count) + blackHole(s) + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, remove existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s[Key(i)] = nil + } + } + precondition(s.isEmpty) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> subscript, remove missing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + let c = input.count + timer.measure { + for i in lookups { + s[Key(i + c)] = nil + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> defaulted subscript, successful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let keysAndValues = input.map { (Key($0), 2 * $0) } + let s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + return { timer in + for i in lookups { + precondition(s[Key(i), default: -1] != -1) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> defaulted subscript, unsuccessful lookups", + input: ([Int], [Int]).self + ) { input, lookups in + let keysAndValues = input.map { (Key($0), 2 * $0) } + let s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + return { timer in + let c = s.count + for i in lookups { + precondition(s[Key(i + c), default: -1] == -1) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> defaulted subscript, _modify existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s[Key(i), default: -1] *= 2 + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> defaulted subscript, _modify missing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + let c = input.count + timer.measure { + for i in lookups { + s[Key(c + i), default: -1] *= 2 + } + } + precondition(s.count == 2 * input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> successful index(forKey:)", + input: ([Int], [Int]).self + ) { input, lookups in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, + values: values) + return { timer in + for i in lookups { + precondition(s.index(forKey: Key(i)) != nil) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> unsuccessful index(forKey:)", + input: ([Int], [Int]).self + ) { input, lookups in + let keys = input.map { Key($0) } + let values = input.map { 2 * $0 } + let s = SparseSet( + uncheckedUniqueKeys: keys, + values: values) + return { timer in + for i in lookups { + precondition(s.index(forKey: Key(lookups.count + i)) == nil) + } + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> updateValue(_:forKey:), existing", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s.updateValue(0, forKey: Key(i)) + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> updateValue(_:forKey:), append", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + s.updateValue(0, forKey: Key(input.count + i)) + } + } + precondition(s.count == 2 * input.count) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> random swaps", + input: [Int].self + ) { input in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + var v = 0 + for i in input { + s.swapAt(Int(i), v) + v += 1 + } + } + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> partitioning around middle", + input: [Int].self + ) { input in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + let pivot = input.count / 2 + timer.measure { + let r = s.partition(by: { $0.key >= pivot }) + precondition(r == pivot) + } + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> sort", + input: [Int].self + ) { input in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + s.sort() + } + precondition(s.keys.elementsEqual((0 ..< input.count).lazy.map { Key($0) })) + precondition(s.values.elementsEqual((0 ..< input.count).lazy.map { 2 * $0 })) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> removeLast", + input: Int.self + ) { size in + return { timer in + var s = SparseSet( + uncheckedUniqueKeys: (0 ..< size).map { Key($0) }, + values: size ..< 2 * size) + timer.measure { + for _ in 0 ..< size { + s.removeLast() + } + } + precondition(s.isEmpty) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> removeFirst", + input: Int.self + ) { size in + return { timer in + var s = SparseSet( + uncheckedUniqueKeys: (0 ..< size).map { Key($0) }, + values: size ..< 2 * size) + timer.measure { + for _ in 0 ..< size { + s.removeFirst() + } + } + precondition(s.isEmpty) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> random removals (offset-based)", + input: Insertions.self + ) { insertions in + return { timer in + let insertions = insertions.values + var s = SparseSet( + uncheckedUniqueKeys: (0 ..< insertions.count).map { Key($0) }, + values: insertions.count ..< 2 * insertions.count) + timer.measure { + for i in stride(from: insertions.count, to: 0, by: -1) { + s.remove(at: insertions[i - 1]) + } + } + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> random removals (existing keys)", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let keysAndValues = input.map { (Key($0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + precondition(s.removeValue(forKey: Key(i)) != nil) + } + } + precondition(s.count == 0) + blackHole(s) + } + } + + self.add( + title: "SparseSet<\(Key.self), Int> random removals (missing keys)", + input: ([Int], [Int]).self + ) { input, lookups in + return { timer in + let c = input.count + let keysAndValues = input.map { (Key(c + $0), 2 * $0) } + var s = SparseSet( + uncheckedUniqueKeysWithValues: keysAndValues) + timer.measure { + for i in lookups { + precondition(s.removeValue(forKey: Key(i)) == nil) + } + } + precondition(s.count == input.count) + blackHole(s) + } + } + } +} diff --git a/Benchmarks/swift-collections-benchmark/main.swift b/Benchmarks/swift-collections-benchmark/main.swift index d2096d384..bf7a78b53 100644 --- a/Benchmarks/swift-collections-benchmark/main.swift +++ b/Benchmarks/swift-collections-benchmark/main.swift @@ -20,6 +20,7 @@ benchmark.addDequeBenchmarks() benchmark.addOrderedSetBenchmarks() benchmark.addOrderedDictionaryBenchmarks() benchmark.addHeapBenchmarks() +benchmark.addSparseSetBenchmarks() benchmark.addCppBenchmarks() #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) benchmark.addFoundationBenchmarks() diff --git a/Package.swift b/Package.swift index ace16942c..1d38cb587 100644 --- a/Package.swift +++ b/Package.swift @@ -55,6 +55,7 @@ let package = Package( .library(name: "DequeModule", targets: ["DequeModule"]), .library(name: "OrderedCollections", targets: ["OrderedCollections"]), .library(name: "PriorityQueueModule", targets: ["PriorityQueueModule"]), + .library(name: "SparseSetModule", targets: ["SparseSetModule"]), ], dependencies: [ // This is only used in the benchmark executable target. @@ -67,6 +68,7 @@ let package = Package( "DequeModule", "OrderedCollections", "PriorityQueueModule", + "SparseSetModule", ], path: "Sources/Collections", exclude: ["CMakeLists.txt"], @@ -142,6 +144,16 @@ let package = Package( name: "PriorityQueueTests", dependencies: ["PriorityQueueModule"], swiftSettings: settings), + + // SparseSet + .target( + name: "SparseSetModule", + exclude: ["CMakeLists.txt"], + swiftSettings: settings), + .testTarget( + name: "SparseSetTests", + dependencies: ["SparseSetModule", "CollectionsTestSupport"], + swiftSettings: settings), ], cxxLanguageStandard: .cxx1z ) diff --git a/Sources/SparseSetModule/CMakeLists.txt b/Sources/SparseSetModule/CMakeLists.txt new file mode 100644 index 000000000..7c40c29f1 --- /dev/null +++ b/Sources/SparseSetModule/CMakeLists.txt @@ -0,0 +1,33 @@ + +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2021 Apple Inc. and the Swift project authors +Licensed under Apache License v2.0 with Runtime Library Exception + +See https://swift.org/LICENSE.txt for license information +#]] + +add_library(SparseSetModule + SparseSet.swift + SparseSet+Codable.swift + SparseSet+CustomDebugStringConvertible.swift + SparseSet+CustomStringConvertible.swift + SparseSet+DenseStorage.swift + SparseSet+Equatable.swift + SparseSet+Elements.swift + SparseSet+Elements+SubSequence.swift + SparseSet+ExpressibleByDictionaryLiteral.swift + SparseSet+Hashable.swift + SparseSet+Initializers.swift + SparseSet+Invariants.swift + "SparseSet+Partial MutableCollection.swift" + "SparseSet+Partial RangeReplaceableCollection.swift" + SparseSet+Sequence.swift + SparseSet+SparseStorage.swift + SparseSet+Values.swift) +set_target_properties(SparseSetModule PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +_install_target(SparseSetModule) +set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS SparseSetModule) diff --git a/Sources/SparseSetModule/SparseSet+Codable.swift b/Sources/SparseSetModule/SparseSet+Codable.swift new file mode 100644 index 000000000..505609bb3 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Codable.swift @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: Encodable where Key: Encodable, Value: Encodable { + /// Encodes the contents of this sparse set into the given encoder. + /// + /// The sparse set's contents are encoded as alternating key-value pairs in + /// an unkeyed container. + /// + /// This function throws an error if any values are invalid for the given + /// encoder's format. + /// + /// - Parameter encoder: The encoder to write data to. + @inlinable + public func encode(to encoder: Encoder) throws { + // Encode contents as an array of alternating key-value pairs. + var container = encoder.unkeyedContainer() + for (key, value) in self { + try container.encode(key) + try container.encode(value) + } + } +} + +extension SparseSet: Decodable where Key: Decodable, Value: Decodable { + /// Creates a new sparse set by decoding from the given decoder. + /// + /// `SparseSet` expects its contents to be encoded as alternating + /// key-value pairs in an unkeyed container. + /// + /// This initializer throws an error if reading from the decoder fails, or + /// if the decoded contents are not in the expected format. + /// + /// - Parameter decoder: The decoder to read data from. + @inlinable + public init(from decoder: Decoder) throws { + // We expect to be encoded as an array of alternating key-value pairs. + var container = try decoder.unkeyedContainer() + + self.init() + while !container.isAtEnd { + let key = try container.decode(Key.self) + let index = _find(key: key) + guard index == nil else { + let context = DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Duplicate key at offset \(container.currentIndex - 1)") + throw DecodingError.dataCorrupted(context) + } + + guard !container.isAtEnd else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Unkeyed container reached end before value in key-value pair" + ) + ) + } + let value = try container.decode(Value.self) + _append(value: value, key: key) + } + } +} diff --git a/Sources/SparseSetModule/SparseSet+CustomDebugStringConvertible.swift b/Sources/SparseSetModule/SparseSet+CustomDebugStringConvertible.swift new file mode 100644 index 000000000..010b367b9 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+CustomDebugStringConvertible.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: CustomDebugStringConvertible { + /// A textual representation of this instance, suitable for debugging. + public var debugDescription: String { + _debugDescription(typeName: _debugTypeName()) + } + + internal func _debugTypeName() -> String { + "SparseSet<\(Key.self), \(Value.self)>" + } + + internal func _debugDescription(typeName: String) -> String { + var result = "\(typeName)(" + if isEmpty { + result += "[:]" + } else { + result += "[" + var first = true + for (key, value) in self { + if first { + first = false + } else { + result += ", " + } + debugPrint(key, terminator: "", to: &result) + result += ": " + debugPrint(value, terminator: "", to: &result) + } + result += "]" + } + result += ")" + return result + } +} diff --git a/Sources/SparseSetModule/SparseSet+CustomStringConvertible.swift b/Sources/SparseSetModule/SparseSet+CustomStringConvertible.swift new file mode 100644 index 000000000..3c4cc456e --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+CustomStringConvertible.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: CustomStringConvertible { + /// A textual representation of this instance. + public var description: String { + if isEmpty { return "[:]" } + var result = "[" + var first = true + for (key, value) in self { + if first { + first = false + } else { + result += ", " + } + result += "\(key): \(value)" + } + result += "]" + return result + } +} diff --git a/Sources/SparseSetModule/SparseSet+DenseStorage.swift b/Sources/SparseSetModule/SparseSet+DenseStorage.swift new file mode 100644 index 000000000..f033c0c2b --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+DenseStorage.swift @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + @usableFromInline + internal struct DenseStorage { + @usableFromInline + internal var _keys: ContiguousArray + + @usableFromInline + internal var _values: ContiguousArray + + @usableFromInline + internal init(_keys: ContiguousArray, _values: ContiguousArray) { + self._keys = _keys + self._values = _values + } + } +} + +extension SparseSet.DenseStorage { + @usableFromInline + internal init() { + self._keys = [] + self._values = [] + } + + @usableFromInline + internal init(minimumCapacity: Int) { + var keys: ContiguousArray = [] + keys.reserveCapacity(minimumCapacity) + self._keys = keys + var values: ContiguousArray = [] + values.reserveCapacity(minimumCapacity) + self._values = values + } +} + +extension SparseSet.DenseStorage { + @inlinable + @inline(__always) + public var isEmpty: Bool { _keys.isEmpty } + + @inlinable + @inline(__always) + public var count: Int { _keys.count } +} + +extension SparseSet.DenseStorage { + @inlinable + internal mutating func removeAll(keepingCapacity: Bool) { + _keys.removeAll(keepingCapacity: keepingCapacity) + _values.removeAll(keepingCapacity: keepingCapacity) + } +} + +extension SparseSet.DenseStorage { + @inlinable + internal mutating func append(value: Value, key: Key) { + _keys.append(key) + _values.append(value) + } + + @inlinable + @discardableResult + internal mutating func removeLast() -> (key: Key, value: Value) { + return (key: _keys.removeLast(), value: _values.removeLast()) + } +} diff --git a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift new file mode 100644 index 000000000..c20af740f --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift @@ -0,0 +1,322 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet.Elements { + /// A collection that represents a contiguous slice of a sparse set. + /// + /// Sparse set slices are random access collections that support efficient + /// key-based lookups. + @frozen + public struct SubSequence { + @usableFromInline + internal var _base: SparseSet + @usableFromInline + internal var _bounds: Range + + @inlinable + @inline(__always) + internal init(_base: SparseSet, bounds: Range) { + self._base = _base + self._bounds = bounds + } + } +} + +extension SparseSet.Elements.SubSequence { + /// A read-only collection view containing the keys in this slice. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: ContiguousArray.SubSequence { + _base.keys[_bounds] + } + + /// A read-only collection view containing the values in this slice. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: SparseSet.Values.SubSequence { + _base.values[_bounds] + } +} + +extension SparseSet.Elements.SubSequence { + /// Returns the index for the given key. + /// + /// If the given key is found in the sparse set, this method returns an index + /// into the sparse set that corresponds with the key-value pair. + /// + /// - Parameter key: The key to find in the sparse set. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the sparse set; otherwise, `nil`. + /// + /// - Complexity: O(1) + @inlinable + public func index(forKey key: Key) -> Int? { + guard let index = _base.index(forKey: key) else { return nil } + guard _bounds.contains(index) else { return nil } + return index + } +} + +extension SparseSet.Elements.SubSequence: Sequence { + // A type representing the collection’s elements. + public typealias Element = SparseSet.Element + + /// The type that allows iteration over the collection's elements. + @frozen + public struct Iterator: IteratorProtocol { + @usableFromInline + internal var _base: SparseSet + + @usableFromInline + internal var _end: Int + + @usableFromInline + internal var _index: Int + + @inlinable + @inline(__always) + internal init(_ base: SparseSet.Elements.SubSequence) { + self._base = base._base + self._end = base._bounds.upperBound + self._index = base._bounds.lowerBound + } + + /// Advances to the next element and returns it, or `nil` if no next + /// element exists. + /// + /// - Complexity: O(1) + @inlinable + public mutating func next() -> Element? { + guard _index < _end else { return nil } + defer { _index += 1 } + return (_base._dense._keys[_index], _base._dense._values[_index]) + } + } + + /// Returns an iterator over the elements of this sparse set slice. + @inlinable + @inline(__always) + public func makeIterator() -> Iterator { + Iterator(self) + } +} + +extension SparseSet.Elements.SubSequence: RandomAccessCollection { + /// The index type for a sparse set: `Int`. + /// + /// The indices are integer offsets from the start of the original (unsliced) + /// collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting a + /// sparse set, in ascending order. + public typealias Indices = Range + + /// Sparse set subsequences are self-slicing. + public typealias SubSequence = Self + + /// The position of the first element in a nonempty sparse set slice. + /// + /// Note that instances of `SparseSet.SubSequence` generally don't have a + /// `startIndex` with an offset of zero. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { _bounds.lowerBound } + + /// The "past the end" position---that is, the position one greater than the + /// last valid subscript argument. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _bounds.upperBound } + + /// The indices that are valid for subscripting the collection, in ascending + /// order. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var indices: Range { _bounds } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the sparse set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the sparse set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the sparse set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the sparse set. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the sparse set. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the set.) + /// + /// - Parameters: + /// - i: A valid index of the sparse set. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._dense._keys.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + public subscript(position: Int) -> Element { + precondition(_bounds.contains(position), "Index out of range") + return _base[offset: position] + } + + /// Accesses a contiguous subrange of the sparse set's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original sparse set. In particular, that slice, unlike a + /// `SparseSet`, may have a nonzero `startIndex.offset` and an + /// `endIndex.offset` that is not equal to `count`. Always use the slice's + /// `startIndex` and `endIndex` properties instead of assuming that its + /// indices start or end at a particular value. + /// + /// - Parameter bounds: A range of valid indices in the sparse set. + /// + /// - Complexity: O(1) + @inlinable + public subscript(bounds: Range) -> SubSequence { + precondition( + bounds.lowerBound >= _bounds.lowerBound + && bounds.upperBound <= _bounds.upperBound, + "Index out of range") + return Self(_base: _base, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _bounds.isEmpty } + + /// The number of elements in the sparse set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _bounds.count } +} diff --git a/Sources/SparseSetModule/SparseSet+Elements.swift b/Sources/SparseSetModule/SparseSet+Elements.swift new file mode 100644 index 000000000..1bc4d14d0 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Elements.swift @@ -0,0 +1,641 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + /// A view of the contents of a sparse set as a random-access collection. + @frozen + public struct Elements { + @usableFromInline + internal var _base: SparseSet + + @inlinable + @inline(__always) + internal init(_base: SparseSet) { + self._base = _base + } + } +} + +extension SparseSet { + /// A view of the contents of this sparse set as a random-access collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var elements: Elements { + get { + Elements(_base: self) + } + _modify { + var elements = Elements(_base: self) + self = Self() + defer { self = elements._base } + yield &elements + } + } +} + +extension SparseSet.Elements { + /// A read-only collection view containing the keys in this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: ContiguousArray { + _base._dense._keys + } + + /// A mutable collection view containing the values in this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: SparseSet.Values { + get { + _base.values + } + _modify { + var values = SparseSet.Values(_base: _base) + self = Self(_base: .init()) + defer { self._base = values._base } + yield &values + } + } +} + +extension SparseSet.Elements { + /// Returns the index for the given key. + /// + /// If the given key is found in the sparse set, this method returns an index + /// into the sparse set that corresponds with the key-value pair. + /// + /// - Parameter key: The key to find in the sparse set. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the sparse set; otherwise, `nil`. + /// + /// - Complexity: O(1) + @inlinable + public func index(forKey key: Key) -> Int? { + _base.index(forKey: key) + } +} + +extension SparseSet.Elements: Sequence { + /// The element type of the collection. + public typealias Element = (key: Key, value: Value) + + @inlinable + public var underestimatedCount: Int { _base.count } + + @inlinable + public func makeIterator() -> SparseSet.Iterator { + _base.makeIterator() + } +} + +extension SparseSet.Elements: RandomAccessCollection { + /// The index type for a sparse set: `Int`. + /// + /// Indices in `Elements` are integer offsets from the start of the + /// collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting the + /// `Elements` collection, in ascending order. + public typealias Indices = Range + + /// The position of the first element in a nonempty sparse set. + /// + /// For an instance of `SparseSet.Elements`, `startIndex` is always zero. If + /// the sparse set is empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// + /// In `SparseSet.Elements`, `endIndex` always equals the count of elements. + /// If the sparse set is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _base.count } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the collection.) + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._dense._keys.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Accesses the element at the specified position. This can be used to + /// perform in-place mutations on sparse set values. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Element { + (_base._dense._keys[position], _base._dense._values[position]) + } + + /// Accesses a contiguous subrange of the sparse set's elements. + /// + /// The returned `Subsequence` instance uses the same indices for the same + /// elements as the original collection. In particular, the slice, unlike an + /// `Elements`, may have a nonzero `startIndex` and an `endIndex` that is not + /// equal to `count`. Always use the slice's `startIndex` and `endIndex` + /// properties instead of assuming that its indices start or end at a + /// particular value. + /// + /// - Parameter bounds: A range of valid indices in the collection. + /// + /// - Complexity: O(1) + public subscript(bounds: Range) -> SubSequence { + _failEarlyRangeCheck(bounds, bounds: startIndex ..< endIndex) + return SubSequence(_base: _base, bounds: bounds) + } + + /// A Boolean value indicating whether the collection is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _base.isEmpty } + + /// The number of elements in the sparse set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _base.count } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: Range) { + _base._dense._keys._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ index: Int, bounds: ClosedRange) { + _base._dense._keys._failEarlyRangeCheck(index, bounds: bounds) + } + + @inlinable + @inline(__always) + public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { + _base._dense._keys._failEarlyRangeCheck(range, bounds: bounds) + } +} + +extension SparseSet.Elements: CustomStringConvertible { + public var description: String { + _base.description + } +} + +extension SparseSet.Elements: CustomDebugStringConvertible { + public var debugDescription: String { + _base._debugDescription( + typeName: "SparseSet<\(Key.self), \(Value.self)>.Elements") + } +} + +extension SparseSet.Elements: CustomReflectable { + public var customMirror: Mirror { + Mirror(self, unlabeledChildren: self, displayStyle: .collection) + } +} + +extension SparseSet.Elements: Equatable where Value: Equatable { + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left._base == right._base + } +} + +extension SparseSet.Elements: Hashable where Value: Hashable { + @inlinable + public func hash(into hasher: inout Hasher) { + _base.hash(into: &hasher) + } +} + +// MARK: Partial `MutableCollection` + +extension SparseSet.Elements { + /// Exchanges the key-value pairs at the specified indices of the sparse set. + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the sparse set's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + @inline(__always) + public mutating func swapAt(_ i: Int, _ j: Int) { + _base.swapAt(i, j) + } + + /// Reorders the elements of the sparse set such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + public mutating func partition( + by belongsInSecondPartition: (Element) throws -> Bool + ) rethrows -> Int { + try _base.partition(by: belongsInSecondPartition) + } +} + +extension SparseSet.Elements { + /// Sorts the collection in place, using the given predicate as the + /// comparison between elements. + /// + /// When you want to sort a collection of elements that don't conform to + /// the `Comparable` protocol, pass a closure to this method that returns + /// `true` when the first element should be ordered before the second. + /// + /// Alternatively, use this method to sort a collection of elements that do + /// conform to `Comparable` when you want the sort to be descending instead + /// of ascending. Pass the greater-than operator (`>`) operator as the + /// predicate. + /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its + /// first argument should be ordered before its second argument; + /// otherwise, `false`. If `areInIncreasingOrder` throws an error during + /// the sort, the elements may be in a different order, but none will be + /// lost. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + @inline(__always) + public mutating func sort( + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + try _base.sort(by: areInIncreasingOrder) + } +} + +extension SparseSet.Elements { + /// Sorts the sparse set in place by comparing the elements' keys. + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare equal. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + @inline(__always) + public mutating func sort() { + _base.sort() + } +} + +extension SparseSet.Elements { + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of an sparse + /// set. + /// + /// This method is equivalent to calling `shuffle(using:)`, passing in the + /// system's default random generator. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + public mutating func shuffle() { + _base.shuffle() + } + + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + /// + /// - Note: The algorithm used to shuffle a collection may change in a future + /// version of Swift. If you're passing a generator that results in the + /// same shuffled order each time you run your program, that sequence may + /// change when your program is compiled using a different version of + /// Swift. + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + _base.shuffle(using: &generator) + } +} + +extension SparseSet.Elements { + /// Reverses the elements of the sparse set in place. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reverse() { + _base.reverse() + } +} + +// MARK: Partial `RangeReplaceableCollection` + +extension SparseSet.Elements { + /// Removes all members from the sparse set. + /// + /// - Parameter keepingCapacity: If `true`, the sparse set's storage capacity + /// is preserved; if `false`, the underlying storage is released. The + /// default is `false`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) { + _base.removeAll(keepingCapacity: keepCapacity) + } + + /// Removes and returns the element at the specified position. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// element is removed the final element is moved to fill the resulting gap. + /// + /// - Parameter index: The position of the element to remove. `index` must be + /// a valid index of the collection that is not equal to the collection's + /// end index. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + _base.remove(at: index) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// subrange is removed the resulting gap is filled or closed by moving the + /// required number of elements (in an order preserving fashion) from the end + /// of the collection. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`bounds.count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + _base.removeSubrange(bounds) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// subrange is removed the resulting gap is filled or closed by moving the + /// required number of elements (in an order preserving fashion) from the end + /// of the collection. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`bounds.count`) + @inlinable + public mutating func removeSubrange( + _ bounds: R + ) where R.Bound == Int { + _base.removeSubrange(bounds) + } + + + /// Removes the last element of a non-empty sparse set. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + _base.removeLast() + } + + /// Removes the last `n` element of the sparse set. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the collection. + /// + /// - Complexity: O(`n`) + @inlinable + public mutating func removeLast(_ n: Int) { + _base.removeLast(n) + } + + /// Removes the first element of a non-empty sparse set. + /// + /// The members following the removed key-value pair need to be moved to close + /// the resulting gaps in the storage arrays. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + _base.removeFirst() + } + + /// Removes the first `n` elements of the sparse set. + /// + /// The members following the removed items need to be moved to close the + /// resulting gaps in the storage arrays. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the set. + /// + /// - Complexity: O(`n`). + @inlinable + public mutating func removeFirst(_ n: Int) { + _base.removeFirst(n) + } + + /// Removes all the elements that satisfy the given predicate. + /// + /// Use this method to remove every element in a collection that meets + /// particular criteria. The order of the remaining elements is preserved. + /// + /// - Parameter shouldBeRemoved: A closure that takes an element of the + /// sparse set as its argument and returns a Boolean value indicating + /// whether the element should be removed from the collection. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll( + where shouldBeRemoved: (Self.Element) throws -> Bool + ) rethrows { + try _base.removeAll(where: shouldBeRemoved) + } +} + diff --git a/Sources/SparseSetModule/SparseSet+Equatable.swift b/Sources/SparseSetModule/SparseSet+Equatable.swift new file mode 100644 index 000000000..7b51687f9 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Equatable.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: Equatable where Value: Equatable { + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Two sparse set are considered equal if they contain the same key-value + /// pairs, in the same order. + /// + /// - Complexity: O(`min(left.count, right.count)`) + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left._dense._keys == right._dense._keys && left._dense._values == right._dense._values + } +} diff --git a/Sources/SparseSetModule/SparseSet+ExpressibleByDictionaryLiteral.swift b/Sources/SparseSetModule/SparseSet+ExpressibleByDictionaryLiteral.swift new file mode 100644 index 000000000..32d17e836 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+ExpressibleByDictionaryLiteral.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: ExpressibleByDictionaryLiteral { + /// Creates a new sparse set from the contents of a dictionary literal. + /// + /// Do not call this initializer directly. It is used by the compiler when + /// you use a dictionary literal. Instead, create a new sparse set using a + /// dictionary literal as its value by enclosing a comma-separated list of + /// key-value pairs in square brackets. You can use a dictionary literal + /// anywhere a sparse set is expected by the type context. + /// + /// - Parameter elements: A variadic list of key-value pairs for the new + /// sparse set. + /// + /// - Complexity: O(`elements.count`). + @inlinable + public init(dictionaryLiteral elements: (Key, Value)...) { + self.init(uniqueKeysWithValues: elements) + } +} diff --git a/Sources/SparseSetModule/SparseSet+Hashable.swift b/Sources/SparseSetModule/SparseSet+Hashable.swift new file mode 100644 index 000000000..f52ef31fc --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Hashable.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet: Hashable where Value: Hashable { + /// Hashes the essential components of this value by feeding them into the + /// given hasher. + /// + /// Complexity: O(`count`) + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // Discriminator + for (key, value) in self { + hasher.combine(key) + hasher.combine(value) + } + } +} diff --git a/Sources/SparseSetModule/SparseSet+Initializers.swift b/Sources/SparseSetModule/SparseSet+Initializers.swift new file mode 100644 index 000000000..10172ac9a --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Initializers.swift @@ -0,0 +1,302 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + @inlinable + public init(minimumCapacity: Int, universeSize: Int? = nil) { + self._dense = DenseStorage(minimumCapacity: minimumCapacity) + let sparse = SparseStorage( + withCapacity: universeSize ?? minimumCapacity, + keys: EmptyCollection()) + self.__sparseBuffer = sparse._buffer + } + + @inlinable + public init() { + self.init(minimumCapacity: 0, universeSize: 0) + } +} + +extension SparseSet { + /// Creates a new sparse set from the key-value pairs in the given sequence. + /// + /// You use this initializer to create a sparse set when you have a sequence + /// of key-value tuples with unique keys. Passing a sequence with duplicate + /// keys to this initializer results in a runtime error. If your sequence + /// might have duplicate keys, use the `SparseSet(_:uniquingKeysWith:)` + /// initializer instead. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for the + /// new sparse set. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new sparse set initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + @inlinable + public init( + uniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { + if S.self == Dictionary.self { + self.init(_uncheckedUniqueKeysWithValues: keysAndValues) + return + } + self.init(minimumCapacity: keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + guard _find(key: key) == nil else { + preconditionFailure("Duplicate key: '\(key)'") + } + _append(value: value, key: key) + } + _checkInvariants() + } + + /// Creates a new sparse set from the key-value pairs in the given sequence. + /// + /// You use this initializer to create a sparse set when you have a sequence + /// of key-value tuples with unique keys. Passing a sequence with duplicate + /// keys to this initializer results in a runtime error. If your + /// sequence might have duplicate keys, use the + /// `SparseSet(_:uniquingKeysWith:)` initializer instead. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new sparse set. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new sparse set initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + @inlinable + public init( + uniqueKeysWithValues keysAndValues: S + ) where S.Element == (Key, Value) { + self.init(minimumCapacity: keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + guard _find(key: key) == nil else { + preconditionFailure("Duplicate key: '\(key)'") + } + _append(value: value, key: key) + } + _checkInvariants() + } +} + +extension SparseSet { + /// Creates a new sparse set from separate sequences of keys and values. + /// + /// You use this initializer to create a sparse set when you have two + /// sequences with unique keys and their associated values, respectively. + /// Passing a `keys` sequence with duplicate keys to this initializer results + /// in a runtime error. + /// + /// - Parameters: + /// - keys: A sequence of unique keys. + /// - values: A sequence of values associated with items in `keys`. + /// + /// - Returns: A new sparse set initialized with the data in `keys` and + /// `values`. + /// + /// - Precondition: The sequence must not have duplicate keys, and `keys` and + /// `values` must contain an equal number of elements. + @inlinable + public init( + uniqueKeys keys: Keys, + values: Values + ) where Keys.Element == Key, Values.Element == Value { + let keys = ContiguousArray(keys) + let values = ContiguousArray(values) + precondition(keys.count == values.count, + "Mismatching element counts between keys and values") + self._dense = DenseStorage(_keys: keys, _values: values) + let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 + var sparse = SparseStorage(withCapacity: universeSize) + for (i, key) in keys.enumerated() { + let existingIndex = sparse[key] + precondition(existingIndex < 0 || existingIndex >= i || keys[existingIndex] != key, "Duplicate key: '\(key)'") + sparse[key] = i + } + __sparseBuffer = sparse._buffer + _checkInvariants() + } +} + +extension SparseSet { + /// Creates a new sparse set 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 sparse set when you have a sequence + /// of key-value tuples that might have duplicate keys. As the sparse set is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// returns the value to use in the resulting sparse set: The closure can + /// choose between the two values, combine them to produce a new value, or + /// even throw an error. + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs to use for the new + /// sparse set. + /// - 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 sparse set. + @inlinable + @inline(__always) + public init( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (key: Key, value: Value) { + self.init() + try self.merge(keysAndValues, uniquingKeysWith: combine) + } + + /// Creates a new sparse set 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 sparse set when you have a sequence + /// of key-value tuples that might have duplicate keys. As the sparse set is + /// built, the initializer calls the `combine` closure with the current and + /// new values for any duplicate keys. Pass a closure as `combine` that + /// returns the value to use in the resulting sparse set: The closure can + /// choose between the two values, combine them to produce a new value, or + /// even throw an error. + /// + /// - Parameters: + /// - keysAndValues: A sequence of key-value pairs to use for the new + /// sparse set. + /// - 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 sparse set. + @inlinable + @inline(__always) + public init( + _ keysAndValues: S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + self.init() + try self.merge(keysAndValues, uniquingKeysWith: combine) + } +} + +extension SparseSet { + @inlinable + internal init( + _uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { + self.init(minimumCapacity: keysAndValues.underestimatedCount) + for (key, value) in keysAndValues { + _append(value: value, key: key) + } + _checkInvariants() + } + + /// Creates a new sparse set from the key-value pairs in the given sequence, + /// which must not contain duplicate keys. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the sparse set somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt sparse set value that may be + /// difficult to debug. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new sparse set. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new sparse set initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + @inlinable + public init( + uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (key: Key, value: Value) { +#if DEBUG + self.init(uniqueKeysWithValues: keysAndValues) +#else + self.init(_uncheckedUniqueKeysWithValues: keysAndValues) +#endif + } + + /// Creates a new sparse set from the key-value pairs in the given sequence, + /// which must not contain duplicate keys. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the sparse set somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt sparse set value that may be + /// difficult to debug. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use for + /// the new sparse set. Every key in `keysAndValues` must be unique. + /// + /// - Returns: A new sparse set initialized with the elements of + /// `keysAndValues`. + /// + /// - Precondition: The sequence must not have duplicate keys. + @inlinable + public init( + uncheckedUniqueKeysWithValues keysAndValues: S + ) where S.Element == (Key, Value) { + // Add tuple labels + let keysAndValues = keysAndValues.lazy.map { (key: $0.0, value: $0.1) } + self.init(uncheckedUniqueKeysWithValues: keysAndValues) + } +} + +extension SparseSet { + @inlinable + internal init( + _uncheckedUniqueKeys keys: Keys, + values: Values + ) where Keys.Element == Key, Values.Element == Value { + let keys = ContiguousArray(keys) + let values = ContiguousArray(values) + self._dense = DenseStorage(_keys: keys, _values: values) + let sparse = SparseStorage(keys: keys) + __sparseBuffer = sparse._buffer + _checkInvariants() + } + + /// Creates a new sparse set from separate sequences of unique keys and + /// associated values. + /// + /// In optimized builds, this initializer does not verify that the keys are + /// actually unique. This makes creating the sparse set somewhat faster if you + /// know for sure that the elements are unique (e.g., because they come from + /// another collection with guaranteed-unique members, such as a + /// `Dictionary`). However, if you accidentally call this initializer with + /// duplicate members, it can return a corrupt sparse set value that may be + /// difficult to debug. + /// + /// - Parameters: + /// - keys: A sequence of unique keys. + /// - values: A sequence of values associated with items in `keys`. + /// + /// - Returns: A new sparse set initialized with the data in + /// `keys` and `values`. + /// + /// - Precondition: The sequence must not have duplicate keys, and `keys` and + /// `values` must contain an equal number of elements. + @inlinable + @inline(__always) + public init( + uncheckedUniqueKeys keys: Keys, + values: Values + ) where Keys.Element == Key, Values.Element == Value { +#if DEBUG + self.init(uniqueKeys: keys, values: values) +#else + self.init(_uncheckedUniqueKeys: keys, values: values) +#endif + } +} diff --git a/Sources/SparseSetModule/SparseSet+Invariants.swift b/Sources/SparseSetModule/SparseSet+Invariants.swift new file mode 100644 index 000000000..1d328e0f3 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Invariants.swift @@ -0,0 +1,32 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + #if COLLECTIONS_INTERNAL_CHECKS + @inlinable + @inline(never) + internal func _checkInvariants() { + // Check there are the same number of keys as values. + precondition(_dense._keys.count == _dense._values.count) + // Check that the sparse storage buffer has sufficient capacity. + let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 + precondition(_sparse.capacity >= universeSize) + // Check that the keys' positions in the dense storage agree with those + // given by the sparse storage. + for (i, key) in _dense._keys.enumerated() { + precondition(_sparse[key] == i) + } + } + #else + @inline(__always) @inlinable + public func _checkInvariants() {} + #endif // COLLECTIONS_INTERNAL_CHECKS +} diff --git a/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift b/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift new file mode 100644 index 000000000..1608fa2bb --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift @@ -0,0 +1,215 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + /// Exchanges the key-value pairs at the specified indices of the sparse set. + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the sparse set's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + public mutating func swapAt(_ i: Int, _ j: Int) { + _swapAt(i, j) + } + + /// Reorders the elements of the sparse set such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func partition( + by belongsInSecondPartition: (Element) throws -> Bool + ) rethrows -> Int { + _ensureUnique() + + var low = _dense._keys.startIndex + var high = _dense._keys.endIndex + + while true { + // Invariants at this point: + // - low <= high + // - all elements in `startIndex ..< low` belong in the first partition + // - all elements in `high ..< endIndex` belong in the second partition + + // Find next element from `lo` that may not be in the right place. + while true { + if low == high { return low } + if try belongsInSecondPartition((_dense._keys[low], _dense._values[low])) { break } + low += 1 + } + + // Find next element down from `hi` that we can swap `lo` with. + while true { + high -= 1 + if low == high { return low } + if try !belongsInSecondPartition((_dense._keys[high], _dense._values[high])) { break } + } + + // Swap the two elements. + _swapAt(low, high) + + low += 1 + } + } +} + +extension SparseSet { + /// Sorts the collection in place, using the given predicate as the + /// comparison between elements. + /// + /// When you want to sort a collection of elements that don't conform to + /// the `Comparable` protocol, pass a closure to this method that returns + /// `true` when the first element should be ordered before the second. + /// + /// Alternatively, use this method to sort a collection of elements that do + /// conform to `Comparable` when you want the sort to be descending instead + /// of ascending. Pass the greater-than operator (`>`) operator as the + /// predicate. + /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// + /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its + /// first argument should be ordered before its second argument; + /// otherwise, `false`. If `areInIncreasingOrder` throws an error during + /// the sort, the elements may be in a different order, but none will be + /// lost. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort( + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + // FIXME: Implement in-place sorting. + _ensureUnique() + let temp = try self.sorted(by: areInIncreasingOrder) + precondition(temp.count == self.count) + temp.withUnsafeBufferPointer { source in + _dense._keys = ContiguousArray(source.lazy.map { $0.key }) + _dense._values = ContiguousArray(source.lazy.map { $0.value }) + } + _sparse.reindex(keys: _dense._keys) + } +} + +extension SparseSet { + /// Sorts the sparse set in place by comparing the elements' keys. + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare equal. + /// + /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. + @inlinable + public mutating func sort() { + sort { $0.key < $1.key } + } +} + +extension SparseSet { + /// Shuffles the collection in place. + /// + /// Use the `shuffle()` method to randomly reorder the elements of a sparse + /// set. + /// + /// This method is equivalent to calling `shuffle(using:)`, passing in the + /// system's default random generator. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + @inlinable + public mutating func shuffle() { + var generator = SystemRandomNumberGenerator() + shuffle(using: &generator) + } + + /// Shuffles the collection in place, using the given generator as a source + /// for randomness. + /// + /// You use this method to randomize the elements of a collection when you + /// are using a custom random number generator. For example, you can use the + /// `shuffle(using:)` method to randomly reorder the elements of an array. + /// + /// - Parameter generator: The random number generator to use when shuffling + /// the collection. + /// + /// - Complexity: O(*n*), where *n* is the length of the collection. + /// + /// - Note: The algorithm used to shuffle a collection may change in a future + /// version of Swift. If you're passing a generator that results in the + /// same shuffled order each time you run your program, that sequence may + /// change when your program is compiled using a different version of + /// Swift. + @inlinable + public mutating func shuffle( + using generator: inout T + ) { + guard count > 1 else { return } + _ensureUnique() + var keys = _dense._keys + var values = _dense._values + self = [:] + var amount = keys.count + var current = 0 + while amount > 1 { + let random = Int.random(in: 0 ..< amount, using: &generator) + amount -= 1 + keys.swapAt(current, current + random) + values.swapAt(current, current + random) + current += 1 + } + self = SparseSet(uncheckedUniqueKeys: keys, values: values) + } +} + +extension SparseSet { + /// Reverses the elements of the sparse set in place. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func reverse() { + _ensureUnique() + _dense._keys.reverse() + _dense._values.reverse() + _sparse.reindex(keys: _dense._keys) + } +} diff --git a/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift b/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift new file mode 100644 index 000000000..f6bc51770 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift @@ -0,0 +1,189 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + /// Removes all keys and their associated values from the sparse set. + /// + /// - Parameter keepingCapacity: If `true` then the underlying storage's + /// capacity is preserved. The default is `false`. + /// + /// - Complexity: O(`count`) + @inlinable + public mutating func removeAll(keepingCapacity: Bool = false) { + _dense.removeAll(keepingCapacity: keepingCapacity) + if !keepingCapacity { + _sparse = SparseStorage(withCapacity: 0) + } + _checkInvariants() + } + + /// Removes and returns the element at the specified position. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// element is removed the final element is moved to fill the resulting gap. + /// + /// - Parameter index: The position of the element to remove. `index` must be + /// a valid index of the collection that is not equal to the collection's + /// end index. + /// + /// - Returns: The removed element. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func remove(at index: Int) -> Element { + let existing = _remove(at: index) + return existing + } + + /// Removes the specified subrange of elements from the collection. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// subrange is removed the resulting gap is filled or closed by moving the + /// required number of elements (in an order preserving fashion) from the end + /// of the collection. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`bounds.count`) + @inlinable + public mutating func removeSubrange(_ bounds: Range) { + guard !bounds.isEmpty else { return } + defer { _checkInvariants() } + let finalSegment = bounds.endIndex ..< _dense._keys.endIndex + let regionToMove: Range? + if bounds.count <= finalSegment.count { + regionToMove = finalSegment.endIndex - bounds.count ..< finalSegment.endIndex + } else if !finalSegment.isEmpty { + regionToMove = finalSegment + } else { + regionToMove = nil + } + if let regionToMove = regionToMove { + _ensureUnique() + _dense._keys.withUnsafeMutableBufferPointer { ptr in + ptr.baseAddress!.advanced(by: bounds.startIndex) + .assign(from: ptr.baseAddress!.advanced(by: regionToMove.startIndex), + count: regionToMove.count) + } + _dense._values.withUnsafeMutableBufferPointer { ptr in + ptr.baseAddress!.advanced(by: bounds.startIndex) + .assign(from: ptr.baseAddress!.advanced(by: regionToMove.startIndex), + count: regionToMove.count) + } + for (i, key) in _dense._keys[regionToMove].enumerated() { + _sparse[key] = bounds.startIndex + i + } + } + _dense._keys.removeLast(bounds.count) + _dense._values.removeLast(bounds.count) + } + + /// Removes the specified subrange of elements from the collection. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// subrange is removed the resulting gap is filled or closed by moving the + /// required number of elements (in an order preserving fashion) from the end + /// of the collection. + /// + /// - Parameter bounds: The subrange of the collection to remove. The bounds + /// of the range must be valid indices of the collection. + /// + /// - Complexity: O(`bounds.count`) + @inlinable + public mutating func removeSubrange( + _ bounds: R + ) where R.Bound == Int { + removeSubrange(bounds.relative(to: _dense._keys)) + } + + /// Removes the last element of a non-empty sparse set. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeLast() -> Element { + precondition(!isEmpty, "Cannot remove last element of an empty collection") + return remove(at: count - 1) + } + + /// Removes the last `n` element of the sparse set. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the collection. + /// + /// - Complexity: O(`n`) + @inlinable + public mutating func removeLast(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + _dense._keys.removeLast(n) + _dense._values.removeLast(n) + _checkInvariants() + } + + /// Removes the first element of a non-empty sparse set. + /// + /// Calling this method will invalidate existing indices - the final element + /// will be moved to fill the gap left by removing the first element. + /// + /// - Complexity: O(1) + @inlinable + @discardableResult + public mutating func removeFirst() -> Element { + precondition(!isEmpty, "Cannot remove first element of an empty collection") + return remove(at: 0) + } + + /// Removes the first `n` elements of the sparse set. + /// + /// Calling this method will invalidate existing indices. The gap created by + /// removing initial elements is filled or closed by moving the required + /// number of elements (in an order preserving fashion) from the end of the + /// collection. + /// + /// - Parameter n: The number of elements to remove from the collection. + /// `n` must be greater than or equal to zero and must not exceed the + /// number of elements in the set. + /// + /// - Complexity: O(`n`) + @inlinable + public mutating func removeFirst(_ n: Int) { + precondition(n >= 0, "Can't remove a negative number of elements") + precondition(n <= count, "Can't remove more elements than there are in the collection") + removeSubrange(0.. Bool + ) rethrows { + guard !isEmpty else { return } + for i in (0 ..< count).reversed() { + let element = (key: _dense._keys[i], value: _dense._values[i]) + if try shouldBeRemoved(element) { + _remove(at: i) + } + } + } +} diff --git a/Sources/SparseSetModule/SparseSet+Sequence.swift b/Sources/SparseSetModule/SparseSet+Sequence.swift new file mode 100644 index 000000000..8cfa87c2a --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Sequence.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import Foundation + +extension SparseSet: Sequence { + /// The element type of a sparse set: a tuple containing an individual + /// key-value pair. + public typealias Element = (key: Key, value: Value) + + /// The type that allows iteration over a sparse set's elements. + @frozen + public struct Iterator: IteratorProtocol { + @usableFromInline + internal let _base: SparseSet + + @usableFromInline + internal var _position: Int + + @inlinable + @inline(__always) + internal init(_base: SparseSet) { + self._base = _base + self._position = 0 + } + + /// Advances to the next element and returns it, or nil if no next element + /// exists. + /// + /// - Complexity: O(1) + @inlinable + public mutating func next() -> Element? { + guard _position < _base._dense.count else { return nil } + let result = (_base._dense._keys[_position], _base._dense._values[_position]) + _position += 1 + return result + } + } + + /// The number of elements in the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline (__always) + public var underestimatedCount: Int { + count + } + + /// Returns an iterator over the elements of this collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func makeIterator() -> Iterator { + Iterator(_base: self) + } +} diff --git a/Sources/SparseSetModule/SparseSet+SparseStorage.swift b/Sources/SparseSetModule/SparseSet+SparseStorage.swift new file mode 100644 index 000000000..e1bcd92b5 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+SparseStorage.swift @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + @usableFromInline + internal struct SparseStorage { + @usableFromInline + var _buffer: Buffer + + @inlinable + @inline(__always) + init(_ buffer: Buffer) { + self._buffer = buffer + } + + @inlinable + internal init(withCapacity capacity: Int, keys: C) where C.Element == Key { + self._buffer = Buffer.bufferWith(capacity: capacity, keys: keys) + } + + @inlinable + internal init(withCapacity capacity: Int) { + self._buffer = Buffer.bufferWith(capacity: capacity, keys: EmptyCollection()) + } + + @inlinable + internal init(keys: C) where C.Element == Key { + let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 + self._buffer = Buffer.bufferWith(capacity: universeSize, keys: keys) + } + } +} + +extension SparseSet.SparseStorage { + @usableFromInline + internal struct Header { + @usableFromInline + internal var capacity: Int + } +} + +extension SparseSet.SparseStorage { + @usableFromInline + internal final class Buffer: ManagedBuffer { + /// Create a buffer populated with the given key data. + /// + /// - Parameters: + /// - capacity: The capacity of the new buffer. + /// - keys: A collection of keys. + /// + /// - Returns: A new buffer. + @usableFromInline + internal static func bufferWith(capacity: Int, keys: C) -> Buffer where C.Element == Key { + assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) + let newBuffer = Buffer.create( + minimumCapacity: capacity, + makingHeaderWith: { _ in + Header(capacity: capacity) + }) + newBuffer.withUnsafeMutablePointerToElements { ptr in + for (i, key) in keys.enumerated() { + let index = Int(key) + precondition(index >= 0, "Negative key") + precondition(index < capacity, "Insufficient capacity") + ptr[index] = Key(i) + } + } + return unsafeDowncast(newBuffer, to: Buffer.self) + } + + /// Create a new buffer populated with the same data as the given buffer. + /// + /// - Parameter buffer: The buffer to clone. + /// + /// - Returns: A new buffer. + @usableFromInline + internal static func bufferWith(contentsOf buffer: Buffer) -> Buffer { + return Buffer.bufferWith(capacity: buffer.capacity, contentsOf: buffer) + } + + /// Create a new buffer populated with data from the given buffer. + /// + /// The new capacity may be smaller than the capacity of the buffer + /// providing the data, in which case not all the data will be copied. + /// + /// - Parameters: + /// - capacity: The capacity of the new buffer. + /// - buffer: The data to populate the new buffer with. + /// + /// - Returns: A new buffer. + @usableFromInline + internal static func bufferWith(capacity: Int, contentsOf buffer: Buffer) -> Buffer { + let newBuffer = Buffer.create( + minimumCapacity: capacity, + makingHeaderWith: { _ in + Header(capacity: capacity) + }) + newBuffer.withUnsafeMutablePointerToElements { targetPtr in + buffer.withUnsafeMutablePointerToElements { sourcePtr in + targetPtr.moveAssign(from: sourcePtr, count: Swift.min(capacity, buffer.capacity)) + } + } + return unsafeDowncast(newBuffer, to: Buffer.self) + } + } +} + +extension SparseSet.SparseStorage { + @inlinable + internal var capacity: Int { + _buffer.header.capacity + } + + /// Resize this index table to a new capacity. + /// + /// The underlying buffer is replaced with a new one and its contents are + /// copied. + /// + /// - Parameter newCapacity: The new capacity of the storage. + @usableFromInline + internal mutating func resize(to newCapacity: Int) { + _buffer = Buffer.bufferWith(capacity: newCapacity, contentsOf: _buffer) + } + + /// Resize this index table to a new capacity. + /// + /// The underlying buffer is replaced with a new one. The contents of the + /// new buffer is initialized from the provided `keys` collection. This + /// may be faster than `resize(to:)` when the density of the set is low (the + /// number of members is small relative to the capacity). + /// + /// - Parameters: + /// - newCapacity: The new capacity of the storage. + /// - keys: A collection of keys. + @usableFromInline + internal mutating func resize(to newCapacity: Int, keys: C) where C.Element == Key { + _buffer = Buffer.bufferWith(capacity: newCapacity, keys: keys) + } + + /// Rebuilds the index data for the given key data. + /// + /// - Parameter indices: A collection of keys. + @usableFromInline + internal mutating func reindex(keys: C) where C.Element == Key { + assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) + _buffer.withUnsafeMutablePointerToElements { ptr in + for(i, key) in keys.enumerated() { + let index = Int(key) + precondition(index >= 0, "Negative key") + precondition(index < capacity, "Insufficient capacity") + ptr[index] = Key(i) + } + } + } +} + +extension SparseSet.SparseStorage { + @inlinable + internal subscript(position: Key) -> Int { + get { + _buffer.withUnsafeMutablePointerToElements { ptr in + Int(ptr[Int(position)]) + } + } + set { + _buffer.withUnsafeMutablePointerToElements { ptr in + ptr[Int(position)] = Key(newValue) + } + } + } +} diff --git a/Sources/SparseSetModule/SparseSet+Values.swift b/Sources/SparseSetModule/SparseSet+Values.swift new file mode 100644 index 000000000..e14dfb7ce --- /dev/null +++ b/Sources/SparseSetModule/SparseSet+Values.swift @@ -0,0 +1,380 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +extension SparseSet { + /// A view of a sparse set's values as a standalone collection. + public struct Values { + @usableFromInline + internal var _base: SparseSet + + @inlinable + @inline(__always) + internal init(_base: SparseSet) { + self._base = _base + } + } +} + + +extension SparseSet.Values { + /// A read-only view of the contents of this collection as an array value. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var elements: Array { + Array(_base._dense._values) + } +} + +extension SparseSet.Values { + /// Calls a closure with a pointer to the collection's contiguous storage. + /// + /// Often, the optimizer can eliminate bounds checks within a collection + /// algorithm, but when that fails, invoking the same algorithm on the + /// buffer pointer passed into your closure lets you trade safety for speed. + /// + /// The pointer passed as an argument to `body` is valid only during the + /// execution of `withUnsafeBufferPointer(_:)`. Do not store or return the + /// pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter that + /// points to the contiguous storage for the collection. If `body` has a + /// return value, that value is also used as the return value for the + /// `withUnsafeBufferPointer(_:)` method. The pointer argument is valid only + /// for the duration of the method's execution. + /// + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// - Complexity: O(1) (not counting the closure call) + @inlinable + @inline(__always) + public func withUnsafeBufferPointer( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R { + try _base._dense._values.withUnsafeBufferPointer(body) + } + + /// Calls the given closure with a pointer to the collection's mutable + /// contiguous storage. + /// + /// Often, the optimizer can eliminate bounds checks within a collection + /// algorithm, but when that fails, invoking the same algorithm on the buffer + /// pointer passed into your closure lets you trade safety for speed. + /// + /// The pointer passed as an argument to `body` is valid only during the + /// execution of `withUnsafeMutableBufferPointer(_:)`. Do not store or return + /// the pointer for later use. + /// + /// - Parameter body: A closure with an `UnsafeMutableBufferPointer` parameter + /// that points to the contiguous storage for the collection. If `body` has + /// a return value, that value is also used as the return value for the + /// `withUnsafeMutableBufferPointer(_:)` method. The pointer argument is + /// valid only for the duration of the method's execution. + /// + /// - Returns: The return value, if any, of the `body` closure parameter. + /// + /// - Complexity: O(1) (not counting the closure call) + @inlinable + @inline(__always) + public mutating func withUnsafeMutableBufferPointer( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R { + try _base._dense._values.withUnsafeMutableBufferPointer(body) + } +} + +extension SparseSet.Values: Sequence { + /// The element type of the collection. + public typealias Element = Value + + /// The type that allows iteration over the collection's elements. + public typealias Iterator = IndexingIterator +} + +extension SparseSet.Values: RandomAccessCollection { + /// The index type for a sparse set's values view, `Int`. + /// + /// Indices in `Values` are integer offsets from the start of the collection. + public typealias Index = Int + + /// The type that represents the indices that are valid for subscripting the + /// `Values` collection, in ascending order. + public typealias Indices = Range + + /// The position of the first element in a nonempty sparse set. + /// + /// For an instance of `SparseSet.Values`, `startIndex` is always zero. If the + /// sparse set is empty, `startIndex` is equal to `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var startIndex: Int { 0 } + + /// The collection's "past the end" position---that is, the position one + /// greater than the last valid subscript argument. + /// + /// In `SparseSet.Values`, `endIndex` always equals the count of elements. If + /// the sparse set is empty, `endIndex` is equal to `startIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var endIndex: Int { _base._dense.count } + + /// Returns the position immediately after the given index. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately after `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(after i: Int) -> Int { i + 1 } + + /// Returns the position immediately before the given index. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Returns: The index immediately before `i`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(before i: Int) -> Int { i - 1 } + + /// Replaces the given index with its successor. + /// + /// The specified index must be a valid index less than `endIndex`, or the + /// returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(after i: inout Int) { i += 1 } + + /// Replaces the given index with its predecessor. + /// + /// The specified index must be a valid index greater than `startIndex`, or + /// the returned value won't be a valid index in the collection. + /// + /// - Parameter i: A valid index of the collection. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func formIndex(before i: inout Int) { i -= 1 } + + /// Returns an index that is the specified distance from the given index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, or the returned value will not be a valid index. + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// + /// - Returns: An index offset by `distance` from the index `i`. If `distance` + /// is positive, this is the same value as the result of `distance` calls to + /// `index(after:)`. If `distance` is negative, this is the same value as + /// the result of `abs(distance)` calls to `index(before:)`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(_ i: Int, offsetBy distance: Int) -> Int { + i + distance + } + + /// Returns an index that is the specified distance from the given index, + /// unless that distance is beyond a given limiting index. + /// + /// The value passed as `distance` must not offset `i` beyond the bounds of + /// the collection, unless the index passed as `limit` prevents offsetting + /// beyond those bounds. (Otherwise the returned value won't be a valid index + /// in the collection.) + /// + /// - Parameters: + /// - i: A valid index of the collection. + /// - distance: The distance to offset `i`. + /// - limit: A valid index of the collection to use as a limit. If + /// `distance > 0`, `limit` has no effect if it is less than `i`. + /// Likewise, if `distance < 0`, `limit` has no effect if it is greater + /// than `i`. + /// - Returns: An index offset by `distance` from the index `i`, unless that + /// index would be beyond `limit` in the direction of movement. In that + /// case, the method returns `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index( + _ i: Int, + offsetBy distance: Int, + limitedBy limit: Int + ) -> Int? { + _base._dense._values.index(i, offsetBy: distance, limitedBy: limit) + } + + /// Returns the distance between two indices. + /// + /// - Parameters: + /// - start: A valid index of the collection. + /// - end: Another valid index of the collection. If `end` is equal to + /// `start`, the result is zero. + /// + /// - Returns: The distance between `start` and `end`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func distance(from start: Int, to end: Int) -> Int { + end - start + } + + /// Call `body(p)`, where `p` is a buffer pointer to the collection’s + /// contiguous storage. `SparseSet.Values` values always have contiguous + /// storage. + /// + /// - Parameter body: A function to call. The function must not escape its + /// unsafe buffer pointer argument. + /// + /// - Returns: The value returned by `body`. + /// + /// - Complexity: O(1) (ignoring time spent in `body`) + @inlinable + @inline(__always) + public func withContiguousStorageIfAvailable( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R? { + try _base._dense._values.withUnsafeBufferPointer(body) + } +} + +extension SparseSet.Values: MutableCollection { + /// Accesses the element at the specified position. This can be used to + /// perform in-place mutations on sparse set values. + /// + /// - Parameter index: The position of the element to access. `index` must be + /// greater than or equal to `startIndex` and less than `endIndex`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(position: Int) -> Value { + get { + _base._dense._values[position] + } + _modify { + yield &_base._dense._values[position] + } + } + + /// Exchanges the values at the specified indices of the collection. (Leaving + /// their associated keys in the underlying sparse set at their original + /// position.) + /// + /// Both parameters must be valid indices below `endIndex`. Passing the same + /// index as both `i` and `j` has no effect. + /// + /// - Parameters: + /// - i: The index of the first value to swap. + /// - j: The index of the second value to swap. + /// + /// - Complexity: O(1) when the sparse set's storage isn't shared with another + /// value; O(`count`) otherwise. + @inlinable + @inline(__always) + public mutating func swapAt(_ i: Int, _ j: Int) { + _base._dense._values.swapAt(i, j) + } + + /// Reorders the elements of the collection such that all the elements that + /// match the given predicate are after all the elements that don't match. + /// + /// This operation does not reorder the keys of the underlying sparse set, + /// just their associated values. + /// + /// After partitioning a collection, there is a pivot index `p` where + /// no element before `p` satisfies the `belongsInSecondPartition` + /// predicate and every element at or after `p` satisfies + /// `belongsInSecondPartition`. + /// + /// - Parameter belongsInSecondPartition: A predicate used to partition + /// the collection. All elements satisfying this predicate are ordered + /// after all elements not satisfying it. + /// + /// - Returns: The index of the first element in the reordered collection + /// that matches `belongsInSecondPartition`. If no elements in the + /// collection match `belongsInSecondPartition`, the returned index is + /// equal to the collection's `endIndex`. + /// + /// - Complexity: O(`count`) + @inlinable + @inline(__always) + public mutating func partition( + by belongsInSecondPartition: (Value) throws -> Bool + ) rethrows -> Int { + try _base._dense._values.partition(by: belongsInSecondPartition) + } + + /// Call `body(b)`, where `b` is an unsafe buffer pointer to the collection's + /// mutable contiguous storage. `SparseSet.Values` always stores its + /// elements in contiguous storage. + /// + /// The supplied buffer pointer is only valid for the duration of the call. + /// + /// Often, the optimizer can eliminate bounds- and uniqueness-checks within an + /// algorithm, but when that fails, invoking the same algorithm on the unsafe + /// buffer supplied to `body` lets you trade safety for speed. + /// + /// - Parameter body: The function to invoke. + /// + /// - Returns: The value returned by `body`, or `nil` if `body` wasn't called. + /// + /// - Complexity: O(1) when this instance has a unique reference to its + /// underlying storage; O(`count`) otherwise. (Not counting the call to + /// `body`.) + @inlinable + @inline(__always) + public mutating func withContiguousMutableStorageIfAvailable( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + try _base._dense._values.withUnsafeMutableBufferPointer(body) + } +} + +extension SparseSet.Values: Equatable where Value: Equatable { + @inlinable + public static func ==(left: Self, right: Self) -> Bool { + left.elementsEqual(right) + } +} + +extension SparseSet.Values: Hashable where Value: Hashable { + @inlinable + public func hash(into hasher: inout Hasher) { + hasher.combine(count) // Discriminator + for item in self { + hasher.combine(item) + } + } +} diff --git a/Sources/SparseSetModule/SparseSet.swift b/Sources/SparseSetModule/SparseSet.swift new file mode 100644 index 000000000..b02da5687 --- /dev/null +++ b/Sources/SparseSetModule/SparseSet.swift @@ -0,0 +1,642 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +public struct SparseSet where Key: FixedWidthInteger, Key.Stride == Int { + @usableFromInline + internal var _dense: DenseStorage + + @inlinable + @inline(__always) + internal var _sparse: SparseStorage { + get { + SparseStorage(__sparseBuffer) + } + set { + __sparseBuffer = newValue._buffer + } + } + + @usableFromInline + internal var __sparseBuffer: SparseStorage.Buffer +} + +// MARK: - + +extension SparseSet { + /// A read-only collection view for the keys contained in this sparse set, as + /// a `ContiguousArray`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var keys: ContiguousArray { + _dense._keys + } + + /// A mutable collection view containing the values in this sparse set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var values: Values { + get { + Values(_base: self) + } + _modify { + var values = Values(_base: self) + self = SparseSet() + defer { self = values._base } + yield &values + } + } +} + +// MARK: - + +extension SparseSet { + /// The size of the key universe. The sparse set has enough space allocated to + /// store all (non-negative) keys less than this value. Adding a key to the + /// sparse set that is greater than or equal to the universe size will result + /// in extra memory allocation. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var universeSize: Int { _sparse.capacity } + + /// A Boolean value indicating whether the sparse set is empty. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var isEmpty: Bool { _dense.isEmpty } + + /// The number of elements in the sparse set. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public var count: Int { _dense.count } + + /// Returns the index for the given key. + /// + /// If the given key is found in the sparse set, this method returns an + /// index into the sparse set that corresponds with the key-value pair. + /// + /// - Parameter key: The key to find in the sparse set. + /// + /// - Returns: The index for `key` and its associated value if `key` is in + /// the sparse set; otherwise, `nil`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public func index(forKey key: Key) -> Int? { + return _find(key: key) + } + + /// Accesses the element at the specified index. + /// + /// - Parameter offset: The offset of the element to access, measured from + /// the start of the collection. `offset` must be greater than or equal to + /// `0` and less than `count`. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) + public subscript(offset offset: Int) -> Element { + (_dense._keys[offset], _dense._values[offset]) + } +} + +// MARK: - + +extension SparseSet { + /// Inserts a new value and its associated key into the sparse set. If the key + /// already exists in the sparse set then its currently associated value is + /// replaced by the new value. + /// + /// - Parameters: + /// - newValue: The value to insert. + /// - key: The key associated with the value. + /// + /// - Returns: If the key already exists in the sparse set then its associated + /// value is replaced with `newValue` and the old value is returned, + /// otherwise `nil` is returned. + /// + /// - Complexity: Amortized O(1). + @discardableResult + @inlinable + public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { + if let existingIndex = _find(key: key) { + let existingValue = _dense._values[existingIndex] + _dense._values[existingIndex] = value + return existingValue + } + _append(value: value, key: key) + return nil + } + + /// Ensures that the specified key exists in the sparse set (by appending one + /// with the supplied default value if necessary), then calls `body` to update + /// it in place. + /// + /// You can use this method to perform in-place operations on values in the + /// sparse set, whether or not `Value` has value semantics. + /// + /// - Parameters: + /// - key: The key to look up (or append). If `key` does not already exist + /// in the sparse set, it is appended with the supplied default value. + /// - defaultValue: The default value to append if `key` doesn't exist in + /// the sparse set. + /// - body: A function that performs an in-place mutation on the sparse set + /// value. + /// + /// - Returns: The return value of `body`. + /// + /// - Complexity: `O(1)`. + @inlinable + public mutating func modifyValue( + forKey key: Key, + default defaultValue: @autoclosure () -> Value, + _ body: (inout Value) throws -> R + ) rethrows -> R { + if let existingIndex = _find(key: key) { + return try body(&_dense._values[existingIndex]) + } + _append(value: defaultValue(), key: key) + let i = _dense.count - 1 + return try body(&_dense._values[i]) + } + + /// Removes the given key and its associated value from the sparse set. + /// + /// Calling this method will invalidate existing indices. When a non-final + /// element is removed the final element is moved to fill the resulting gap. + /// + /// - Parameter key: The key to remove. + /// + /// - Returns: If the key is contained in the sparse set then its associated + /// value is returned, otherwise `nil`. + /// + /// - Complexity: O(1). + @discardableResult + @inlinable + public mutating func removeValue(forKey key: Key) -> Value? { + guard let existingIndex = _find(key: key) else { + return nil + } + let existing = _remove(at: existingIndex) + return existing.value + } +} + +// MARK: - + +extension SparseSet { + /// Accesses the value associated with the given key for reading and writing. + /// + /// This *key-based* subscript returns the value for the given key if the key + /// is found in the sparse set, or `nil` if the key is not found. + /// + /// When you assign a value for a key and that key already exists, the + /// sparse set overwrites the existing value. If the sparse set doesn't + /// contain the key, the key and value are added as a new key-value pair. + /// + /// If you assign `nil` as the value for the given key, the sparse set + /// removes that key and its associated value. + /// + /// - Parameter key: The key to find in the sparse set. + /// + /// - Returns: The value associated with `key` if `key` is in the sparse set; + /// otherwise, `nil`. + /// + /// - Complexity: Looking up values in the sparse set through this subscript + /// has O(1) complexity. Updating the sparse set also has an amortized + /// expected complexity of O(1) -- although individual updates may need to + /// copy or resize the sparse set's underlying storage. + @inlinable + public subscript(key: Key) -> Value? { + get { + guard let index = _find(key: key) else { return nil } + return _dense._values[index] + } + set { + // We have a separate `set` in addition to `_modify` in hopes of getting + // rid of `_modify`'s swapAt dance in the usual case where the caller just + // wants to assign a new value. + let index = _find(key: key) + switch (index, newValue) { + case let (index?, newValue?): // Assign + _dense._values[index] = newValue + case let (index?, nil): // Remove + _remove(at: index) + case let (nil, newValue?): // Insert + _append(value: newValue, key: key) + case (nil, nil): // Noop + break + } + } + _modify { + let index = _find(key: key) + + // To support in-place mutations better, we swap the value to the end of + // the array and pop it off. Later we either put things back in place, + // or swap keys too depending on whether we are are assigning or removing. + var value: Value? = nil + if let index = index { + _dense._values.swapAt(index, _dense._values.count - 1) + value = _dense._values.removeLast() + } + + defer { + switch (index, value) { + case let (index?, value?): // Assign + _dense._values.append(value) + _dense._values.swapAt(index, _dense._values.count - 1) + case let (index?, nil): // Remove + _ensureUnique() + if index < _dense._values.count { + let shiftedKey = _dense._keys.removeLast() + _dense._keys[index] = shiftedKey + _sparse[shiftedKey] = index + } else { + _dense._keys.removeLast() + } + case let (nil, value?): // Insert + _append(value: value, key: key) + case (nil, nil): // Noop + break + } + } + + yield &value + } + } + + /// Accesses the value with the given key. If the sparse set doesn't contain + /// the given key, accesses the provided default value as if the key and + /// default value existed in the sparse set. + /// + /// Use this subscript when you want either the value for a particular key + /// or, when that key is not present in the sparse set, a default value. + /// + /// When a sparse set's `Value` type has value semantics, you can use this + /// subscript to perform in-place operations on values in the sparse set. + /// + /// - Note: Do not use this subscript to modify sparse set values if the + /// sparse set's `Value` type is a class. In that case, the default value + /// and key are not written back to the sparse set after an operation. (For + /// a variant of this operation that supports this usecase, see + /// `modifyValue(forKey:default:_:)`.) + /// + /// - Parameters: + /// - key: The key the look up in the sparse set. + /// - defaultValue: The default value to use if `key` doesn't exist in the + /// sparse set. + /// + /// - Returns: The value associated with `key` in the sparse set; otherwise, + /// `defaultValue`. + /// + /// - Complexity: Looking up values in the sparse set through this subscript + /// has O(1) complexity. Updating the sparse set also has an amortized + /// expected complexity of O(1) -- although individual updates may need to + /// copy or resize the sparse set's underlying storage. + @inlinable + public subscript( + key: Key, + default defaultValue: @autoclosure () -> Value + ) -> Value { + get { + guard let index = _find(key: key) else { return defaultValue() } + return _dense._values[index] + } + _modify { + let index: Int + + if let existingIndex = _find(key: key) { + index = existingIndex + } else { + index = _dense.count + _append(value: defaultValue(), key: key) + } + + var value: Value = _dense._values.withUnsafeMutableBufferPointer { buffer in + assert(index < buffer.count) + return (buffer.baseAddress! + index).move() + } + defer { + _dense._values.withUnsafeMutableBufferPointer { buffer in + assert(index < buffer.count) + (buffer.baseAddress! + index).initialize(to: value) + } + } + yield &value + } + } +} + +// MARK: - + +extension SparseSet { + /// Merges the key-value pairs in the given sequence into the sparse set, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the updated sparse + /// set, or to combine existing and new values. As the key-value pairs are + /// merged with the sparse set, the `combine` closure is called with the + /// current and new values for any duplicate keys that are encountered. + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// sparse set. + @inlinable + public mutating func merge( + _ keysAndValues: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (key: Key, value: Value) { + for (key, value) in keysAndValues { + if let index = _find(key: key) { + try { $0 = try combine($0, value) }(&_dense._values[index]) + } else { + _append(value: value, key: key) + } + } + } + + /// Merges the key-value pairs in the given sequence into the sparse set, + /// using a combining closure to determine the value for any duplicate keys. + /// + /// Use the `combine` closure to select a value to use in the updated sparse + /// set, or to combine existing and new values. As the key-value pairs are + /// merged with the sparse set, the `combine` closure is called with the + /// current and new values for any duplicate keys that are encountered. + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// sparse set. + @inlinable + public mutating func merge( + _ keysAndValues: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows where S.Element == (Key, Value) { + let mapped: LazyMapSequence = + keysAndValues.lazy.map { (key: $0.0, value: $0.1) } + try merge(mapped, uniquingKeysWith: combine) + } + + /// Creates a sparse set by merging key-value pairs in a sequence into this + /// sparse set, using a combining closure to determine the value for duplicate + /// keys. + /// + /// Use the `combine` closure to select a value to use in the returned sparse + /// set, or to combine existing and new values. As the key-value pairs are + /// merged with the sparse set, the `combine` closure is called with the + /// current and new values for any duplicate keys that are encountered. + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// sparse set. + /// + /// - Returns: A new sparse set with the combined keys and values of this + /// sparse set and `other`. + @inlinable + public __consuming func merging( + _ other: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows -> Self where S.Element == (key: Key, value: Value) { + var copy = self + try copy.merge(other, uniquingKeysWith: combine) + return copy + } + + /// Creates a sparse set by merging key-value pairs in a sequence into this + /// sparse set, using a combining closure to determine the value for duplicate + /// keys. + /// + /// Use the `combine` closure to select a value to use in the returned sparse + /// set, or to combine existing and new values. As the key-value pairs are + /// merged with the sparse set, the `combine` closure is called with the + /// current and new values for any duplicate keys that are encountered. + /// + /// - Parameters: + /// - other: A sequence of key-value pairs. + /// - combine: A closure that takes the current and new values for any + /// duplicate keys. The closure returns the desired value for the final + /// sparse set. + /// + /// - Returns: A new sparse set with the combined keys and values of this + /// sparse set and `other`. + @inlinable + public __consuming func merging( + _ other: __owned S, + uniquingKeysWith combine: (Value, Value) throws -> Value + ) rethrows -> Self where S.Element == (Key, Value) { + var copy = self + try copy.merge(other, uniquingKeysWith: combine) + return copy + } +} + +// MARK: - + +extension SparseSet { + /// Returns a new sparse set containing the key-value pairs of the sparse set + /// 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 sparse set. + /// + /// - Returns: A sparse set of the key-value pairs that `isIncluded` allows. + /// + /// - Complexity: O(`count`) + @inlinable + public func filter( + _ isIncluded: (Element) throws -> Bool + ) rethrows -> Self { + var result: SparseSet = [:] + for element in self where try isIncluded(element) { + result._append(value: element.value, key: element.key) + } + return result + } +} + +// MARK: - + +extension SparseSet { + /// Returns a new sparse set containing the keys of this sparse set with the + /// values transformed by the given closure. + /// + /// - Parameter transform: A closure that transforms a value. `transform` + /// accepts each value of the sparse set as its parameter and returns a + /// transformed value of the same or of a different type. + /// - Returns: A sparse set containing the keys and transformed values of + /// this sparse set. + /// + /// - Complexity: O(`count`) + @inlinable + public func mapValues( + _ transform: (Value) throws -> T + ) rethrows -> SparseSet { + SparseSet( + uniqueKeys: _dense._keys, + values: ContiguousArray(try _dense._values.map(transform))) + } + + /// Returns a new sparse set containing only the key-value pairs that have + /// non-`nil` values as the result of transformation by the given closure. + /// + /// Use this method to receive a sparse set with non-optional values when + /// your transformation produces optional values. + /// + /// - Parameter transform: A closure that transforms a value. `transform` + /// accepts each value of the sparse set as its parameter and returns an + /// optional transformed value of the same or of a different type. + /// + /// - Returns: A sparse set containing the keys and non-`nil` transformed + /// values of this sparse set. + /// + /// - Complexity: O(`count`) + @inlinable + public func compactMapValues( + _ transform: (Value) throws -> T? + ) rethrows -> SparseSet { + var result: SparseSet = [:] + for (key, value) in self { + if let value = try transform(value) { + result._append(value: value, key: key) + } + } + return result + } +} + +// MARK: - + +extension SparseSet { + /// When resizing or cloning the sparse data storage we can either: copy all + /// of the data in the existing underlying buffer (including that of keys not + /// in the set) to the new buffer; or initialize the new buffer using only the + /// dense key data storage. The latter will be more efficient when the number + /// of keys in the sparse set is small relative to the universe size. This + /// constant is the density threshold which determines which strategy we use. + @usableFromInline + internal static var sparseDensityThresholdForCopyAll: Double { 0.1 } + + /// Ensures that the sparse data storage buffer is uniquely referenced, + /// copying it if necessary. + /// + /// This function should be called whenever key data is mutated in a way that + /// would make the sparse storage inconsistent with the keys in the dense + /// storage. + @inlinable + internal mutating func _ensureUnique() { + if !isKnownUniquelyReferenced(&__sparseBuffer) { + let density = Double(_dense.count) / Double(_sparse.capacity) + if density > SparseSet.sparseDensityThresholdForCopyAll { + __sparseBuffer = .bufferWith(contentsOf: __sparseBuffer) + } else { + __sparseBuffer = .bufferWith(capacity: _sparse.capacity, keys: _dense._keys) + } + } + } + + @inlinable + internal mutating func _ensureUniverseContains(key: Key) { + let minUniverseSize = Int(key) + 1 + if universeSize < minUniverseSize { + var newUniverseSize = Swift.max(Int((1.5 * Double(universeSize)).rounded(.up)), minUniverseSize) + if newUniverseSize - 1 > Int(Key.max) { + newUniverseSize = Int(Key.max) + 1 + } + _resizeUniverse(to: newUniverseSize) + } + } + + @inlinable + internal mutating func _resizeUniverse(to newUniverseSize: Int) { + let density = Double(_dense.count) / Double(_sparse.capacity) + let copyAllThreshold = 0.1 + if density > copyAllThreshold { + _sparse.resize(to: newUniverseSize) + } else { + _sparse.resize(to: newUniverseSize, keys: keys) + } + } +} + +// MARK: - + +extension SparseSet { + @inlinable + internal func _find(key: Key) -> Int? { + guard key >= 0 && key < _sparse.capacity else { + return nil + } + let index = _sparse[key] + guard index >= 0 && index < _dense.count else { + return nil + } + if _dense._keys[index] == key { + return index + } + return nil + } + + @inlinable + internal mutating func _append(value: Value, key: Key) { + defer { _checkInvariants() } + _ensureUnique() + _dense.append(value: value, key: key) + _ensureUniverseContains(key: key) + _sparse[key] = _dense.count - 1 + } + + @inlinable + @discardableResult + internal mutating func _remove(at index: Int) -> (key: Key, value: Value) { + defer { _checkInvariants() } + if index < _dense.count - 1 { + _ensureUnique() + let existingKey = _dense._keys[index] + let existingValue = _dense._values[index] + let (shiftedKey, shiftedValue) = _dense.removeLast() + _dense._keys[index] = shiftedKey + _dense._values[index] = shiftedValue + _sparse[shiftedKey] = index + return (existingKey, existingValue) + } else { + return _dense.removeLast() + } + } + + @inlinable + internal mutating func _swapAt(_ i: Int, _ j: Int) { + guard i != j else { return } + defer { _checkInvariants() } + _ensureUnique() + _dense._values.swapAt(i, j) + let keyA = _dense._keys[i] + let keyB = _dense._keys[j] + _dense._keys[i] = keyB + _dense._keys[j] = keyA + _sparse[keyA] = j + _sparse[keyB] = i + } +} diff --git a/Tests/SparseSetTests/SparseSet Tests.swift b/Tests/SparseSetTests/SparseSet Tests.swift new file mode 100644 index 000000000..2105ac45b --- /dev/null +++ b/Tests/SparseSetTests/SparseSet Tests.swift @@ -0,0 +1,1065 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import CollectionsTestSupport +@testable import SparseSetModule + +final class SparseSetTests: CollectionTestCase { + func test_isEmpty() { + let set = SparseSet() + expectEqual(set.count, 0) + expectTrue(set.isEmpty) + expectEqualElements(set, []) + } + + func test_init_minimumCapacity_universeSize() { + let s = SparseSet(minimumCapacity: 1_000, universeSize: 10_000) + expectGreaterThanOrEqual(s._dense._keys.capacity, 1_000) + expectGreaterThanOrEqual(s._dense._values.capacity, 1_000) + expectEqual(s._sparse.capacity, 10_000) + } + + func test_uniqueKeysWithValues_Dictionary() { + let items: Dictionary = [ + 0: "zero", + 1: "one", + 2: "two", + 3: "three", + ] + let s = SparseSet(uncheckedUniqueKeysWithValues: items) + expectEqualElements(s, items) + } + + func test_uniqueKeysWithValues_labeled_tuples() { + let items: KeyValuePairs = [ + 0: "zero", + 1: "one", + 2: "two", + 3: "three", + ] + let s = SparseSet(uncheckedUniqueKeysWithValues: items) + expectEqualElements(s, items) + } + + func test_uniqueKeysWithValues_unlabeled_tuples() { + let items: [(Int, String)] = [ + (0, "zero"), + (1, "one"), + (2, "two"), + (3, "three"), + ] + let s = SparseSet(uncheckedUniqueKeysWithValues: items) + expectEqualElements(s, items) + } + + func test_uniqueKeys_values() { + let s = SparseSet( + uncheckedUniqueKeys: [0, 1, 2, 3], + values: ["zero", "one", "two", "three"]) + expectEqualElements(s, [ + (key: 0, value: "zero"), + (key: 1, value: "one"), + (key: 2, value: "two"), + (key: 3, value: "three"), + ]) + } + + func test_uniquing_initializer_labeled_tuples() { + let items: KeyValuePairs = [ + 0: "a", + 1: "a", + 2: "a", + 0: "b", + 0: "c", + 1: "b", + 3: "c", + ] + let s = SparseSet(items, uniquingKeysWith: +) + expectEqualElements(s, [ + (key: 0, value: "abc"), + (key: 1, value: "ab"), + (key: 2, value: "a"), + (key: 3, value: "c") + ]) + } + + func test_uniquing_initializer_unlabeled_tuples() { + let items: [(Int, String)] = [ + (0, "a"), + (1, "a"), + (2, "a"), + (0, "b"), + (0, "c"), + (1, "b"), + (3, "c"), + ] + let s = SparseSet(items, uniquingKeysWith: +) + expectEqualElements(s, [ + (key: 0, value: "abc"), + (key: 1, value: "ab"), + (key: 2, value: "a"), + (key: 3, value: "c") + ]) + } + + func test_uncheckedUniqueKeysWithValues_labeled_tuples() { + let items: KeyValuePairs = [ + 0: "zero", + 1: "one", + 2: "two", + 3: "three", + ] + let s = SparseSet(uncheckedUniqueKeysWithValues: items) + expectEqualElements(s, items) + } + + func test_uncheckedUniqueKeysWithValues_unlabeled_tuples() { + let items: [(Int, String)] = [ + (0, "zero"), + (1, "one"), + (2, "two"), + (3, "three"), + ] + let s = SparseSet(uncheckedUniqueKeysWithValues: items) + expectEqualElements(s, items) + } + + func test_uncheckedUniqueKeys_values() { + let s = SparseSet( + uncheckedUniqueKeys: [0, 1, 2, 3], + values: ["zero", "one", "two", "three"]) + expectEqualElements(s, [ + (key: 0, value: "zero"), + (key: 1, value: "one"), + (key: 2, value: "two"), + (key: 3, value: "three"), + ]) + } + + func test_ExpressibleByDictionaryLiteral() { + let s0: SparseSet = [:] + expectTrue(s0.isEmpty) + + let s1: SparseSet = [ + 1: "one", + 2: "two", + 3: "three", + 4: "four", + ] + expectEqualElements(s1.map { $0.key }, [1, 2, 3, 4]) + expectEqualElements(s1.map { $0.value }, ["one", "two", "three", "four"]) + } + + func test_keys() { + let s: SparseSet = [ + 1: "one", + 2: "two", + 3: "three", + 4: "four", + ] + expectEqual(s.keys, [1, 2, 3, 4]) + } + + func test_counts() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, _, _) = tracker.sparseSet(keys: 0 ..< count) + expectEqual(sparseSet.isEmpty, count == 0) + expectEqual(sparseSet.count, count) + expectEqual(sparseSet.underestimatedCount, count) + } + } + } + + func test_index_forKey() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, _) = tracker.sparseSet(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + expectEqual(sparseSet.index(forKey: keys[offset]), offset) + } + expectNil(sparseSet.index(forKey: -1)) + expectNil(sparseSet.index(forKey: count)) + } + } + } + + func test_subscript_getter() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + expectEqual(sparseSet[keys[offset]], values[offset]) + } + expectNil(sparseSet[count]) + } + } + } + + func test_subscript_setter_update() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet[keys[offset]] = replacement + values[offset] = replacement + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_subscript_setter_remove() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet[keys[offset]] = nil + // Mimic expected sparse set removal: remove the element at + // offset, replacing it with the final element. + keys.swapAt(offset, keys.endIndex - 1) + keys.removeLast() + values.swapAt(offset, values.endIndex - 1) + values.removeLast() + withEvery("i", in: 0 ..< count - 1) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_subscript_setter_insert() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) + var sparseSet: SparseSet> = [:] + withEvery("offset", in: 0 ..< count) { offset in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet[keys[offset]] = values[offset] + withEvery("i", in: 0 ... offset) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_subscript_setter_noop() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let key = -1 + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet[key] = nil + } + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + + func mutate( + _ value: inout T, + _ body: (inout T) throws -> R + ) rethrows -> R { + try body(&value) + } + + func test_subscript_modify_update() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + mutate(&sparseSet[keys[offset]]) { $0 = replacement } + values[offset] = replacement + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + + func test_subscript_modify_remove() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + mutate(&sparseSet[key]) { v in + expectEqual(v, values[offset]) + v = nil + } + // Mimic expected sparse set removal: remove the element at + // offset, replacing it with the final element. + keys.swapAt(offset, keys.endIndex - 1) + keys.removeLast() + values.swapAt(offset, values.endIndex - 1) + values.removeLast() + withEvery("i", in: 0 ..< count - 1) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_subscript_modify_insert() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) + var sparseSet: SparseSet> = [:] + withEvery("offset", in: 0 ..< count) { offset in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + mutate(&sparseSet[keys[offset]]) { v in + expectNil(v) + v = values[offset] + } + expectEqual(sparseSet.count, offset + 1) + withEvery("i", in: 0 ... offset) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_subscript_modify_noop() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let key = -1 + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + mutate(&sparseSet[key]) { v in + expectNil(v) + v = nil + } + } + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + + func test_defaulted_subscript_getter() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let fallback = tracker.instance(for: -1) + withEvery("offset", in: 0 ..< count) { offset in + let key = keys[offset] + expectEqual(sparseSet[key, default: fallback], values[offset]) + } + expectEqual( + sparseSet[-1, default: fallback], + fallback) + expectEqual( + sparseSet[count, default: fallback], + fallback) + } + } + } + } + + func test_defaulted_subscript_modify_update() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + let fallback = tracker.instance(for: -1) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + mutate(&sparseSet[key, default: fallback]) { v in + expectEqual(v, values[offset]) + v = replacement + } + values[offset] = replacement + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_defaulted_subscript_modify_insert() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) + var sparseSet: SparseSet> = [:] + let fallback = tracker.instance(for: -1) + withEvery("offset", in: 0 ..< count) { offset in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + mutate(&sparseSet[key, default: fallback]) { v in + expectEqual(v, fallback) + v = values[offset] + } + expectEqual(sparseSet.count, offset + 1) + withEvery("i", in: 0 ... offset) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_updateValue_forKey_update() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + let old = sparseSet.updateValue(replacement, forKey: key) + expectEqual(old, values[offset]) + values[offset] = replacement + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_updateValue_forKey_insert() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) + var sparseSet: SparseSet> = [:] + withEvery("offset", in: 0 ..< count) { offset in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + let old = sparseSet.updateValue(values[offset], forKey: key) + expectNil(old) + expectEqual(sparseSet.count, offset + 1) + withEvery("i", in: 0 ... offset) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_modifyValue_forKey_default_closure_update() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + let fallback = tracker.instance(for: -2) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + sparseSet.modifyValue(forKey: key, default: fallback) { value in + expectEqual(value, values[offset]) + value = replacement + } + values[offset] = replacement + withEvery("i", in: 0 ..< count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_modifyValue_forKey_default_closure_insert() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + let values = tracker.instances(for: (0 ..< count).map { 100 + $0 }) + var sparseSet: SparseSet> = [:] + let fallback = tracker.instance(for: -2) + withEvery("offset", in: 0 ..< count) { offset in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let key = keys[offset] + sparseSet.modifyValue(forKey: key, default: fallback) { value in + expectEqual(value, fallback) + value = values[offset] + } + expectEqual(sparseSet.count, offset + 1) + withEvery("i", in: 0 ... offset) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + } + } + } + } + } + } + + func test_removeValue_forKey() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + // Mimic expected sparse set removal: remove the element at + // offset, replacing it with the final element. + keys.swapAt(offset, keys.endIndex - 1) + let key = keys.removeLast() + values.swapAt(offset, values.endIndex - 1) + let expected = values.removeLast() + let actual = sparseSet.removeValue(forKey: key) + expectEqual(actual, expected) + expectEqual(sparseSet.count, values.count) + withEvery("i", in: 0 ..< values.count) { i in + let (k, v) = sparseSet[offset: i] + expectEqual(k, keys[i]) + expectEqual(v, values[i]) + } + expectNil(sparseSet.removeValue(forKey: key)) + } + } + } + } + } + } + + func test_merge_labeled_tuple() { + var s: SparseSet = [ + 1: "a", + 2: "b", + 3: "c", + ] + + let items: KeyValuePairs = [ + 1: "d", + 1: "e", + 3: "f", + 4: "g", + 1: "h", + ] + + s.merge(items, uniquingKeysWith: +) + + expectEqualElements(s, [ + 1: "adeh", + 2: "b", + 3: "cf", + 4: "g", + ] as KeyValuePairs) + } + + func test_merge_unlabeled_tuple() { + var s: SparseSet = [ + 1: "a", + 2: "b", + 3: "c", + ] + + let items: [(Int, String)] = [ + (1, "d"), + (1, "e"), + (3, "f"), + (4, "g"), + (1, "h"), + ] + + s.merge(items, uniquingKeysWith: +) + + expectEqualElements(s, [ + 1: "adeh", + 2: "b", + 3: "cf", + 4: "g", + ] as KeyValuePairs) + } + + func test_merging_labeled_tuple() { + let s: SparseSet = [ + 1: "a", + 2: "b", + 3: "c", + ] + + let items: KeyValuePairs = [ + 1: "d", + 1: "e", + 3: "f", + 4: "g", + 1: "h", + ] + + let s2 = s.merging(items, uniquingKeysWith: +) + + expectEqualElements(s, [ + 1: "a", + 2: "b", + 3: "c", + ] as KeyValuePairs) + + expectEqualElements(s2, [ + 1: "adeh", + 2: "b", + 3: "cf", + 4: "g", + ] as KeyValuePairs) + } + + func test_merging_unlabeled_tuple() { + let s: SparseSet = [ + 1: "a", + 2: "b", + 3: "c", + ] + + let items: [(Int, String)] = [ + (1, "d"), + (1, "e"), + (3, "f"), + (4, "g"), + (1, "h"), + ] + + let s2 = s.merging(items, uniquingKeysWith: +) + + expectEqualElements(s, [ + 1: "a", + 2: "b", + 3: "c", + ] as KeyValuePairs) + + expectEqualElements(s2, [ + 1: "adeh", + 2: "b", + 3: "cf", + 4: "g", + ] as KeyValuePairs) + } + + func test_filter() { + let items = (0 ..< 100).map { ($0, 100 * $0) } + let s = SparseSet(uniqueKeysWithValues: items) + + var c = 0 + let s2 = s.filter { item in + c += 1 + expectEqual(item.value, 100 * item.key) + return item.key.isMultiple(of: 2) + } + expectEqual(c, 100) + expectEqualElements(s, items) + + expectEqualElements(s2, (0 ..< 50).compactMap { key in + return (key: 2 * key, value: 200 * key) + }) + } + + func test_mapValues() { + let items = (0 ..< 100).map { ($0, 100 * $0) } + let s = SparseSet(uniqueKeysWithValues: items) + + var c = 0 + let s2 = s.mapValues { value -> String in + c += 1 + expectTrue(value.isMultiple(of: 100)) + return "\(value)" + } + expectEqual(c, 100) + expectEqualElements(s, items) + + expectEqualElements(s2, (0 ..< 100).compactMap { key in + (key: key, value: "\(100 * key)") + }) + } + + func test_compactMapValue() { + let items = (0 ..< 100).map { ($0, 100 * $0) } + let s = SparseSet(uniqueKeysWithValues: items) + + var c = 0 + let s2 = s.compactMapValues { value -> String? in + c += 1 + guard value.isMultiple(of: 200) else { return nil } + expectTrue(value.isMultiple(of: 100)) + return "\(value)" + } + expectEqual(c, 100) + expectEqualElements(s, items) + + expectEqualElements(s2, (0 ..< 50).map { key in + (key: 2 * key, value: "\(200 * key)") + }) + } + + func test_CustomStringConvertible() { + let a: SparseSet = [:] + expectEqual(a.description, "[:]") + + let b: SparseSet = [0: 1] + expectEqual(b.description, "[0: 1]") + + let c: SparseSet = [0: 1, 2: 3, 4: 5] + expectEqual(c.description, "[0: 1, 2: 3, 4: 5]") + } + + func test_CustomDebugStringConvertible() { + let a: SparseSet = [:] + expectEqual(a.debugDescription, + "SparseSet([:])") + + let b: SparseSet = [0: 1] + expectEqual(b.debugDescription, + "SparseSet([0: 1])") + + let c: SparseSet = [0: 1, 2: 3, 4: 5] + expectEqual(c.debugDescription, + "SparseSet([0: 1, 2: 3, 4: 5])") + } + + func test_Equatable_Hashable() { + let samples: [[SparseSet]] = [ + [[:], [:]], + [[1: 100], [1: 100]], + [[2: 200], [2: 200]], + [[3: 300], [3: 300]], + [[100: 1], [100: 1]], + [[1: 1], [1: 1]], + [[100: 100], [100: 100]], + [[1: 100, 2: 200], [1: 100, 2: 200]], + [[2: 200, 1: 100], [2: 200, 1: 100]], + [[1: 100, 2: 200, 3: 300], [1: 100, 2: 200, 3: 300]], + [[2: 200, 1: 100, 3: 300], [2: 200, 1: 100, 3: 300]], + [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], + [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] + ] + checkHashable(equivalenceClasses: samples) + } + + func test_Encodable() throws { + let s1: SparseSet = [:] + let v1: MinimalEncoder.Value = .array([]) + expectEqual(try MinimalEncoder.encode(s1), v1) + + let s2: SparseSet = [0: 1] + let v2: MinimalEncoder.Value = .array([.int(0), .int(1)]) + expectEqual(try MinimalEncoder.encode(s2), v2) + + let s3: SparseSet = [0: 1, 2: 3] + let v3: MinimalEncoder.Value = + .array([.int(0), .int(1), .int(2), .int(3)]) + expectEqual(try MinimalEncoder.encode(s3), v3) + + let s4 = SparseSet( + uniqueKeys: 0 ..< 100, + values: (0 ..< 100).map { 100 * $0 }) + let v4: MinimalEncoder.Value = + .array((0 ..< 100).flatMap { [.int($0), .int(100 * $0)] }) + expectEqual(try MinimalEncoder.encode(s4), v4) + } + + func test_Decodable() throws { + typealias SSII = SparseSet + + let s1: SSII = [:] + let v1: MinimalEncoder.Value = .array([]) + expectEqual(try MinimalDecoder.decode(v1, as: SSII.self), s1) + + let s2: SSII = [0: 1] + let v2: MinimalEncoder.Value = .array([.int(0), .int(1)]) + expectEqual(try MinimalDecoder.decode(v2, as: SSII.self), s2) + + let s3: SSII = [0: 1, 2: 3] + let v3: MinimalEncoder.Value = + .array([.int(0), .int(1), .int(2), .int(3)]) + expectEqual(try MinimalDecoder.decode(v3, as: SSII.self), s3) + + let s4: SSII = SparseSet( + uniqueKeys: 0 ..< 100, + values: (0 ..< 100).map { 100 * $0 }) + let v4: MinimalEncoder.Value = + .array((0 ..< 100).flatMap { [.int($0), .int(100 * $0)] }) + expectEqual(try MinimalDecoder.decode(v4, as: SSII.self), s4) + + let v5: MinimalEncoder.Value = .array([.int(0), .int(1), .int(2)]) + expectThrows(try MinimalDecoder.decode(v5, as: SSII.self)) { error in + guard case DecodingError.dataCorrupted(let context) = error else { + expectFailure("Unexpected error \(error)") + return + } + expectEqual(context.debugDescription, + "Unkeyed container reached end before value in key-value pair") + + } + + let v6: MinimalEncoder.Value = .array([.int(0), .int(1), .int(0), .int(2)]) + expectThrows(try MinimalDecoder.decode(v6, as: SSII.self)) { error in + guard case DecodingError.dataCorrupted(let context) = error else { + expectFailure("Unexpected error \(error)") + return + } + expectEqual(context.debugDescription, "Duplicate key at offset 2") + } + } + + func test_swapAt() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("i", in: 0 ..< count) { i in + withEvery("j", in: 0 ..< count) { j in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + keys.swapAt(i, j) + values.swapAt(i, j) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.swapAt(i, j) + expectEqualElements(sparseSet.values, values) + expectEqual(sparseSet[keys[i]], values[i]) + expectEqual(sparseSet[keys[j]], values[j]) + } + } + } + } + } + } + } + + func test_partition() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + let expectedPivot = items.partition { $0.0 < count / 2 } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actualPivot = sparseSet.partition { $0.key < count / 2 } + expectEqual(actualPivot, expectedPivot) + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_sort() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + items.sort(by: { $0.0 < $1.0 }) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.sort() + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_sort_by() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + items.sort(by: { $0.0 > $1.0 }) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.sort(by: { $0.key > $1.key }) + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_shuffle() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withEvery("seed", in: 0 ..< 10) { seed in + var sparseSet = SparseSet( + uniqueKeys: 0 ..< count, + values: 100 ..< 100 + count) + var items = (0 ..< count).map { (key: $0, value: 100 + $0) } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + expectEqualElements(sparseSet, items) + + var rng1 = RepeatableRandomNumberGenerator(seed: seed) + items.shuffle(using: &rng1) + + var rng2 = RepeatableRandomNumberGenerator(seed: seed) + sparseSet.shuffle(using: &rng2) + + items.sort(by: { $0.key < $1.key }) + sparseSet.sort() + expectEqualElements(sparseSet, items) + } + } + } + if count >= 2 { + // Check that shuffling with the system RNG does permute the elements. + var sparseSet = SparseSet( + uniqueKeys: 0 ..< count, + values: 100 ..< 100 + count) + let original = sparseSet + var success = false + for _ in 0 ..< 1000 { + sparseSet.shuffle() + if !sparseSet.elementsEqual( + original, + by: { $0.key == $1.key && $0.value == $1.value} + ) { + success = true + break + } + } + expectTrue(success) + } + } + } + + func test_reverse() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + var items = Array(zip(keys, values)) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + items.reverse() + sparseSet.reverse() + expectEqualElements(sparseSet, items) + } + } + } + } + } + + func test_removeAll() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, _, _) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.removeAll() + expectEqual(sparseSet.universeSize, 0) + expectEqualElements(sparseSet, []) + } + } + } + } + } + + func test_removeAll_keepCapacity() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, _, _) = tracker.sparseSet(keys: 0 ..< count) + let origUniverseSize = sparseSet.universeSize + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.removeAll(keepingCapacity: true) + expectEqual(sparseSet.universeSize, origUniverseSize) + expectEqualElements(sparseSet, []) + } + } + } + } + } +} diff --git a/Tests/SparseSetTests/SparseSet Utils.swift b/Tests/SparseSetTests/SparseSet Utils.swift new file mode 100644 index 000000000..6d5eeb312 --- /dev/null +++ b/Tests/SparseSetTests/SparseSet Utils.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import CollectionsTestSupport +import SparseSetModule + +extension LifetimeTracker { + func sparseSet( + keys: Keys + ) -> ( + sparseSet: SparseSet>, + keys: [Int], + values: [LifetimeTracked] + ) + where Keys.Element == Int + { + let k = Array(keys) + let values = self.instances(for: k) + let sparseSet = SparseSet(uniqueKeys: k, values: values) + return (sparseSet, k, values) + } +} diff --git a/Tests/SparseSetTests/SparseSet+Elements Tests.swift b/Tests/SparseSetTests/SparseSet+Elements Tests.swift new file mode 100644 index 000000000..d37c6ecd1 --- /dev/null +++ b/Tests/SparseSetTests/SparseSet+Elements Tests.swift @@ -0,0 +1,546 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import CollectionsTestSupport +@testable import SparseSetModule + +class SparseSetElementsTests: CollectionTestCase { + func test_elements_getter() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let items = zip(keys, values).map { (key: $0.0, value: $0.1) } + expectEqualElements(sparseSet.elements, items) + } + } + } + + func test_elements_modify() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let items = zip(keys, values).map { (key: $0.0, value: $0.1) } + + var sparseSet2 = SparseSet>() + + swap(&sparseSet.elements, &sparseSet2.elements) + + expectEqualElements(sparseSet, []) + expectEqualElements(sparseSet2, items) + } + } + } + + func test_keys_values() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + + expectEqualElements(sparseSet.elements.keys, keys) + expectEqualElements(sparseSet.elements.values, values) + + values.reverse() + sparseSet.elements.values.reverse() + expectEqualElements(sparseSet.elements.values, values) + } + } + } + + func test_index_forKey() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, _) = tracker.sparseSet(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + expectEqual(sparseSet.elements.index(forKey: keys[offset]), offset) + } + expectNil(sparseSet.elements.index(forKey: -1)) + expectNil(sparseSet.elements.index(forKey: count)) + } + } + } + + func test_RandomAccessCollection() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let items = zip(keys, values).map { (key: $0.0, value: $0.1) } + checkBidirectionalCollection( + sparseSet.elements, expectedContents: items, + by: { $0 == $1 }) + } + } + } + + func test_CustomStringConvertible() { + let a: SparseSet = [:] + expectEqual(a.elements.description, "[:]") + + let b: SparseSet = [0: 1] + expectEqual(b.elements.description, "[0: 1]") + + let c: SparseSet = [0: 1, 2: 3, 4: 5] + expectEqual(c.elements.description, "[0: 1, 2: 3, 4: 5]") + } + + func test_CustomDebugStringConvertible() { + let a: SparseSet = [:] + expectEqual(a.elements.debugDescription, + "SparseSet.Elements([:])") + + let b: SparseSet = [0: 1] + expectEqual(b.elements.debugDescription, + "SparseSet.Elements([0: 1])") + + let c: SparseSet = [0: 1, 2: 3, 4: 5] + expectEqual(c.elements.debugDescription, + "SparseSet.Elements([0: 1, 2: 3, 4: 5])") + } + + func test_customReflectable() { + do { + let sparseSet: SparseSet = [1: 2, 3: 4, 5: 6] + let mirror = Mirror(reflecting: sparseSet.elements) + expectEqual(mirror.displayStyle, .collection) + expectNil(mirror.superclassMirror) + expectTrue(mirror.children.compactMap { $0.label }.isEmpty) // No label + expectEqualElements( + mirror.children.compactMap { $0.value as? (key: Int, value: Int) }, + sparseSet.map { $0 }) + } + } + + func test_Equatable_Hashable() { + let samples: [[SparseSet]] = [ + [[:], [:]], + [[1: 100], [1: 100]], + [[2: 200], [2: 200]], + [[3: 300], [3: 300]], + [[100: 1], [100: 1]], + [[1: 1], [1: 1]], + [[100: 100], [100: 100]], + [[1: 100, 2: 200], [1: 100, 2: 200]], + [[2: 200, 1: 100], [2: 200, 1: 100]], + [[1: 100, 2: 200, 3: 300], [1: 100, 2: 200, 3: 300]], + [[2: 200, 1: 100, 3: 300], [2: 200, 1: 100, 3: 300]], + [[3: 300, 2: 200, 1: 100], [3: 300, 2: 200, 1: 100]], + [[3: 300, 1: 100, 2: 200], [3: 300, 1: 100, 2: 200]] + ] + checkHashable(equivalenceClasses: samples.map { $0.map { $0.elements }}) + } + + func test_swapAt() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("i", in: 0 ..< count) { i in + withEvery("j", in: 0 ..< count) { j in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + keys.swapAt(i, j) + values.swapAt(i, j) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.swapAt(i, j) + expectEquivalentElements( + sparseSet, zip(keys, values), + by: { $0.key == $1.0 && $0.value == $1.1 }) + expectEqual(sparseSet[keys[i]], values[i]) + expectEqual(sparseSet[keys[j]], values[j]) + } + } + } + } + } + } + } + + func test_partition() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + let expectedPivot = items.partition { $0.0 < count / 2 } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actualPivot = sparseSet.elements.partition { $0.key < count / 2 } + expectEqual(actualPivot, expectedPivot) + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_sort() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + items.sort(by: { $0.0 < $1.0 }) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.sort() + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_sort_by() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + var items = Array(zip(keys, values)) + items.sort(by: { $0.0 > $1.0 }) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.sort(by: { $0.key > $1.key }) + expectEqualElements(sparseSet, items) + } + } + } + } + } + } + + func test_shuffle() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withEvery("seed", in: 0 ..< 10) { seed in + var sparseSet = SparseSet( + uniqueKeys: 0 ..< count, + values: 100 ..< 100 + count) + var items = (0 ..< count).map { (key: $0, value: 100 + $0) } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + expectEqualElements(sparseSet.elements, items) + + var rng1 = RepeatableRandomNumberGenerator(seed: seed) + items.shuffle(using: &rng1) + + var rng2 = RepeatableRandomNumberGenerator(seed: seed) + sparseSet.elements.shuffle(using: &rng2) + + items.sort(by: { $0.key < $1.key }) + sparseSet.elements.sort() + expectEqualElements(sparseSet, items) + } + } + } + if count >= 2 { + // Check that shuffling with the system RNG does permute the elements. + var sparseSet = SparseSet( + uniqueKeys: 0 ..< count, + values: 100 ..< 100 + count) + let original = sparseSet + var success = false + for _ in 0 ..< 1000 { + sparseSet.elements.shuffle() + if !sparseSet.elementsEqual( + original, + by: { $0.key == $1.key && $0.value == $1.value} + ) { + success = true + break + } + } + expectTrue(success) + } + } + } + + func test_reverse() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + var items = Array(zip(keys, values)) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + items.reverse() + sparseSet.elements.reverse() + expectEqualElements(sparseSet, items) + } + } + } + } + } + + func test_removeAll() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, _, _) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeAll() + expectEqual(sparseSet.universeSize, 0) + expectEqualElements(sparseSet, []) + } + } + } + } + } + + func test_removeAll_keepCapacity() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, _, _) = tracker.sparseSet(keys: 0 ..< count) + let origUniverseSize = sparseSet.universeSize + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeAll(keepingCapacity: true) + expectEqual(sparseSet.universeSize, origUniverseSize) + expectEqualElements(sparseSet, []) + } + } + } + } + } + + func test_remove_at() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actual = sparseSet.elements.remove(at: offset) + // Mimic expected sparse set removal: remove the element at + // offset, replacing it with the final element. + keys.swapAt(offset, keys.endIndex - 1) + let expectedKey = keys.removeLast() + values.swapAt(offset, values.endIndex - 1) + let expectedValue = values.removeLast() + expectEqual(actual.key, expectedKey) + expectEqual(actual.value, expectedValue) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + } + + func test_removeSubrange() { + withEvery("count", in: 0 ..< 30) { count in + withEveryRange("range", in: 0 ..< count) { range in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeSubrange(range) + // Mimic expected sparse set removal: remove the subrange and + // close the gap with elements from the end (preserving their + // order). + let movedKeys = keys[range.endIndex...].suffix(range.count) + keys.removeLast(movedKeys.count) + keys.insert(contentsOf: movedKeys, at: range.endIndex) + keys.removeSubrange(range) + let movedValues = values[range.endIndex...].suffix(range.count) + values.removeLast(movedValues.count) + values.insert(contentsOf: movedValues, at: range.endIndex) + values.removeSubrange(range) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + } + + func test_removeSubrange_rangeExpression() { + let s = SparseSet(uniqueKeys: 0 ..< 30, values: 100 ..< 130) + let items = (0 ..< 30).map { (key: $0, value: 100 + $0) } + + var s1 = s + s1.elements.removeSubrange(...10) + let idx1 = items[11...].suffix(11).startIndex + expectEqualElements(s1, items[11...].suffix(11) + items[11 ..< idx1]) + + var s2 = s + s2.elements.removeSubrange(..<10) + let idx2 = items[10...].suffix(10).startIndex + expectEqualElements(s2, items[10...].suffix(10) + items[10 ..< idx2]) + + var s3 = s + s3.elements.removeSubrange(10...) + expectEqualElements(s3, items[0 ..< 10]) + } + + func test_removeLast() { + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< 30) + withEvery("i", in: 0 ..< sparseSet.count) { i in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actual = sparseSet.elements.removeLast() + let expectedKey = keys.removeLast() + let expectedValue = values.removeLast() + expectEqual(actual.key, expectedKey) + expectEqual(actual.value, expectedValue) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + + func test_removeFirst() { + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< 30) + withEvery("i", in: 0 ..< sparseSet.count) { i in + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actual = sparseSet.elements.removeFirst() + // Mimic expected sparse set removal: remove the first element, + // replacing it with the final element. + keys.swapAt(0, keys.endIndex - 1) + let expectedKey = keys.removeLast() + values.swapAt(0, values.endIndex - 1) + let expectedValue = values.removeLast() + expectEqual(actual.key, expectedKey) + expectEqual(actual.value, expectedValue) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + + func test_removeLast_n() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("suffix", in: 0 ..< count) { suffix in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeLast(suffix) + keys.removeLast(suffix) + values.removeLast(suffix) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + } + + func test_removeFirst_n() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("prefix", in: 0 ..< count) { prefix in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeFirst(prefix) + // Mimic expected sparse set removal: remove initial elements, + // replacing them with elements from the end (preserving their + // order). + let movedKeys = Array(keys[prefix...].suffix(prefix)) + keys.removeLast(movedKeys.count) + keys.insert(contentsOf: movedKeys, at: prefix) + keys.removeFirst(prefix) + let movedValues = Array(values[prefix...].suffix(prefix)) + values.removeLast(movedKeys.count) + values.insert(contentsOf: movedValues, at: prefix) + values.removeFirst(prefix) + expectEqualElements( + sparseSet, + zip(keys, values).map { (key: $0.0, value: $0.1) }) + } + } + } + } + } + } + + func test_removeAll_where() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("n", in: [2, 3, 4]) { n in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + var items = zip(keys, values).map { (key: $0.0, value: $0.1) } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.elements.removeAll(where: { !$0.key.isMultiple(of: n) }) + items.removeAll(where: { !$0.key.isMultiple(of: n) }) + // Note: SparseSet doesn't guarantee that ordering is preserved + // after calling `removeAll(where:)`. + let dict1 = Dictionary(uniqueKeysWithValues: items) + let dict2 = Dictionary(uniqueKeysWithValues: sparseSet.elements.lazy.map { ($0.key, $0.value) }) + expectEqual(dict1, dict2) + } + } + } + } + } + } + + func test_slice_keys() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, keys, _) = tracker.sparseSet(keys: 0 ..< count) + withEveryRange("range", in: 0 ..< count) { range in + expectEqual(sparseSet.elements[range].keys, sparseSet.keys[range]) + expectEqualElements(sparseSet.elements[range].keys, keys[range]) + } + } + } + } + + func test_slice_values() { + withEvery("count", in: 0 ..< 30) { count in + withLifetimeTracking { tracker in + let (sparseSet, _, values) = tracker.sparseSet(keys: 0 ..< count) + withEveryRange("range", in: 0 ..< count) { range in + expectEqualElements(sparseSet.elements[range].values, sparseSet.values[range]) + expectEqualElements(sparseSet.elements[range].values, values[range]) + } + } + } + } + + func test_slice_index_forKey() { + withEvery("count", in: 0 ..< 30) { count in + withEveryRange("range", in: 0 ..< count) { range in + withLifetimeTracking { tracker in + let (sparseSet, keys, _) = tracker.sparseSet(keys: 0 ..< count) + withEvery("offset", in: 0 ..< count) { offset in + let actual = sparseSet.elements[range].index(forKey: keys[offset]) + let expected = range.contains(offset) ? offset : nil + expectEqual(actual, expected) + } + expectNil(sparseSet.elements[range].index(forKey: -1)) + expectNil(sparseSet.elements[range].index(forKey: count)) + } + } + } + } +} diff --git a/Tests/SparseSetTests/SparseSet+Values Tests.swift b/Tests/SparseSetTests/SparseSet+Values Tests.swift new file mode 100644 index 000000000..fb5a316ba --- /dev/null +++ b/Tests/SparseSetTests/SparseSet+Values Tests.swift @@ -0,0 +1,206 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +import XCTest +import CollectionsTestSupport +@testable import SparseSetModule + +class SparseSetValueTests: CollectionTestCase { + func test_values_getter() { + let s: SparseSet = [ + 1: "one", + 2: "two", + 3: "three", + 4: "four", + ] + expectEqualElements(s.values, ["one", "two", "three", "four"]) + } + + func test_values_RandomAccessCollection() { + withEvery("count", in: 0 ..< 30) { count in + let keys = 0 ..< count + let values = keys.map { $0 + 100 } + let s = SparseSet(uniqueKeys: keys, values: values) + checkBidirectionalCollection(s.values, expectedContents: values) + } + } + + func test_values_subscript_assignment() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instance(for: -1) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.values[offset] = replacement + values[offset] = replacement + expectEqualElements(sparseSet.values, values) + expectEqual(sparseSet[keys[offset]], values[offset]) + } + } + } + } + } + } + + func test_values_subscript_inPlaceMutation() { + func checkMutation( + _ item: inout LifetimeTracked, + tracker: LifetimeTracker, + delta: Int, + file: StaticString = #file, + line: UInt = #line + ) { + expectTrue(isKnownUniquelyReferenced(&item)) + item = tracker.instance(for: item.payload + delta) + } + + withEvery("count", in: 0 ..< 30) { count in + withEvery("offset", in: 0 ..< count) { offset in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + // Discard `values` and recreate it from scratch to make sure its + // elements are uniquely referenced. + values = tracker.instances(for: values.lazy.map { $0.payload }) + + checkMutation(&values[offset], tracker: tracker, delta: 10) + checkMutation(&sparseSet.values[offset], tracker: tracker, delta: 10) + + expectEqualElements(sparseSet.values, values) + expectEqual(sparseSet[keys[offset]], values[offset]) + } + } + } + } + + func test_swapAt() { + withEvery("count", in: 0 ..< 20) { count in + withEvery("i", in: 0 ..< count) { i in + withEvery("j", in: 0 ..< count) { j in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + values.swapAt(i, j) + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + sparseSet.values.swapAt(i, j) + expectEqualElements(sparseSet.values, values) + expectEqual(sparseSet[keys[i]], values[i]) + expectEqual(sparseSet[keys[j]], values[j]) + } + } + } + } + } + } + } + + func test_partition() { + withEvery("seed", in: 0 ..< 10) { seed in + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var rng = RepeatableRandomNumberGenerator(seed: seed) + var (sparseSet, keys, values) = tracker.sparseSet( + keys: (0 ..< count).shuffled(using: &rng)) + let expectedPivot = values.partition { $0.payload < 100 + count / 2 } + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet in + let actualPivot = sparseSet.values.partition { $0.payload < 100 + count / 2 } + expectEqual(actualPivot, expectedPivot) + expectEqualElements(sparseSet.values, values) + withEvery("i", in: 0 ..< count) { i in + expectEqual(sparseSet[keys[i]], values[i]) + } + } + } + } + } + } + } + + func test_withUnsafeBufferPointer() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, _, values) = tracker.sparseSet(keys: 0 ..< count) + typealias R = [LifetimeTracked] + let actual = + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet -> R in + sparseSet.values.withUnsafeBufferPointer { buffer -> R in + Array(buffer) + } + } + expectEqualElements(actual, values) + } + } + } + } + + func test_withUnsafeMutableBufferPointer() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (sparseSet, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instances(for: (0 ..< count).map { -$0 }) + typealias R = [LifetimeTracked] + let actual = + withHiddenCopies(if: isShared, of: &sparseSet) { sparseSet -> R in + sparseSet.values.withUnsafeMutableBufferPointer { buffer -> R in + let result = Array(buffer) + expectEqual(buffer.count, replacement.count, trapping: true) + for i in 0 ..< replacement.count { + buffer[i] = replacement[i] + } + return result + } + } + expectEqualElements(actual, values) + expectEqualElements(sparseSet.values, replacement) + withEvery("i", in: 0 ..< count) { i in + expectEqual(sparseSet[keys[i]], replacement[i]) + } + } + } + } + } + + func test_withContiguousMutableStorageIfAvailable() { + withEvery("count", in: 0 ..< 30) { count in + withEvery("isShared", in: [false, true]) { isShared in + withLifetimeTracking { tracker in + var (d, keys, values) = tracker.sparseSet(keys: 0 ..< count) + let replacement = tracker.instances(for: (0 ..< count).map { -$0 }) + typealias R = [LifetimeTracked] + let actual = + withHiddenCopies(if: isShared, of: &d) { sparseSet -> R? in + sparseSet.values.withContiguousMutableStorageIfAvailable { buffer -> R in + let result = Array(buffer) + expectEqual(buffer.count, replacement.count, trapping: true) + for i in 0 ..< replacement.count { + buffer[i] = replacement[i] + } + return result + } + } + if let actual = actual { + expectEqualElements(actual, values) + expectEqualElements(d.values, replacement) + withEvery("i", in: 0 ..< count) { i in + expectEqual(d[keys[i]], replacement[i]) + } + } else { + expectFailure("SparseSet.Value isn't contiguous?") + } + } + } + } + } +} From cb6bfdc5c862be0cbe15fcab9ab7d75ff1996d1f Mon Sep 17 00:00:00 2001 From: Edward Marchant Date: Fri, 13 Aug 2021 16:47:01 +0100 Subject: [PATCH 2/5] Improve performance of sparse storage initialization and reindexing --- .../SparseSetModule/SparseSet+SparseStorage.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Sources/SparseSetModule/SparseSet+SparseStorage.swift b/Sources/SparseSetModule/SparseSet+SparseStorage.swift index e1bcd92b5..7cca8fc1b 100644 --- a/Sources/SparseSetModule/SparseSet+SparseStorage.swift +++ b/Sources/SparseSetModule/SparseSet+SparseStorage.swift @@ -44,6 +44,11 @@ extension SparseSet.SparseStorage { internal struct Header { @usableFromInline internal var capacity: Int + + @inlinable + internal init(capacity: Int) { + self.capacity = capacity + } } } @@ -57,7 +62,7 @@ extension SparseSet.SparseStorage { /// - keys: A collection of keys. /// /// - Returns: A new buffer. - @usableFromInline + @inlinable internal static func bufferWith(capacity: Int, keys: C) -> Buffer where C.Element == Key { assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) let newBuffer = Buffer.create( @@ -81,7 +86,7 @@ extension SparseSet.SparseStorage { /// - Parameter buffer: The buffer to clone. /// /// - Returns: A new buffer. - @usableFromInline + @inlinable internal static func bufferWith(contentsOf buffer: Buffer) -> Buffer { return Buffer.bufferWith(capacity: buffer.capacity, contentsOf: buffer) } @@ -96,7 +101,7 @@ extension SparseSet.SparseStorage { /// - buffer: The data to populate the new buffer with. /// /// - Returns: A new buffer. - @usableFromInline + @inlinable internal static func bufferWith(capacity: Int, contentsOf buffer: Buffer) -> Buffer { let newBuffer = Buffer.create( minimumCapacity: capacity, @@ -148,7 +153,7 @@ extension SparseSet.SparseStorage { /// Rebuilds the index data for the given key data. /// /// - Parameter indices: A collection of keys. - @usableFromInline + @inlinable internal mutating func reindex(keys: C) where C.Element == Key { assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) _buffer.withUnsafeMutablePointerToElements { ptr in From 872010e9a483a7f3004fe1dcbc6f6fc1c7222554 Mon Sep 17 00:00:00 2001 From: Edward Marchant Date: Fri, 13 Aug 2021 17:32:55 +0100 Subject: [PATCH 3/5] Fix some symbol documentation formatting and typos --- .../SparseSetModule/SparseSet+Elements+SubSequence.swift | 1 + Sources/SparseSetModule/SparseSet+SparseStorage.swift | 8 ++++---- Sources/SparseSetModule/SparseSet.swift | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift index c20af740f..82977ae67 100644 --- a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift +++ b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift @@ -242,6 +242,7 @@ extension SparseSet.Elements.SubSequence: RandomAccessCollection { /// `distance > 0`, `limit` has no effect if it is less than `i`. /// Likewise, if `distance < 0`, `limit` has no effect if it is greater /// than `i`. + /// /// - Returns: An index offset by `distance` from the index `i`, unless that /// index would be beyond `limit` in the direction of movement. In that /// case, the method returns `nil`. diff --git a/Sources/SparseSetModule/SparseSet+SparseStorage.swift b/Sources/SparseSetModule/SparseSet+SparseStorage.swift index 7cca8fc1b..17d570960 100644 --- a/Sources/SparseSetModule/SparseSet+SparseStorage.swift +++ b/Sources/SparseSetModule/SparseSet+SparseStorage.swift @@ -124,7 +124,7 @@ extension SparseSet.SparseStorage { _buffer.header.capacity } - /// Resize this index table to a new capacity. + /// Resize this storage to a new capacity. /// /// The underlying buffer is replaced with a new one and its contents are /// copied. @@ -135,7 +135,7 @@ extension SparseSet.SparseStorage { _buffer = Buffer.bufferWith(capacity: newCapacity, contentsOf: _buffer) } - /// Resize this index table to a new capacity. + /// Resize this storage to a new capacity. /// /// The underlying buffer is replaced with a new one. The contents of the /// new buffer is initialized from the provided `keys` collection. This @@ -150,9 +150,9 @@ extension SparseSet.SparseStorage { _buffer = Buffer.bufferWith(capacity: newCapacity, keys: keys) } - /// Rebuilds the index data for the given key data. + /// Rebuilds the index data for the given keys. /// - /// - Parameter indices: A collection of keys. + /// - Parameter keys: A collection of keys. @inlinable internal mutating func reindex(keys: C) where C.Element == Key { assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) diff --git a/Sources/SparseSetModule/SparseSet.swift b/Sources/SparseSetModule/SparseSet.swift index b02da5687..95c244e40 100644 --- a/Sources/SparseSetModule/SparseSet.swift +++ b/Sources/SparseSetModule/SparseSet.swift @@ -295,7 +295,7 @@ extension SparseSet { /// - Note: Do not use this subscript to modify sparse set values if the /// sparse set's `Value` type is a class. In that case, the default value /// and key are not written back to the sparse set after an operation. (For - /// a variant of this operation that supports this usecase, see + /// a variant of this operation that supports this use case, see /// `modifyValue(forKey:default:_:)`.) /// /// - Parameters: @@ -486,6 +486,7 @@ extension SparseSet { /// - Parameter transform: A closure that transforms a value. `transform` /// accepts each value of the sparse set as its parameter and returns a /// transformed value of the same or of a different type. + /// /// - Returns: A sparse set containing the keys and transformed values of /// this sparse set. /// From 91b08639b0b3a99acdb1e899d202a9ff6b49650e Mon Sep 17 00:00:00 2001 From: Edward Marchant Date: Fri, 20 Aug 2021 16:48:06 +0100 Subject: [PATCH 4/5] Prefer Array over ContiguousArray for public API --- .../SparseSetModule/SparseSet+Elements+SubSequence.swift | 2 +- Sources/SparseSetModule/SparseSet+Elements.swift | 4 ++-- Sources/SparseSetModule/SparseSet.swift | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift index 82977ae67..677ede973 100644 --- a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift +++ b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift @@ -36,7 +36,7 @@ extension SparseSet.Elements.SubSequence { /// - Complexity: O(1) @inlinable @inline(__always) - public var keys: ContiguousArray.SubSequence { + public var keys: Array.SubSequence { _base.keys[_bounds] } diff --git a/Sources/SparseSetModule/SparseSet+Elements.swift b/Sources/SparseSetModule/SparseSet+Elements.swift index 1bc4d14d0..31ed77a8c 100644 --- a/Sources/SparseSetModule/SparseSet+Elements.swift +++ b/Sources/SparseSetModule/SparseSet+Elements.swift @@ -49,8 +49,8 @@ extension SparseSet.Elements { /// - Complexity: O(1) @inlinable @inline(__always) - public var keys: ContiguousArray { - _base._dense._keys + public var keys: Array { + Array(_base._dense._keys) } /// A mutable collection view containing the values in this collection. diff --git a/Sources/SparseSetModule/SparseSet.swift b/Sources/SparseSetModule/SparseSet.swift index 95c244e40..674d4153c 100644 --- a/Sources/SparseSetModule/SparseSet.swift +++ b/Sources/SparseSetModule/SparseSet.swift @@ -32,13 +32,13 @@ public struct SparseSet where Key: FixedWidthInteger, Key.Stride == extension SparseSet { /// A read-only collection view for the keys contained in this sparse set, as - /// a `ContiguousArray`. + /// an `Array`. /// /// - Complexity: O(1) @inlinable @inline(__always) - public var keys: ContiguousArray { - _dense._keys + public var keys: Array { + Array(_dense._keys) } /// A mutable collection view containing the values in this sparse set. From 976a8c276338a00a436f620f9cb8d3c0f74f6d81 Mon Sep 17 00:00:00 2001 From: Edward Marchant Date: Fri, 20 Aug 2021 21:54:42 +0100 Subject: [PATCH 5/5] Rename symbols to follow stdlib's Leading Underscore Rule --- .../SparseSet+DenseStorage.swift | 42 +++++----- .../SparseSet+Elements+SubSequence.swift | 14 ++-- .../SparseSetModule/SparseSet+Elements.swift | 12 +-- .../SparseSetModule/SparseSet+Equatable.swift | 2 +- .../SparseSet+Initializers.swift | 18 ++--- .../SparseSet+Invariants.swift | 4 +- .../SparseSet+Partial MutableCollection.swift | 24 +++--- ...t+Partial RangeReplaceableCollection.swift | 22 ++--- .../SparseSetModule/SparseSet+Sequence.swift | 2 +- .../SparseSet+SparseStorage.swift | 32 ++++---- .../SparseSetModule/SparseSet+Values.swift | 20 ++--- Sources/SparseSetModule/SparseSet.swift | 80 +++++++++---------- Tests/SparseSetTests/SparseSet Tests.swift | 4 +- 13 files changed, 138 insertions(+), 138 deletions(-) diff --git a/Sources/SparseSetModule/SparseSet+DenseStorage.swift b/Sources/SparseSetModule/SparseSet+DenseStorage.swift index f033c0c2b..a73846f63 100644 --- a/Sources/SparseSetModule/SparseSet+DenseStorage.swift +++ b/Sources/SparseSetModule/SparseSet+DenseStorage.swift @@ -11,67 +11,67 @@ extension SparseSet { @usableFromInline - internal struct DenseStorage { + internal struct _DenseStorage { @usableFromInline - internal var _keys: ContiguousArray + internal var keys: ContiguousArray @usableFromInline - internal var _values: ContiguousArray + internal var values: ContiguousArray @usableFromInline - internal init(_keys: ContiguousArray, _values: ContiguousArray) { - self._keys = _keys - self._values = _values + internal init(keys: ContiguousArray, values: ContiguousArray) { + self.keys = keys + self.values = values } } } -extension SparseSet.DenseStorage { +extension SparseSet._DenseStorage { @usableFromInline internal init() { - self._keys = [] - self._values = [] + self.keys = [] + self.values = [] } @usableFromInline internal init(minimumCapacity: Int) { var keys: ContiguousArray = [] keys.reserveCapacity(minimumCapacity) - self._keys = keys + self.keys = keys var values: ContiguousArray = [] values.reserveCapacity(minimumCapacity) - self._values = values + self.values = values } } -extension SparseSet.DenseStorage { +extension SparseSet._DenseStorage { @inlinable @inline(__always) - public var isEmpty: Bool { _keys.isEmpty } + public var isEmpty: Bool { keys.isEmpty } @inlinable @inline(__always) - public var count: Int { _keys.count } + public var count: Int { keys.count } } -extension SparseSet.DenseStorage { +extension SparseSet._DenseStorage { @inlinable internal mutating func removeAll(keepingCapacity: Bool) { - _keys.removeAll(keepingCapacity: keepingCapacity) - _values.removeAll(keepingCapacity: keepingCapacity) + keys.removeAll(keepingCapacity: keepingCapacity) + values.removeAll(keepingCapacity: keepingCapacity) } } -extension SparseSet.DenseStorage { +extension SparseSet._DenseStorage { @inlinable internal mutating func append(value: Value, key: Key) { - _keys.append(key) - _values.append(value) + keys.append(key) + values.append(value) } @inlinable @discardableResult internal mutating func removeLast() -> (key: Key, value: Value) { - return (key: _keys.removeLast(), value: _values.removeLast()) + return (key: keys.removeLast(), value: values.removeLast()) } } diff --git a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift index 677ede973..d35b6f93d 100644 --- a/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift +++ b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift @@ -88,10 +88,10 @@ extension SparseSet.Elements.SubSequence: Sequence { @inlinable @inline(__always) - internal init(_ base: SparseSet.Elements.SubSequence) { - self._base = base._base - self._end = base._bounds.upperBound - self._index = base._bounds.lowerBound + internal init(_base: SparseSet.Elements.SubSequence) { + self._base = _base._base + self._end = _base._bounds.upperBound + self._index = _base._bounds.lowerBound } /// Advances to the next element and returns it, or `nil` if no next @@ -102,7 +102,7 @@ extension SparseSet.Elements.SubSequence: Sequence { public mutating func next() -> Element? { guard _index < _end else { return nil } defer { _index += 1 } - return (_base._dense._keys[_index], _base._dense._values[_index]) + return (_base._dense.keys[_index], _base._dense.values[_index]) } } @@ -110,7 +110,7 @@ extension SparseSet.Elements.SubSequence: Sequence { @inlinable @inline(__always) public func makeIterator() -> Iterator { - Iterator(self) + Iterator(_base: self) } } @@ -255,7 +255,7 @@ extension SparseSet.Elements.SubSequence: RandomAccessCollection { offsetBy distance: Int, limitedBy limit: Int ) -> Int? { - _base._dense._keys.index(i, offsetBy: distance, limitedBy: limit) + _base._dense.keys.index(i, offsetBy: distance, limitedBy: limit) } /// Returns the distance between two indices. diff --git a/Sources/SparseSetModule/SparseSet+Elements.swift b/Sources/SparseSetModule/SparseSet+Elements.swift index 31ed77a8c..62e5073dd 100644 --- a/Sources/SparseSetModule/SparseSet+Elements.swift +++ b/Sources/SparseSetModule/SparseSet+Elements.swift @@ -50,7 +50,7 @@ extension SparseSet.Elements { @inlinable @inline(__always) public var keys: Array { - Array(_base._dense._keys) + Array(_base._dense.keys) } /// A mutable collection view containing the values in this collection. @@ -234,7 +234,7 @@ extension SparseSet.Elements: RandomAccessCollection { offsetBy distance: Int, limitedBy limit: Int ) -> Int? { - _base._dense._keys.index(i, offsetBy: distance, limitedBy: limit) + _base._dense.keys.index(i, offsetBy: distance, limitedBy: limit) } /// Returns the distance between two indices. @@ -263,7 +263,7 @@ extension SparseSet.Elements: RandomAccessCollection { @inlinable @inline(__always) public subscript(position: Int) -> Element { - (_base._dense._keys[position], _base._dense._values[position]) + (_base._dense.keys[position], _base._dense.values[position]) } /// Accesses a contiguous subrange of the sparse set's elements. @@ -300,19 +300,19 @@ extension SparseSet.Elements: RandomAccessCollection { @inlinable @inline(__always) public func _failEarlyRangeCheck(_ index: Int, bounds: Range) { - _base._dense._keys._failEarlyRangeCheck(index, bounds: bounds) + _base._dense.keys._failEarlyRangeCheck(index, bounds: bounds) } @inlinable @inline(__always) public func _failEarlyRangeCheck(_ index: Int, bounds: ClosedRange) { - _base._dense._keys._failEarlyRangeCheck(index, bounds: bounds) + _base._dense.keys._failEarlyRangeCheck(index, bounds: bounds) } @inlinable @inline(__always) public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { - _base._dense._keys._failEarlyRangeCheck(range, bounds: bounds) + _base._dense.keys._failEarlyRangeCheck(range, bounds: bounds) } } diff --git a/Sources/SparseSetModule/SparseSet+Equatable.swift b/Sources/SparseSetModule/SparseSet+Equatable.swift index 7b51687f9..db4ba6449 100644 --- a/Sources/SparseSetModule/SparseSet+Equatable.swift +++ b/Sources/SparseSetModule/SparseSet+Equatable.swift @@ -18,6 +18,6 @@ extension SparseSet: Equatable where Value: Equatable { /// - Complexity: O(`min(left.count, right.count)`) @inlinable public static func ==(left: Self, right: Self) -> Bool { - left._dense._keys == right._dense._keys && left._dense._values == right._dense._values + left._dense.keys == right._dense.keys && left._dense.values == right._dense.values } } diff --git a/Sources/SparseSetModule/SparseSet+Initializers.swift b/Sources/SparseSetModule/SparseSet+Initializers.swift index 10172ac9a..e31960a93 100644 --- a/Sources/SparseSetModule/SparseSet+Initializers.swift +++ b/Sources/SparseSetModule/SparseSet+Initializers.swift @@ -12,11 +12,11 @@ extension SparseSet { @inlinable public init(minimumCapacity: Int, universeSize: Int? = nil) { - self._dense = DenseStorage(minimumCapacity: minimumCapacity) - let sparse = SparseStorage( + self._dense = _DenseStorage(minimumCapacity: minimumCapacity) + let sparse = _SparseStorage( withCapacity: universeSize ?? minimumCapacity, keys: EmptyCollection()) - self.__sparseBuffer = sparse._buffer + self.__sparseBuffer = sparse.buffer } @inlinable @@ -115,15 +115,15 @@ extension SparseSet { let values = ContiguousArray(values) precondition(keys.count == values.count, "Mismatching element counts between keys and values") - self._dense = DenseStorage(_keys: keys, _values: values) + self._dense = _DenseStorage(keys: keys, values: values) let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 - var sparse = SparseStorage(withCapacity: universeSize) + var sparse = _SparseStorage(withCapacity: universeSize) for (i, key) in keys.enumerated() { let existingIndex = sparse[key] precondition(existingIndex < 0 || existingIndex >= i || keys[existingIndex] != key, "Duplicate key: '\(key)'") sparse[key] = i } - __sparseBuffer = sparse._buffer + __sparseBuffer = sparse.buffer _checkInvariants() } } @@ -261,9 +261,9 @@ extension SparseSet { ) where Keys.Element == Key, Values.Element == Value { let keys = ContiguousArray(keys) let values = ContiguousArray(values) - self._dense = DenseStorage(_keys: keys, _values: values) - let sparse = SparseStorage(keys: keys) - __sparseBuffer = sparse._buffer + self._dense = _DenseStorage(keys: keys, values: values) + let sparse = _SparseStorage(keys: keys) + __sparseBuffer = sparse.buffer _checkInvariants() } diff --git a/Sources/SparseSetModule/SparseSet+Invariants.swift b/Sources/SparseSetModule/SparseSet+Invariants.swift index 1d328e0f3..ca4ab2008 100644 --- a/Sources/SparseSetModule/SparseSet+Invariants.swift +++ b/Sources/SparseSetModule/SparseSet+Invariants.swift @@ -15,13 +15,13 @@ extension SparseSet { @inline(never) internal func _checkInvariants() { // Check there are the same number of keys as values. - precondition(_dense._keys.count == _dense._values.count) + precondition(_dense.keys.count == _dense.values.count) // Check that the sparse storage buffer has sufficient capacity. let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 precondition(_sparse.capacity >= universeSize) // Check that the keys' positions in the dense storage agree with those // given by the sparse storage. - for (i, key) in _dense._keys.enumerated() { + for (i, key) in _dense.keys.enumerated() { precondition(_sparse[key] == i) } } diff --git a/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift b/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift index 1608fa2bb..16cfe3025 100644 --- a/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift +++ b/Sources/SparseSetModule/SparseSet+Partial MutableCollection.swift @@ -49,8 +49,8 @@ extension SparseSet { ) rethrows -> Int { _ensureUnique() - var low = _dense._keys.startIndex - var high = _dense._keys.endIndex + var low = _dense.keys.startIndex + var high = _dense.keys.endIndex while true { // Invariants at this point: @@ -61,7 +61,7 @@ extension SparseSet { // Find next element from `lo` that may not be in the right place. while true { if low == high { return low } - if try belongsInSecondPartition((_dense._keys[low], _dense._values[low])) { break } + if try belongsInSecondPartition((_dense.keys[low], _dense.values[low])) { break } low += 1 } @@ -69,7 +69,7 @@ extension SparseSet { while true { high -= 1 if low == high { return low } - if try !belongsInSecondPartition((_dense._keys[high], _dense._values[high])) { break } + if try !belongsInSecondPartition((_dense.keys[high], _dense.values[high])) { break } } // Swap the two elements. @@ -126,10 +126,10 @@ extension SparseSet { let temp = try self.sorted(by: areInIncreasingOrder) precondition(temp.count == self.count) temp.withUnsafeBufferPointer { source in - _dense._keys = ContiguousArray(source.lazy.map { $0.key }) - _dense._values = ContiguousArray(source.lazy.map { $0.value }) + _dense.keys = ContiguousArray(source.lazy.map { $0.key }) + _dense.values = ContiguousArray(source.lazy.map { $0.value }) } - _sparse.reindex(keys: _dense._keys) + _sparse.reindex(keys: _dense.keys) } } @@ -185,8 +185,8 @@ extension SparseSet { ) { guard count > 1 else { return } _ensureUnique() - var keys = _dense._keys - var values = _dense._values + var keys = _dense.keys + var values = _dense.values self = [:] var amount = keys.count var current = 0 @@ -208,8 +208,8 @@ extension SparseSet { @inlinable public mutating func reverse() { _ensureUnique() - _dense._keys.reverse() - _dense._values.reverse() - _sparse.reindex(keys: _dense._keys) + _dense.keys.reverse() + _dense.values.reverse() + _sparse.reindex(keys: _dense.keys) } } diff --git a/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift b/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift index f6bc51770..ccfcde848 100644 --- a/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift +++ b/Sources/SparseSetModule/SparseSet+Partial RangeReplaceableCollection.swift @@ -20,7 +20,7 @@ extension SparseSet { public mutating func removeAll(keepingCapacity: Bool = false) { _dense.removeAll(keepingCapacity: keepingCapacity) if !keepingCapacity { - _sparse = SparseStorage(withCapacity: 0) + _sparse = _SparseStorage(withCapacity: 0) } _checkInvariants() } @@ -59,7 +59,7 @@ extension SparseSet { public mutating func removeSubrange(_ bounds: Range) { guard !bounds.isEmpty else { return } defer { _checkInvariants() } - let finalSegment = bounds.endIndex ..< _dense._keys.endIndex + let finalSegment = bounds.endIndex ..< _dense.keys.endIndex let regionToMove: Range? if bounds.count <= finalSegment.count { regionToMove = finalSegment.endIndex - bounds.count ..< finalSegment.endIndex @@ -70,22 +70,22 @@ extension SparseSet { } if let regionToMove = regionToMove { _ensureUnique() - _dense._keys.withUnsafeMutableBufferPointer { ptr in + _dense.keys.withUnsafeMutableBufferPointer { ptr in ptr.baseAddress!.advanced(by: bounds.startIndex) .assign(from: ptr.baseAddress!.advanced(by: regionToMove.startIndex), count: regionToMove.count) } - _dense._values.withUnsafeMutableBufferPointer { ptr in + _dense.values.withUnsafeMutableBufferPointer { ptr in ptr.baseAddress!.advanced(by: bounds.startIndex) .assign(from: ptr.baseAddress!.advanced(by: regionToMove.startIndex), count: regionToMove.count) } - for (i, key) in _dense._keys[regionToMove].enumerated() { + for (i, key) in _dense.keys[regionToMove].enumerated() { _sparse[key] = bounds.startIndex + i } } - _dense._keys.removeLast(bounds.count) - _dense._values.removeLast(bounds.count) + _dense.keys.removeLast(bounds.count) + _dense.values.removeLast(bounds.count) } /// Removes the specified subrange of elements from the collection. @@ -103,7 +103,7 @@ extension SparseSet { public mutating func removeSubrange( _ bounds: R ) where R.Bound == Int { - removeSubrange(bounds.relative(to: _dense._keys)) + removeSubrange(bounds.relative(to: _dense.keys)) } /// Removes the last element of a non-empty sparse set. @@ -127,8 +127,8 @@ extension SparseSet { public mutating func removeLast(_ n: Int) { precondition(n >= 0, "Can't remove a negative number of elements") precondition(n <= count, "Can't remove more elements than there are in the collection") - _dense._keys.removeLast(n) - _dense._values.removeLast(n) + _dense.keys.removeLast(n) + _dense.values.removeLast(n) _checkInvariants() } @@ -180,7 +180,7 @@ extension SparseSet { ) rethrows { guard !isEmpty else { return } for i in (0 ..< count).reversed() { - let element = (key: _dense._keys[i], value: _dense._values[i]) + let element = (key: _dense.keys[i], value: _dense.values[i]) if try shouldBeRemoved(element) { _remove(at: i) } diff --git a/Sources/SparseSetModule/SparseSet+Sequence.swift b/Sources/SparseSetModule/SparseSet+Sequence.swift index 8cfa87c2a..3822d8ecb 100644 --- a/Sources/SparseSetModule/SparseSet+Sequence.swift +++ b/Sources/SparseSetModule/SparseSet+Sequence.swift @@ -39,7 +39,7 @@ extension SparseSet: Sequence { @inlinable public mutating func next() -> Element? { guard _position < _base._dense.count else { return nil } - let result = (_base._dense._keys[_position], _base._dense._values[_position]) + let result = (_base._dense.keys[_position], _base._dense.values[_position]) _position += 1 return result } diff --git a/Sources/SparseSetModule/SparseSet+SparseStorage.swift b/Sources/SparseSetModule/SparseSet+SparseStorage.swift index 17d570960..dcdf91b33 100644 --- a/Sources/SparseSetModule/SparseSet+SparseStorage.swift +++ b/Sources/SparseSetModule/SparseSet+SparseStorage.swift @@ -11,35 +11,35 @@ extension SparseSet { @usableFromInline - internal struct SparseStorage { + internal struct _SparseStorage { @usableFromInline - var _buffer: Buffer + var buffer: Buffer @inlinable @inline(__always) init(_ buffer: Buffer) { - self._buffer = buffer + self.buffer = buffer } @inlinable internal init(withCapacity capacity: Int, keys: C) where C.Element == Key { - self._buffer = Buffer.bufferWith(capacity: capacity, keys: keys) + self.buffer = Buffer.bufferWith(capacity: capacity, keys: keys) } @inlinable internal init(withCapacity capacity: Int) { - self._buffer = Buffer.bufferWith(capacity: capacity, keys: EmptyCollection()) + self.buffer = Buffer.bufferWith(capacity: capacity, keys: EmptyCollection()) } @inlinable internal init(keys: C) where C.Element == Key { let universeSize: Int = keys.max().map { Int($0) + 1 } ?? 0 - self._buffer = Buffer.bufferWith(capacity: universeSize, keys: keys) + self.buffer = Buffer.bufferWith(capacity: universeSize, keys: keys) } } } -extension SparseSet.SparseStorage { +extension SparseSet._SparseStorage { @usableFromInline internal struct Header { @usableFromInline @@ -52,7 +52,7 @@ extension SparseSet.SparseStorage { } } -extension SparseSet.SparseStorage { +extension SparseSet._SparseStorage { @usableFromInline internal final class Buffer: ManagedBuffer { /// Create a buffer populated with the given key data. @@ -118,10 +118,10 @@ extension SparseSet.SparseStorage { } } -extension SparseSet.SparseStorage { +extension SparseSet._SparseStorage { @inlinable internal var capacity: Int { - _buffer.header.capacity + buffer.header.capacity } /// Resize this storage to a new capacity. @@ -132,7 +132,7 @@ extension SparseSet.SparseStorage { /// - Parameter newCapacity: The new capacity of the storage. @usableFromInline internal mutating func resize(to newCapacity: Int) { - _buffer = Buffer.bufferWith(capacity: newCapacity, contentsOf: _buffer) + buffer = Buffer.bufferWith(capacity: newCapacity, contentsOf: buffer) } /// Resize this storage to a new capacity. @@ -147,7 +147,7 @@ extension SparseSet.SparseStorage { /// - keys: A collection of keys. @usableFromInline internal mutating func resize(to newCapacity: Int, keys: C) where C.Element == Key { - _buffer = Buffer.bufferWith(capacity: newCapacity, keys: keys) + buffer = Buffer.bufferWith(capacity: newCapacity, keys: keys) } /// Rebuilds the index data for the given keys. @@ -156,7 +156,7 @@ extension SparseSet.SparseStorage { @inlinable internal mutating func reindex(keys: C) where C.Element == Key { assert(capacity >= keys.max().map { Int($0) + 1 } ?? 0) - _buffer.withUnsafeMutablePointerToElements { ptr in + buffer.withUnsafeMutablePointerToElements { ptr in for(i, key) in keys.enumerated() { let index = Int(key) precondition(index >= 0, "Negative key") @@ -167,16 +167,16 @@ extension SparseSet.SparseStorage { } } -extension SparseSet.SparseStorage { +extension SparseSet._SparseStorage { @inlinable internal subscript(position: Key) -> Int { get { - _buffer.withUnsafeMutablePointerToElements { ptr in + buffer.withUnsafeMutablePointerToElements { ptr in Int(ptr[Int(position)]) } } set { - _buffer.withUnsafeMutablePointerToElements { ptr in + buffer.withUnsafeMutablePointerToElements { ptr in ptr[Int(position)] = Key(newValue) } } diff --git a/Sources/SparseSetModule/SparseSet+Values.swift b/Sources/SparseSetModule/SparseSet+Values.swift index e14dfb7ce..a5b94c3b7 100644 --- a/Sources/SparseSetModule/SparseSet+Values.swift +++ b/Sources/SparseSetModule/SparseSet+Values.swift @@ -31,7 +31,7 @@ extension SparseSet.Values { @inlinable @inline(__always) public var elements: Array { - Array(_base._dense._values) + Array(_base._dense.values) } } @@ -60,7 +60,7 @@ extension SparseSet.Values { public func withUnsafeBufferPointer( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R { - try _base._dense._values.withUnsafeBufferPointer(body) + try _base._dense.values.withUnsafeBufferPointer(body) } /// Calls the given closure with a pointer to the collection's mutable @@ -88,7 +88,7 @@ extension SparseSet.Values { public mutating func withUnsafeMutableBufferPointer( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R { - try _base._dense._values.withUnsafeMutableBufferPointer(body) + try _base._dense.values.withUnsafeMutableBufferPointer(body) } } @@ -231,7 +231,7 @@ extension SparseSet.Values: RandomAccessCollection { offsetBy distance: Int, limitedBy limit: Int ) -> Int? { - _base._dense._values.index(i, offsetBy: distance, limitedBy: limit) + _base._dense.values.index(i, offsetBy: distance, limitedBy: limit) } /// Returns the distance between two indices. @@ -265,7 +265,7 @@ extension SparseSet.Values: RandomAccessCollection { public func withContiguousStorageIfAvailable( _ body: (UnsafeBufferPointer) throws -> R ) rethrows -> R? { - try _base._dense._values.withUnsafeBufferPointer(body) + try _base._dense.values.withUnsafeBufferPointer(body) } } @@ -281,10 +281,10 @@ extension SparseSet.Values: MutableCollection { @inline(__always) public subscript(position: Int) -> Value { get { - _base._dense._values[position] + _base._dense.values[position] } _modify { - yield &_base._dense._values[position] + yield &_base._dense.values[position] } } @@ -304,7 +304,7 @@ extension SparseSet.Values: MutableCollection { @inlinable @inline(__always) public mutating func swapAt(_ i: Int, _ j: Int) { - _base._dense._values.swapAt(i, j) + _base._dense.values.swapAt(i, j) } /// Reorders the elements of the collection such that all the elements that @@ -333,7 +333,7 @@ extension SparseSet.Values: MutableCollection { public mutating func partition( by belongsInSecondPartition: (Value) throws -> Bool ) rethrows -> Int { - try _base._dense._values.partition(by: belongsInSecondPartition) + try _base._dense.values.partition(by: belongsInSecondPartition) } /// Call `body(b)`, where `b` is an unsafe buffer pointer to the collection's @@ -358,7 +358,7 @@ extension SparseSet.Values: MutableCollection { public mutating func withContiguousMutableStorageIfAvailable( _ body: (inout UnsafeMutableBufferPointer) throws -> R ) rethrows -> R? { - try _base._dense._values.withUnsafeMutableBufferPointer(body) + try _base._dense.values.withUnsafeMutableBufferPointer(body) } } diff --git a/Sources/SparseSetModule/SparseSet.swift b/Sources/SparseSetModule/SparseSet.swift index 674d4153c..f1629b758 100644 --- a/Sources/SparseSetModule/SparseSet.swift +++ b/Sources/SparseSetModule/SparseSet.swift @@ -11,21 +11,21 @@ public struct SparseSet where Key: FixedWidthInteger, Key.Stride == Int { @usableFromInline - internal var _dense: DenseStorage + internal var _dense: _DenseStorage @inlinable @inline(__always) - internal var _sparse: SparseStorage { + internal var _sparse: _SparseStorage { get { - SparseStorage(__sparseBuffer) + _SparseStorage(__sparseBuffer) } set { - __sparseBuffer = newValue._buffer + __sparseBuffer = newValue.buffer } } @usableFromInline - internal var __sparseBuffer: SparseStorage.Buffer + internal var __sparseBuffer: _SparseStorage.Buffer } // MARK: - @@ -38,7 +38,7 @@ extension SparseSet { @inlinable @inline(__always) public var keys: Array { - Array(_dense._keys) + Array(_dense.keys) } /// A mutable collection view containing the values in this sparse set. @@ -113,7 +113,7 @@ extension SparseSet { @inlinable @inline(__always) public subscript(offset offset: Int) -> Element { - (_dense._keys[offset], _dense._values[offset]) + (_dense.keys[offset], _dense.values[offset]) } } @@ -137,8 +137,8 @@ extension SparseSet { @inlinable public mutating func updateValue(_ value: Value, forKey key: Key) -> Value? { if let existingIndex = _find(key: key) { - let existingValue = _dense._values[existingIndex] - _dense._values[existingIndex] = value + let existingValue = _dense.values[existingIndex] + _dense.values[existingIndex] = value return existingValue } _append(value: value, key: key) @@ -170,11 +170,11 @@ extension SparseSet { _ body: (inout Value) throws -> R ) rethrows -> R { if let existingIndex = _find(key: key) { - return try body(&_dense._values[existingIndex]) + return try body(&_dense.values[existingIndex]) } _append(value: defaultValue(), key: key) let i = _dense.count - 1 - return try body(&_dense._values[i]) + return try body(&_dense.values[i]) } /// Removes the given key and its associated value from the sparse set. @@ -227,7 +227,7 @@ extension SparseSet { public subscript(key: Key) -> Value? { get { guard let index = _find(key: key) else { return nil } - return _dense._values[index] + return _dense.values[index] } set { // We have a separate `set` in addition to `_modify` in hopes of getting @@ -236,7 +236,7 @@ extension SparseSet { let index = _find(key: key) switch (index, newValue) { case let (index?, newValue?): // Assign - _dense._values[index] = newValue + _dense.values[index] = newValue case let (index?, nil): // Remove _remove(at: index) case let (nil, newValue?): // Insert @@ -253,23 +253,23 @@ extension SparseSet { // or swap keys too depending on whether we are are assigning or removing. var value: Value? = nil if let index = index { - _dense._values.swapAt(index, _dense._values.count - 1) - value = _dense._values.removeLast() + _dense.values.swapAt(index, _dense.values.count - 1) + value = _dense.values.removeLast() } defer { switch (index, value) { case let (index?, value?): // Assign - _dense._values.append(value) - _dense._values.swapAt(index, _dense._values.count - 1) + _dense.values.append(value) + _dense.values.swapAt(index, _dense.values.count - 1) case let (index?, nil): // Remove _ensureUnique() - if index < _dense._values.count { - let shiftedKey = _dense._keys.removeLast() - _dense._keys[index] = shiftedKey + if index < _dense.values.count { + let shiftedKey = _dense.keys.removeLast() + _dense.keys[index] = shiftedKey _sparse[shiftedKey] = index } else { - _dense._keys.removeLast() + _dense.keys.removeLast() } case let (nil, value?): // Insert _append(value: value, key: key) @@ -317,7 +317,7 @@ extension SparseSet { ) -> Value { get { guard let index = _find(key: key) else { return defaultValue() } - return _dense._values[index] + return _dense.values[index] } _modify { let index: Int @@ -329,12 +329,12 @@ extension SparseSet { _append(value: defaultValue(), key: key) } - var value: Value = _dense._values.withUnsafeMutableBufferPointer { buffer in + var value: Value = _dense.values.withUnsafeMutableBufferPointer { buffer in assert(index < buffer.count) return (buffer.baseAddress! + index).move() } defer { - _dense._values.withUnsafeMutableBufferPointer { buffer in + _dense.values.withUnsafeMutableBufferPointer { buffer in assert(index < buffer.count) (buffer.baseAddress! + index).initialize(to: value) } @@ -367,7 +367,7 @@ extension SparseSet { ) rethrows where S.Element == (key: Key, value: Value) { for (key, value) in keysAndValues { if let index = _find(key: key) { - try { $0 = try combine($0, value) }(&_dense._values[index]) + try { $0 = try combine($0, value) }(&_dense.values[index]) } else { _append(value: value, key: key) } @@ -496,8 +496,8 @@ extension SparseSet { _ transform: (Value) throws -> T ) rethrows -> SparseSet { SparseSet( - uniqueKeys: _dense._keys, - values: ContiguousArray(try _dense._values.map(transform))) + uniqueKeys: _dense.keys, + values: ContiguousArray(try _dense.values.map(transform))) } /// Returns a new sparse set containing only the key-value pairs that have @@ -538,7 +538,7 @@ extension SparseSet { /// of keys in the sparse set is small relative to the universe size. This /// constant is the density threshold which determines which strategy we use. @usableFromInline - internal static var sparseDensityThresholdForCopyAll: Double { 0.1 } + internal static var _sparseDensityThresholdForCopyAll: Double { 0.1 } /// Ensures that the sparse data storage buffer is uniquely referenced, /// copying it if necessary. @@ -550,10 +550,10 @@ extension SparseSet { internal mutating func _ensureUnique() { if !isKnownUniquelyReferenced(&__sparseBuffer) { let density = Double(_dense.count) / Double(_sparse.capacity) - if density > SparseSet.sparseDensityThresholdForCopyAll { + if density > SparseSet._sparseDensityThresholdForCopyAll { __sparseBuffer = .bufferWith(contentsOf: __sparseBuffer) } else { - __sparseBuffer = .bufferWith(capacity: _sparse.capacity, keys: _dense._keys) + __sparseBuffer = .bufferWith(capacity: _sparse.capacity, keys: _dense.keys) } } } @@ -594,7 +594,7 @@ extension SparseSet { guard index >= 0 && index < _dense.count else { return nil } - if _dense._keys[index] == key { + if _dense.keys[index] == key { return index } return nil @@ -615,11 +615,11 @@ extension SparseSet { defer { _checkInvariants() } if index < _dense.count - 1 { _ensureUnique() - let existingKey = _dense._keys[index] - let existingValue = _dense._values[index] + let existingKey = _dense.keys[index] + let existingValue = _dense.values[index] let (shiftedKey, shiftedValue) = _dense.removeLast() - _dense._keys[index] = shiftedKey - _dense._values[index] = shiftedValue + _dense.keys[index] = shiftedKey + _dense.values[index] = shiftedValue _sparse[shiftedKey] = index return (existingKey, existingValue) } else { @@ -632,11 +632,11 @@ extension SparseSet { guard i != j else { return } defer { _checkInvariants() } _ensureUnique() - _dense._values.swapAt(i, j) - let keyA = _dense._keys[i] - let keyB = _dense._keys[j] - _dense._keys[i] = keyB - _dense._keys[j] = keyA + _dense.values.swapAt(i, j) + let keyA = _dense.keys[i] + let keyB = _dense.keys[j] + _dense.keys[i] = keyB + _dense.keys[j] = keyA _sparse[keyA] = j _sparse[keyB] = i } diff --git a/Tests/SparseSetTests/SparseSet Tests.swift b/Tests/SparseSetTests/SparseSet Tests.swift index 2105ac45b..2647719e4 100644 --- a/Tests/SparseSetTests/SparseSet Tests.swift +++ b/Tests/SparseSetTests/SparseSet Tests.swift @@ -23,8 +23,8 @@ final class SparseSetTests: CollectionTestCase { func test_init_minimumCapacity_universeSize() { let s = SparseSet(minimumCapacity: 1_000, universeSize: 10_000) - expectGreaterThanOrEqual(s._dense._keys.capacity, 1_000) - expectGreaterThanOrEqual(s._dense._values.capacity, 1_000) + expectGreaterThanOrEqual(s._dense.keys.capacity, 1_000) + expectGreaterThanOrEqual(s._dense.values.capacity, 1_000) expectEqual(s._sparse.capacity, 10_000) }