Skip to content

Commit 9b470c8

Browse files
committed
Initial implementation of SetAlgebra and various refactors.
Key Changes: - SortedSet - Implement SetAlgebra - Implement Hashable - Implement Equatable - Implement Custom(Debug)StringConvertible - Implement CustomReflectable - Implement Codable - Implement subscript(range: Range<Element>) - Implement subscript(offset:) - Implement remove(atOffset:) Minor Changes: - Fix inefficiency on SortedDictionary.customMirror - Optimize to O(1) _BTree.UnsafePath comparator - Optimize away ARC traffic on findAnyPath(forKey:) Various additional refactors and renames.
1 parent 98f2785 commit 9b470c8

21 files changed

+577
-156
lines changed

Sources/SortedCollections/BTree/_BTree+BidirectionalCollection.swift

Lines changed: 98 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ extension _BTree: BidirectionalCollection {
2121
@inline(__always)
2222
internal var isEmpty: Bool { self.count == 0 }
2323

24+
// TODO: look into O(1) implementation
2425
/// Locates the first element and returns a proper path to it, or nil if the BTree is empty.
25-
/// - Complexity: O(1)
26+
/// - Complexity: O(`log n`)
2627
@inlinable
2728
internal var startIndex: Index {
2829
if count == 0 { return Index(nil, forTree: self) }
@@ -156,7 +157,7 @@ extension _BTree: BidirectionalCollection {
156157
}
157158

158159
// Otherwise, re-seek
159-
i = self.indexToElement(at: newIndex)
160+
i = Index(self.path(atOffset: newIndex), forTree: self)
160161
}
161162

162163
/// Returns an index that is the specified distance from the given index.
@@ -225,98 +226,126 @@ extension _BTree: BidirectionalCollection {
225226
@inline(__always)
226227
internal subscript(index: Index) -> Element {
227228
assert(index.path != nil, "Attempt to subscript out of range index.")
228-
return index.path!.element
229+
return index.path.unsafelyUnwrapped.element
229230
}
230231
}
231232

232233
// MARK: Custom Indexing Operations
233234
extension _BTree {
234-
/// Returns the path corresponding to the first found instance of the key. This may
235-
/// not be the first instance of the key. This is marginally more efficient for trees
236-
/// that do not have duplicates.
237-
///
238-
/// - Parameter key: The key to search for within the tree.
239-
/// - Returns: If found, returns a path to the element. Otherwise, `nil`.
235+
/// Returns a path to the key at absolute offset `i`.
236+
/// - Parameter offset: 0-indexed offset within BTree bounds, else may panic.
237+
/// - Returns: the index of the appropriate element.
238+
/// - Complexity: O(`log n`)
239+
240+
241+
/// Obtains the start index for a key (or where it would exist).
240242
@inlinable
241-
internal func anyIndex(forKey key: Key) -> Index? {
243+
internal func startIndex(forKey key: Key) -> Index {
242244
var childSlots = UnsafePath.Offsets(repeating: 0)
243-
var node: Node? = self.root
245+
var targetSlot: Int = 0
246+
var offset = 0
244247

245-
while let currentNode = node {
246-
let path: UnsafePath? = currentNode.read { handle in
247-
let keySlot = handle.startSlot(forKey: key)
248-
if keySlot < handle.elementCount && handle[keyAt: keySlot] == key {
249-
return UnsafePath(node: currentNode.storage, slot: keySlot, childSlots: childSlots, offset: 0)
250-
} else {
248+
func search(in node: Node) -> Unmanaged<Node.Storage>? {
249+
node.read({ handle in
250+
let slot = handle.startSlot(forKey: key)
251+
if slot < handle.elementCount {
251252
if handle.isLeaf {
252-
node = nil
253+
offset += slot
254+
targetSlot = slot
255+
return .passUnretained(node.storage)
253256
} else {
254-
childSlots.append(UInt16(keySlot))
255-
node = handle[childAt: keySlot]
257+
// Calculate offset by summing previous subtrees
258+
for i in 0...slot {
259+
offset += handle[childAt: i].read({ $0.subtreeCount })
260+
}
261+
262+
let currentOffset = offset
263+
let currentDepth = childSlots.depth
264+
childSlots.append(UInt16(slot))
265+
266+
if let foundEarlier = search(in: handle[childAt: slot]) {
267+
return foundEarlier
268+
} else {
269+
childSlots.depth = currentDepth
270+
targetSlot = slot
271+
offset = currentOffset
272+
273+
return .passUnretained(node.storage)
274+
}
256275
}
257-
276+
} else {
277+
// Start index exceeds node and is therefore not in this.
258278
return nil
259279
}
260-
}
261-
262-
if let path = path {
263-
return Index(path, forTree: self)
264-
}
280+
})
265281
}
266282

267-
return nil
283+
if let targetChild = search(in: self.root) {
284+
return Index(UnsafePath(
285+
node: targetChild,
286+
slot: targetSlot,
287+
childSlots: childSlots,
288+
offset: offset
289+
), forTree: self)
290+
} else {
291+
return Index(nil, forTree: self)
292+
}
268293
}
269294

270-
/// Returns a path to the key at absolute offset `i`.
271-
/// - Parameter offset: 0-indexed offset within BTree bounds, else may panic.
272-
/// - Returns: the index of the appropriate element.
273-
/// - Complexity: O(`log n`)
295+
/// Obtains the last index at which a value less than or equal to the key appears.
274296
@inlinable
275-
internal func indexToElement(at offset: Int) -> Index {
276-
assert(offset <= self.count, "Index out of bounds.")
277-
278-
if offset == self.count {
279-
return Index(nil, forTree: self)
280-
}
281-
297+
internal func lastIndex(forKey key: Key) -> Index {
282298
var childSlots = UnsafePath.Offsets(repeating: 0)
299+
var targetSlot: Int = 0
300+
var offset = 0
283301

284-
var node: _Node = self.root
285-
var startIndex = 0
286-
287-
while !node.read({ $0.isLeaf }) {
288-
let internalPath: UnsafePath? = node.read { handle in
289-
for childSlot in 0..<handle.childCount {
290-
let child = handle[childAt: childSlot]
291-
let endIndex = startIndex + child.read({ $0.subtreeCount })
302+
func search(in node: Node) -> Unmanaged<Node.Storage>? {
303+
node.read({ handle in
304+
let slot = handle.endSlot(forKey: key) - 1
305+
if slot > 0 {
306+
// Sanity Check
307+
assert(slot < handle.elementCount, "Slot out of bounds.")
292308

293-
if offset < endIndex {
294-
childSlots.append(UInt16(childSlot))
295-
node = child
296-
return nil
297-
} else if offset == endIndex {
298-
// We've found the node we want
299-
return UnsafePath(node: node, slot: childSlot, childSlots: childSlots, offset: offset)
309+
if handle.isLeaf {
310+
offset += slot
311+
targetSlot = slot
312+
return .passUnretained(node.storage)
300313
} else {
301-
startIndex = endIndex + 1
314+
for i in 0...slot {
315+
offset += handle[childAt: i].read({ $0.subtreeCount })
316+
}
317+
318+
let currentOffset = offset
319+
let currentDepth = childSlots.depth
320+
childSlots.append(UInt16(slot + 1))
321+
322+
if let foundLater = search(in: handle[childAt: slot + 1]) {
323+
return foundLater
324+
} else {
325+
childSlots.depth = currentDepth
326+
targetSlot = slot
327+
offset = currentOffset
328+
329+
return .passUnretained(node.storage)
330+
}
302331
}
332+
} else {
333+
// Start index exceeds node and is therefore not in this.
334+
return nil
303335
}
304-
305-
// TODO: convert into debug-only preconditionFaliure
306-
preconditionFailure("In-bounds index not found within tree.")
307-
}
308-
309-
if let internalPath = internalPath { return Index(internalPath, forTree: self) }
336+
})
310337
}
311338

312-
let path: UnsafePath = UnsafePath(
313-
node: node,
314-
slot: offset - startIndex,
315-
childSlots: childSlots,
316-
offset: offset
317-
)
318-
319-
return Index(path, forTree: self)
339+
if let targetChild = search(in: self.root) {
340+
return Index(UnsafePath(
341+
node: targetChild,
342+
slot: targetSlot,
343+
childSlots: childSlots,
344+
offset: offset
345+
), forTree: self)
346+
} else {
347+
return Index(nil, forTree: self)
348+
}
320349
}
321-
350+
322351
}

Sources/SortedCollections/BTree/_BTree.Index.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
extension _BTree {
1313

1414
/// An index to an element of the BTree represented as a path.
15+
/// - Warning: This has the capability to perform safety checks, however they must be explicitly be
16+
/// performed using the validation methods.
1517
@usableFromInline
1618
internal struct Index: Comparable {
1719
/// The path to the element in the BTree. A `nil` value indicates

Sources/SortedCollections/BTree/_BTree.UnsafePath.swift

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
// TODO: potentially make operations mutating.
1414

1515
extension _BTree {
16-
/// Represents a specific element in a BTree. This holds strong references to the
16+
/// Represents a specific element in a BTree. This does not hold any references to the
1717
/// element it points to.
1818
/// - Warning: Operations on this path will trap if the underlying node is deallocated.
1919
/// and they become invalid if the tree is mutated, however this is not checked. For
20-
/// safety, use ``_BTree.Index`` instead
20+
/// safety, use ``_BTree.Index`` instead with its validation methods.
2121
@usableFromInline
2222
internal struct UnsafePath {
2323
@usableFromInline
@@ -68,7 +68,7 @@ extension _BTree {
6868
childSlots: Offsets,
6969
offset: Int
7070
) {
71-
self.init(node: node.storage, slot: slot, childSlots: childSlots, offset: offset)
71+
self.init(node: .passUnretained(node.storage), slot: slot, childSlots: childSlots, offset: offset)
7272
}
7373

7474
/// Creates a path representing a sequence of nodes to an element.
@@ -79,12 +79,12 @@ extension _BTree {
7979
/// - index: The absolute offset of this path's element in the tree.
8080
@inlinable
8181
internal init(
82-
node: Node.Storage,
82+
node: Unmanaged<Node.Storage>,
8383
slot: Int,
8484
childSlots: Offsets,
8585
offset: Int
8686
) {
87-
self.node = .passUnretained(node)
87+
self.node = node
8888
self.slot = slot
8989
self.childSlots = childSlots
9090
self.offset = offset
@@ -96,7 +96,7 @@ extension _BTree {
9696
@inlinable
9797
@inline(__always)
9898
internal var element: Element {
99-
return self.readNode { $0[elementAt: self.slot] }
99+
self.readNode { $0[elementAt: self.slot] }
100100
}
101101

102102
/// Operators on a handle of the node
@@ -125,23 +125,9 @@ extension _BTree.UnsafePath: Equatable {
125125
// MARK: Comparable
126126
extension _BTree.UnsafePath: Comparable {
127127
/// Returns true if the first path points to an element before the second path
128-
/// - Complexity: O(`log n`)
128+
/// - Complexity: O(1)
129129
@inlinable
130130
public static func <(lhs: _BTree.UnsafePath, rhs: _BTree.UnsafePath) -> Bool {
131-
for i in 0..<min(lhs.childSlots.depth, rhs.childSlots.depth) {
132-
if lhs.childSlots[i] < rhs.childSlots[i] {
133-
return true
134-
}
135-
}
136-
137-
if lhs.childSlots.depth < rhs.childSlots.depth {
138-
let rhsOffset = rhs.childSlots[lhs.childSlots.depth - 1]
139-
return lhs.slot < rhsOffset
140-
} else if rhs.childSlots.depth < lhs.childSlots.depth {
141-
let lhsOffset = lhs.childSlots[rhs.childSlots.depth - 1]
142-
return lhsOffset <= rhs.slot
143-
} else {
144-
return lhs.slot < rhs.slot
145-
}
131+
return lhs.offset < rhs.offset
146132
}
147133
}

0 commit comments

Comments
 (0)