Skip to content

Commit cf67226

Browse files
committed
Final touch up, polishes, and optimizations.
Key Changes: - Add duplicate handling functionality to _BTree.Builder - Remove transient allocation in SortedDictionary.values _modify - Add missing SortedDictionary.Values.subscript _modify - Add SortedSet(sortedElements:) - Add optimized SetAlgebra implementations - Remove remaining high-level offset APIs - Various optimizations - Expand internal documentation Minor Changes: - Add ability to create dummy node - _BTree.Builder.append(key: Key) for sets. - Fix bug where findAnyIndex(forKey:) didn't calculate the offset. - Remove old, unused files - findAnyCursor -> takeCursor (to emphasize consuming nature) - Add takeCursor(at: Index) - Optimize init(grouping:by:) to use modifyValue to reduce CoW Optimizations: - Use dummy node for transient _modify state rather than allocating new buffer
1 parent e67460c commit cf67226

30 files changed

+680
-271
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@
132132
ReferencedContainer = "container:">
133133
</BuildableReference>
134134
</BuildActionEntry>
135+
<BuildActionEntry
136+
buildForTesting = "YES"
137+
buildForRunning = "YES"
138+
buildForProfiling = "YES"
139+
buildForArchiving = "YES"
140+
buildForAnalyzing = "YES">
141+
<BuildableReference
142+
BuildableIdentifier = "primary"
143+
BlueprintIdentifier = "SortedCollections"
144+
BuildableName = "SortedCollections"
145+
BlueprintName = "SortedCollections"
146+
ReferencedContainer = "container:">
147+
</BuildableReference>
148+
</BuildActionEntry>
135149
</BuildActionEntries>
136150
</BuildAction>
137151
<TestAction
@@ -180,6 +194,16 @@
180194
ReferencedContainer = "container:">
181195
</BuildableReference>
182196
</TestableReference>
197+
<TestableReference
198+
skipped = "NO">
199+
<BuildableReference
200+
BuildableIdentifier = "primary"
201+
BlueprintIdentifier = "SortedCollectionsTests"
202+
BuildableName = "SortedCollectionsTests"
203+
BlueprintName = "SortedCollectionsTests"
204+
ReferencedContainer = "container:">
205+
</BuildableReference>
206+
</TestableReference>
183207
</Testables>
184208
</TestAction>
185209
<LaunchAction

.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-benchmark.xcscheme

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,22 @@
5858
ReferencedContainer = "container:">
5959
</BuildableReference>
6060
</TestableReference>
61+
<TestableReference
62+
skipped = "NO">
63+
<BuildableReference
64+
BuildableIdentifier = "primary"
65+
BlueprintIdentifier = "SortedCollectionsTests"
66+
BuildableName = "SortedCollectionsTests"
67+
BlueprintName = "SortedCollectionsTests"
68+
ReferencedContainer = "container:">
69+
</BuildableReference>
70+
</TestableReference>
6171
</Testables>
6272
</TestAction>
6373
<LaunchAction
64-
buildConfiguration = "Debug"
65-
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
66-
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
74+
buildConfiguration = "Release"
75+
selectedDebuggerIdentifier = ""
76+
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
6777
launchStyle = "0"
6878
useCustomWorkingDirectory = "NO"
6979
ignoresPersistentStateOnLaunch = "NO"
@@ -80,6 +90,32 @@
8090
ReferencedContainer = "container:">
8191
</BuildableReference>
8292
</BuildableProductRunnable>
93+
<CommandLineArguments>
94+
<CommandLineArgument
95+
argument = "run"
96+
isEnabled = "YES">
97+
</CommandLineArgument>
98+
<CommandLineArgument
99+
argument = "-f"
100+
isEnabled = "YES">
101+
</CommandLineArgument>
102+
<CommandLineArgument
103+
argument = "SortedDictionary.*init"
104+
isEnabled = "YES">
105+
</CommandLineArgument>
106+
<CommandLineArgument
107+
argument = "/Users/vihan/Documents/swift-collections/output"
108+
isEnabled = "YES">
109+
</CommandLineArgument>
110+
<CommandLineArgument
111+
argument = "--mode"
112+
isEnabled = "YES">
113+
</CommandLineArgument>
114+
<CommandLineArgument
115+
argument = "replace-all"
116+
isEnabled = "YES">
117+
</CommandLineArgument>
118+
</CommandLineArguments>
83119
</LaunchAction>
84120
<ProfileAction
85121
buildConfiguration = "Release"

Sources/SortedCollections/BTree/_BTree+BidirectionalCollection.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ extension _BTree: BidirectionalCollection {
2626
@inline(__always)
2727
internal var isEmpty: Bool { self.count == 0 }
2828

29-
// TODO: look into O(1) implementation
29+
// TODO: further consider O(1) implementation
3030
/// Locates the first element and returns a proper path to it, or nil if the BTree is empty.
3131
/// - Complexity: O(`log n`)
3232
@inlinable
@@ -92,6 +92,8 @@ extension _BTree: BidirectionalCollection {
9292
precondition(index.offset < self.count,
9393
"Attempt to advance out of collection bounds.")
9494

95+
// TODO: this might be redundant given the fact the same (but generalized)
96+
// logic is implemented in offsetBy
9597
let shouldSeekWithinLeaf = index.readNode {
9698
$0.isLeaf && _fastPath(index.slot + 1 < $0.elementCount)
9799
}
@@ -123,7 +125,6 @@ extension _BTree: BidirectionalCollection {
123125
internal func formIndex(before index: inout Index) {
124126
precondition(!self.isEmpty && index.offset != 0,
125127
"Attempt to advance out of collection bounds.")
126-
// TODO: implement more efficient logic to better move through the tree
127128
self.formIndex(&index, offsetBy: -1)
128129
}
129130

Sources/SortedCollections/BTree/_BTree+Partial RangeReplaceableCollection.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ extension _BTree {
1919
public func filter(
2020
_ isIncluded: (Element) throws -> Bool
2121
) rethrows -> _BTree {
22-
// TODO: optimize implementation to O(n)
2322
var builder = Builder()
2423
for element in self where try isIncluded(element) {
2524
builder.append(element)

Sources/SortedCollections/BTree/_BTree+Sequence.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ extension _BTree: Sequence {
9494
return
9595
}
9696

97-
// TODO: maybe convert to unowned(unsafe)
9897
var nextNode: Unmanaged? = .passUnretained(tree.root.storage)
9998
while let node = nextNode {
10099
self.path.append(node)

Sources/SortedCollections/BTree/_BTree+Testing.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

Sources/SortedCollections/BTree/_BTree+UnsafeCursor.swift

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,73 @@ extension _BTree {
379379
/// This 'consumes' the tree, however it expects the callee to retain the root of the tree for the duration of
380380
/// the cursors lifetime.
381381
///
382-
/// - Parameters:
383-
/// - key: The key to search for
384-
/// - mutable: whether a mutable cursor should be returned.
382+
/// - Parameter key: The key to search for
385383
/// - Returns: A `cursor` to the key or where the key should be inserted, and a `found`
386384
/// parameter indicating whether or not the key exists within the tree, iff `found` is true, the cursor
387385
/// is concrete.
388386
/// - Complexity: O(`log n`)
389387
@inlinable
390-
internal mutating func findAnyCursor(
388+
internal mutating func takeCursor(at index: Index) -> UnsafeCursor {
389+
var slots = index.childSlots
390+
slots.append(UInt16(index.slot))
391+
392+
// Initialize parents with some dummy value filling it.
393+
var parents =
394+
UnsafeCursor.Path(repeating: .passUnretained(self.root.storage))
395+
396+
var ownedRoot: Node.Storage
397+
do {
398+
var tempRoot: Node.Storage? = nil
399+
swap(&tempRoot, &self.root._storage)
400+
ownedRoot = tempRoot.unsafelyUnwrapped
401+
}
402+
403+
var node: Unmanaged<Node.Storage> = .passUnretained(ownedRoot)
404+
405+
// The depth containing the first instance of a shared
406+
var lastUniqueDepth = isKnownUniquelyReferenced(&ownedRoot) ? 0 : -1
407+
var isOnUniquePath = isKnownUniquelyReferenced(&ownedRoot)
408+
409+
for d in 0..<index.childSlots.depth {
410+
node._withUnsafeGuaranteedRef { storage in
411+
storage.read { handle in
412+
parents.append(node)
413+
414+
let slot = Int(index.childSlots[d])
415+
node = .passUnretained(handle[childAt: slot].storage)
416+
if isOnUniquePath && handle.isChildUnique(atSlot: slot) {
417+
lastUniqueDepth += 1
418+
} else {
419+
isOnUniquePath = false
420+
}
421+
}
422+
}
423+
}
424+
425+
parents.append(node)
426+
427+
let cursor = UnsafeCursor(
428+
root: ownedRoot,
429+
slots: slots,
430+
path: parents,
431+
lastUniqueDepth: lastUniqueDepth
432+
)
433+
434+
return cursor
435+
}
436+
437+
/// Obtains a cursor to a given element in the tree.
438+
///
439+
/// This 'consumes' the tree, however it expects the callee to retain the root of the tree for the duration of
440+
/// the cursors lifetime.
441+
///
442+
/// - Parameter key: The key to search for
443+
/// - Returns: A `cursor` to the key or where the key should be inserted, and a `found`
444+
/// parameter indicating whether or not the key exists within the tree, iff `found` is true, the cursor
445+
/// is concrete.
446+
/// - Complexity: O(`log n`)
447+
@inlinable
448+
internal mutating func takeCursor(
391449
forKey key: Key
392450
) -> (cursor: UnsafeCursor, found: Bool) {
393451
var slots = Index.Offsets(repeating: 0)

Sources/SortedCollections/BTree/_BTree.Builder.swift

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
extension _BTree {
1313
/// Provides an interface for efficiently constructing a filled B-Tree from sorted data.
1414
///
15+
/// A builder supports duplicate keys, in which case they are inserted in the same order they are recieved.
16+
/// However, is the `deduplicating` parameter is passed as `true`, operations will silently drop
17+
/// duplicates.
18+
///
1519
/// This type has a few advantages when constructing a B-Tree over other approaches such as manually
1620
/// inserting each element or using a cursor:
1721
///
@@ -101,31 +105,47 @@ extension _BTree {
101105
@usableFromInline
102106
internal let internalCapacity: Int
103107

108+
@usableFromInline
109+
internal let deduplicating: Bool
110+
111+
@usableFromInline
112+
internal var lastKey: Key?
113+
104114
/// Creates a new B-Tree builder with default capacities
115+
/// - Parameter deduplicating: Whether duplicates should be removed.
105116
@inlinable
106117
@inline(__always)
107-
internal init() {
118+
internal init(deduplicating: Bool = false) {
108119
self.init(
120+
deduplicating: deduplicating,
109121
leafCapacity: _BTree.defaultLeafCapacity,
110122
internalCapacity: _BTree.defaultInternalCapacity
111123
)
112124
}
113125

114126
/// Creates a new B-Tree builder with a custom uniform capacity configuration
115127
/// - Parameters:
128+
/// - deduplicating: Whether duplicates should be removed.
116129
/// - capacity: The amount of elements per node.
117130
@inlinable
118131
@inline(__always)
119-
internal init(capacity: Int) {
120-
self.init(leafCapacity: capacity, internalCapacity: capacity)
132+
internal init(deduplicating: Bool = false, capacity: Int) {
133+
self.init(
134+
deduplicating: deduplicating,
135+
leafCapacity: capacity,
136+
internalCapacity: capacity
137+
)
121138
}
122139

123140
/// Creates a new B-Tree builder with a custom capacity configuration
124141
/// - Parameters:
125-
/// - capacity: The amount of elements per node.
142+
/// - deduplicating: Whether duplicates should be removed.
143+
/// - leafCapacity: The amount of elements per leaf node.
144+
/// - internalCapacity: The amount of elements per internal node.
126145
@inlinable
127146
@inline(__always)
128147
internal init(
148+
deduplicating: Bool = false,
129149
leafCapacity: Int,
130150
internalCapacity: Int
131151
) {
@@ -138,6 +158,8 @@ extension _BTree {
138158
self._seedling = Node(withCapacity: leafCapacity, isLeaf: true)
139159
self.leafCapacity = leafCapacity
140160
self.internalCapacity = internalCapacity
161+
self.deduplicating = deduplicating
162+
self.lastKey = nil
141163
}
142164

143165
/// Pops a sapling and it's associated seperator
@@ -176,8 +198,16 @@ extension _BTree {
176198
/// Appends a new element to the tree
177199
/// - Parameter element: Element which is after all previous elements in sorted order.
178200
@inlinable
179-
@inline(__always)
180201
internal mutating func append(_ element: __owned Element) {
202+
assert(lastKey == nil || lastKey! <= element.key,
203+
"New element must be non-decreasing.")
204+
defer { lastKey = element.key }
205+
if deduplicating {
206+
if let lastKey = lastKey {
207+
if lastKey == element.key { return }
208+
}
209+
}
210+
181211
switch state {
182212
case .addingSeperator:
183213
completeSeedling(withSeperator: element)
@@ -200,7 +230,6 @@ extension _BTree {
200230
/// Declares that the current seedling is finished with insertion and creates a new seedling to
201231
/// further operate on.
202232
@inlinable
203-
@inline(__always)
204233
internal mutating func completeSeedling(
205234
withSeperator newSeperator: __owned Element
206235
) {
@@ -311,7 +340,6 @@ extension _BTree {
311340
///
312341
/// - Returns: A usable, fully-filled B-Tree
313342
@inlinable
314-
@inline(__always)
315343
internal mutating func finish() -> _BTree {
316344
var root: Node = seedling
317345
_seedling = nil
@@ -331,3 +359,12 @@ extension _BTree {
331359
}
332360
}
333361
}
362+
363+
extension _BTree.Builder where Value == Void {
364+
/// Appends a value to a B-Tree builder without values.
365+
@inlinable
366+
@inline(__always)
367+
internal mutating func append(_ key: __owned Key) {
368+
self.append((key, ()))
369+
}
370+
}

0 commit comments

Comments
 (0)