Skip to content

Commit 8de0b4f

Browse files
committed
Implement primary SortedSet methods and various refactors.
Key Changes: - SortedSet - Implement Partial RangeReplaceableCollection - Implement ExpressibleByArrayLiteral - Implement init<S: Sequence>(_: S) - Implement Set methods: - update(with:) / insert(_:) / contains(_:) - SortedDictionary - Implement SortedDictionary.Values: BidirectionalCollection - Implement SortedDictionary.Keys: BidirectionalCollection - _BTree - Implement Partial RangeReplaceableCollection - Implement Sequence - Add high performance linear iterator Minor Changes: - Benchmarks - Clean up benchmarks - _Node.UnsafeHandle - @Freeze enum InsertionResult - _Node.Storage - Use UnsafeMutablePointer instead of ManagedBuffer Refactors: - Various refactors for method signatures and documentation.
1 parent 761950d commit 8de0b4f

32 files changed

+1821
-814
lines changed

Benchmarks/Benchmarks/SortedDictionaryBenchmarks.swift

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,60 @@ import CollectionsBenchmark
1313

1414
extension Benchmark {
1515
public mutating func addSortedDictionaryBenchmarks() {
16-
self.add(
17-
title: "SortedDictionary<Int, Int> init(uniqueKeysWithValues:)",
18-
input: [Int].self
19-
) { input in
20-
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
21-
22-
return { timer in
23-
blackHole(SortedDictionary(uniqueKeysWithValues: keysAndValues))
24-
}
25-
}
26-
27-
self.add(
28-
title: "SortedDictionary<Int, Int> subscript, append",
29-
input: [Int].self
30-
) { input in
31-
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
32-
var sortedDictionary = SortedDictionary<Int, Int>()
33-
34-
return { timer in
35-
for (key, value) in keysAndValues {
36-
sortedDictionary[key] = value
37-
}
38-
blackHole(sortedDictionary)
39-
}
40-
}
16+
// self.add(
17+
// title: "SortedDictionary<Int, Int> init(uniqueKeysWithValues:)",
18+
// input: [Int].self
19+
// ) { input in
20+
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
21+
//
22+
// return { timer in
23+
// blackHole(SortedDictionary(uniqueKeysWithValues: keysAndValues))
24+
// }
25+
// }
26+
//
27+
// self.add(
28+
// title: "SortedDictionary<Int, Int> subscript, append",
29+
// input: [Int].self
30+
// ) { input in
31+
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
32+
// var sortedDictionary = SortedDictionary<Int, Int>()
33+
//
34+
// return { timer in
35+
// for (key, value) in keysAndValues {
36+
// sortedDictionary[key] = value
37+
// }
38+
// blackHole(sortedDictionary)
39+
// }
40+
// }
41+
//
42+
// self.add(
43+
// title: "SortedDictionary<Int, Int> subscript, successful lookups",
44+
// input: ([Int], [Int]).self
45+
// ) { input, lookups in
46+
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
47+
// let sortedDictionary = SortedDictionary<Int, Int>(uniqueKeysWithValues: keysAndValues)
48+
//
49+
// return { timer in
50+
// for key in lookups {
51+
// precondition(sortedDictionary._root.contains(key: key))
52+
// }
53+
// }
54+
// }
4155

4256
self.add(
43-
title: "SortedDictionary<Int, Int> subscript, successful lookups",
57+
title: "SortedDictionary<Int, Int>._BTree subscript, successful lookups",
4458
input: ([Int], [Int]).self
4559
) { input, lookups in
4660
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
47-
let sortedDictionary = SortedDictionary<Int, Int>(uniqueKeysWithValues: keysAndValues)
61+
var tree = _BTree<Int, Int>()
62+
63+
for (k, v) in keysAndValues {
64+
tree.setAnyValue(v, forKey: k)
65+
}
4866

4967
return { timer in
5068
for key in lookups {
51-
precondition(sortedDictionary._root.contains(key: key))
69+
precondition(tree.contains(key: key))
5270
}
5371
}
5472
}

Sources/SortedCollections/BTree/_BTree+BidirectionalCollection.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ extension _BTree: BidirectionalCollection {
3737
let path = UnsafePath(
3838
node: currentNode,
3939
slot: 0,
40-
childSlots: PackedOffsetList(depth: depth),
40+
childSlots: FixedSizeArray(repeating: 0, depth: depth),
4141
offset: 0
4242
)
4343

@@ -78,7 +78,7 @@ extension _BTree: BidirectionalCollection {
7878
}
7979

8080
let shouldSeekWithinLeaf = Node(path.node).read({
81-
$0.isLeaf && _fastPath(path.slot + 1 < $0.numElements)
81+
$0.isLeaf && _fastPath(path.slot + 1 < $0.elementCount)
8282
})
8383

8484
if shouldSeekWithinLeaf {
@@ -238,13 +238,13 @@ extension _BTree {
238238
/// - Returns: If found, returns a path to the element. Otherwise, `nil`.
239239
@inlinable
240240
internal func anyIndex(forKey key: Key) -> Index? {
241-
var childSlots = PackedOffsetList()
241+
var childSlots = UnsafePath.Offsets(repeating: 0)
242242
var node: Node? = self.root
243243

244244
while let currentNode = node {
245245
let path: UnsafePath? = currentNode.read { handle in
246-
let keySlot = handle.firstSlot(for: key)
247-
if keySlot < handle.numElements && handle[keyAt: keySlot] == key {
246+
let keySlot = handle.startSlot(forKey: key)
247+
if keySlot < handle.elementCount && handle[keyAt: keySlot] == key {
248248
return UnsafePath(node: currentNode.storage, slot: keySlot, childSlots: childSlots, offset: 0)
249249
} else {
250250
if handle.isLeaf {
@@ -278,16 +278,16 @@ extension _BTree {
278278
return Index(nil, forTree: self)
279279
}
280280

281-
var childSlots = PackedOffsetList()
281+
var childSlots = UnsafePath.Offsets(repeating: 0)
282282

283283
var node: _Node = self.root
284284
var startIndex = 0
285285

286286
while !node.read({ $0.isLeaf }) {
287287
let internalPath: UnsafePath? = node.read { handle in
288-
for childSlot in 0..<handle.numChildren {
288+
for childSlot in 0..<handle.childCount {
289289
let child = handle[childAt: childSlot]
290-
let endIndex = startIndex + child.read({ $0.numTotalElements })
290+
let endIndex = startIndex + child.read({ $0.subtreeCount })
291291

292292
if offset < endIndex {
293293
childSlots.append(UInt16(childSlot))
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
extension _BTree {
13+
/// Filters a B-Tree on a predicate, returning a new tree.
14+
///
15+
/// - Complexity: O(`n log n`) where `n` is the number of key-value pairs in the
16+
/// sorted dictionary.
17+
@inlinable
18+
@inline(__always)
19+
public func filter(
20+
_ isIncluded: (Element) throws -> Bool
21+
) rethrows -> _BTree {
22+
// TODO: optimize implementation to O(n)
23+
var newTree: _BTree = _BTree()
24+
for element in self where try isIncluded(element) {
25+
newTree.setAnyValue(element.value, forKey: element.key)
26+
}
27+
return newTree
28+
}
29+
30+
31+
// MARK: Last Removal
32+
33+
/// Removes the first element of a tree, if it exists.
34+
///
35+
/// - Returns: The moved first element of the tree.
36+
@inlinable
37+
@discardableResult
38+
internal mutating func popLast() -> Element? {
39+
invalidateIndices()
40+
41+
if self.count == 0 { return nil }
42+
43+
let removedElement = self.root.update { $0.popLastElement() }
44+
self._balanceRoot()
45+
return removedElement
46+
}
47+
48+
@inlinable
49+
@inline(__always)
50+
@discardableResult
51+
public mutating func removeLast() -> Element {
52+
if let value = self.popLast() {
53+
return value
54+
} else {
55+
preconditionFailure("Can't remove last element from an empty collection")
56+
}
57+
}
58+
59+
@inlinable
60+
@inline(__always)
61+
public mutating func removeLast(_ k: Int) {
62+
assert(0 <= k && k < self.count, "Can't remove more items from a collection than it contains")
63+
for _ in 0..<k {
64+
self.removeLast()
65+
}
66+
}
67+
68+
// MARK: First Removal
69+
/// Removes the first element of a tree, if it exists.
70+
///
71+
/// - Returns: The moved first element of the tree.
72+
@inlinable
73+
@inline(__always)
74+
@discardableResult
75+
internal mutating func popFirst() -> Element? {
76+
invalidateIndices()
77+
78+
if self.count == 0 { return nil }
79+
80+
let removedElement = self.root.update { $0.popFirstElement() }
81+
self._balanceRoot()
82+
return removedElement
83+
}
84+
85+
@inlinable
86+
@inline(__always)
87+
@discardableResult
88+
public mutating func removeFirst() -> Element {
89+
if let value = self.popFirst() {
90+
return value
91+
} else {
92+
preconditionFailure("Can't remove first element from an empty collection")
93+
}
94+
}
95+
96+
@inlinable
97+
@inline(__always)
98+
public mutating func removeFirst(_ k: Int) {
99+
assert(0 <= k && k < self.count, "Can't remove more items from a collection than it contains")
100+
for _ in 0..<k {
101+
self.removeFirst()
102+
}
103+
}
104+
105+
// MARK: Offset Removal
106+
/// Removes the element of a tree at a given offset.
107+
///
108+
/// - Parameter offset: the offset which must be in-bounds.
109+
/// - Returns: The moved element of the tree
110+
@inlinable
111+
@inline(__always)
112+
@discardableResult
113+
internal mutating func remove(at offset: Int) -> Element {
114+
invalidateIndices()
115+
let removedElement = self.root.update { $0.remove(at: offset) }
116+
self._balanceRoot()
117+
return removedElement
118+
}
119+
120+
@inlinable
121+
@inline(__always)
122+
internal mutating func removeAll() {
123+
invalidateIndices()
124+
// TODO: potentially use empty storage class.
125+
self.root = _Node(withCapacity: _BTree.defaultLeafCapacity, isLeaf: true)
126+
}
127+
128+
// MARK: Index Removal
129+
/// Removes the element of a tree at a given index.
130+
///
131+
/// - Parameter index: a valid index of the tree, not `endIndex`
132+
/// - Returns: The moved element of the tree
133+
@inlinable
134+
@inline(__always)
135+
@discardableResult
136+
internal mutating func remove(at index: Index) -> Element {
137+
invalidateIndices()
138+
guard let path = index.path else { preconditionFailure("Index out of bounds.") }
139+
return self.remove(at: path.offset)
140+
}
141+
142+
/// Removes the elements in the specified subrange from the collection.
143+
@inlinable
144+
internal mutating func removeSubrange(_ bounds: Range<Index>) {
145+
guard let startPath = bounds.lowerBound.path else { preconditionFailure("Index out of bounds.") }
146+
guard let _ = bounds.upperBound.path else { preconditionFailure("Index out of bounds.") }
147+
148+
let rangeSize = self.distance(from: bounds.lowerBound, to: bounds.upperBound)
149+
let startOffset = startPath.offset
150+
151+
for _ in 0..<rangeSize {
152+
self.remove(at: startOffset)
153+
}
154+
}
155+
156+
}

0 commit comments

Comments
 (0)