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..a73846f63
--- /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..d35b6f93d
--- /dev/null
+++ b/Sources/SparseSetModule/SparseSet+Elements+SubSequence.swift
@@ -0,0 +1,323 @@
+//===----------------------------------------------------------------------===//
+//
+// 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: Array.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(_base: 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..62e5073dd
--- /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: Array {
+ Array(_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..db4ba6449
--- /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..e31960a93
--- /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..ca4ab2008
--- /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..16cfe3025
--- /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..ccfcde848
--- /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..3822d8ecb
--- /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..dcdf91b33
--- /dev/null
+++ b/Sources/SparseSetModule/SparseSet+SparseStorage.swift
@@ -0,0 +1,184 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+ @inlinable
+ internal init(capacity: Int) {
+ self.capacity = capacity
+ }
+ }
+}
+
+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.
+ @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(
+ 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.
+ @inlinable
+ 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.
+ @inlinable
+ 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 storage 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 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
+ /// 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 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)
+ 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..a5b94c3b7
--- /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..f1629b758
--- /dev/null
+++ b/Sources/SparseSetModule/SparseSet.swift
@@ -0,0 +1,643 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+ /// an `Array`.
+ ///
+ /// - Complexity: O(1)
+ @inlinable
+ @inline(__always)
+ public var keys: Array {
+ Array(_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 use case, 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..2647719e4
--- /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?")
+ }
+ }
+ }
+ }
+ }
+}