Skip to content

Commit 793e38f

Browse files
committed
Use BTreeMerger’s match strategy for BTree comparisons, too.
Also, rename BTreeMergeStrategy to BTreeMatchStrategy. This fixes #22.
1 parent 25f9262 commit 793e38f

6 files changed

+356
-243
lines changed

Sources/BTreeBuilder.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,22 @@ internal struct BTreeBuilder<Key: Comparable, Value> {
129129
self.state = .element
130130
}
131131

132+
var lastKey: Key? {
133+
switch state {
134+
case .separator:
135+
return saplings.last?.last?.0
136+
case .element:
137+
return seedling.last?.0 ?? separators.last?.0
138+
}
139+
}
140+
141+
func isValidNextKey(_ key: Key) -> Bool {
142+
guard let last = lastKey else { return true }
143+
return last <= key
144+
}
145+
132146
mutating func append(_ element: Element) {
147+
assert(isValidNextKey(element.0))
133148
switch state {
134149
case .separator:
135150
separators.append(element)
@@ -154,8 +169,9 @@ internal struct BTreeBuilder<Key: Comparable, Value> {
154169

155170
mutating func appendWithoutCloning(_ node: Node) {
156171
assert(node.order == order)
172+
if node.isEmpty { return }
173+
assert(isValidNextKey(node.first!.0))
157174
if node.depth == 0 {
158-
if node.isEmpty { return }
159175
if state == .separator {
160176
assert(seedling.isEmpty)
161177
separators.append(node.elements.removeFirst())

Sources/BTreeComparisons.swift

+54-38
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ extension BTree {
109109
/// - Complexity:
110110
/// - O(min(`self.count`, `tree.count`)) in general.
111111
/// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs.
112-
public func isSubset(of tree: BTree) -> Bool {
113-
return isSubset(of: tree, strict: false)
112+
public func isSubset(of tree: BTree, by strategy: BTreeMatchStrategy) -> Bool {
113+
return isSubset(of: tree, by: strategy, strict: false)
114114
}
115115

116116
/// Returns true iff all keys in `self` are also in `tree`,
@@ -119,17 +119,17 @@ extension BTree {
119119
/// - Complexity:
120120
/// - O(min(`self.count`, `tree.count`)) in general.
121121
/// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs.
122-
public func isStrictSubset(of tree: BTree) -> Bool {
123-
return isSubset(of: tree, strict: true)
122+
public func isStrictSubset(of tree: BTree, by strategy: BTreeMatchStrategy) -> Bool {
123+
return isSubset(of: tree, by: strategy, strict: true)
124124
}
125125

126126
/// Returns true iff all keys in `tree` are also in `self`.
127127
///
128128
/// - Complexity:
129129
/// - O(min(`self.count`, `tree.count`)) in general.
130130
/// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs.
131-
public func isSuperset(of tree: BTree) -> Bool {
132-
return tree.isSubset(of: self, strict: false)
131+
public func isSuperset(of tree: BTree, by strategy: BTreeMatchStrategy) -> Bool {
132+
return tree.isSubset(of: self, by: strategy, strict: false)
133133
}
134134

135135
/// Returns true iff all keys in `tree` are also in `self`,
@@ -138,55 +138,71 @@ extension BTree {
138138
/// - Complexity:
139139
/// - O(min(`self.count`, `tree.count`)) in general.
140140
/// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs.
141-
public func isStrictSuperset(of tree: BTree) -> Bool {
142-
return tree.isSubset(of: self, strict: true)
141+
public func isStrictSuperset(of tree: BTree, by strategy: BTreeMatchStrategy) -> Bool {
142+
return tree.isSubset(of: self, by: strategy, strict: true)
143143
}
144144

145-
internal func isSubset(of tree: BTree, strict: Bool) -> Bool {
145+
internal func isSubset(of tree: BTree, by strategy: BTreeMatchStrategy, strict: Bool) -> Bool {
146146
var a = BTreeStrongPath(startOf: self.root)
147147
var b = BTreeStrongPath(startOf: tree.root)
148148
var knownStrict = false
149-
if !a.isAtEnd && !b.isAtEnd {
150-
outer: while true {
151-
if a.key < b.key {
152-
return false
153-
}
154-
while a.key == b.key {
155-
while a.node === b.node && a.slot == b.slot {
156-
// Ascend to first ancestor that isn't shared.
157-
repeat {
158-
a.ascendOneLevel()
159-
b.ascendOneLevel()
160-
} while !a.isAtEnd && !b.isAtEnd && a.node === b.node && a.slot == b.slot
161-
if a.isAtEnd || b.isAtEnd { break outer }
162-
a.ascendToKey()
163-
b.ascendToKey()
164-
}
165-
let key = a.key
149+
outer: while !a.isAtEnd && !b.isAtEnd {
150+
while a.key == b.key {
151+
if a.node === b.node && a.slot == b.slot {
152+
// Ascend to first ancestor that isn't shared.
166153
repeat {
154+
a.ascendOneLevel()
155+
b.ascendOneLevel()
156+
} while !a.isAtEnd && a.node === b.node && a.slot == b.slot
157+
if a.isAtEnd || b.isAtEnd { break outer }
158+
a.ascendToKey()
159+
b.ascendToKey()
160+
}
161+
let key = a.key
162+
switch strategy {
163+
case .groupingMatches:
164+
while !a.isAtEnd && a.key == key {
167165
a.nextPart(until: .including(key))
168-
} while !a.isAtEnd && a.key == key
169-
repeat {
166+
}
167+
while !b.isAtEnd && b.key == key {
170168
b.nextPart(until: .including(key))
171-
} while !b.isAtEnd && b.key == key
169+
}
170+
if a.isAtEnd || b.isAtEnd { break outer }
171+
case .countingMatches:
172+
var acount = 0
173+
while !a.isAtEnd && a.key == key {
174+
acount += a.nextPart(until: .including(key)).count
175+
}
176+
var bcount = 0
177+
while !b.isAtEnd && b.key == key {
178+
bcount += b.nextPart(until: .including(key)).count
179+
}
180+
if acount > bcount {
181+
return false
182+
}
183+
if acount < bcount {
184+
knownStrict = true
185+
}
172186
if a.isAtEnd || b.isAtEnd { break outer }
173-
}
174-
while b.key < a.key {
175-
knownStrict = true
176-
b.nextPart(until: .excluding(a.key))
177-
if b.isAtEnd { return false }
178187
}
179188
}
189+
if a.key < b.key {
190+
return false
191+
}
192+
while b.key < a.key {
193+
knownStrict = true
194+
b.nextPart(until: .excluding(a.key))
195+
if b.isAtEnd { return false }
196+
}
180197
}
198+
181199
if a.isAtEnd {
182200
if !b.isAtEnd {
183201
return true
184202
}
185203
}
186-
else {
187-
if b.isAtEnd {
188-
return false
189-
}
204+
else if b.isAtEnd {
205+
return false
190206
}
191207
return !strict || knownStrict
192208
}

Sources/BTreeMerger.swift

+37-19
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Copyright © 2016 Károly Lőrentey.
77
//
88

9-
public enum BTreeMergeStrategy {
9+
public enum BTreeMatchStrategy {
1010
case groupingMatches
1111
case countingMatches
1212
}
@@ -40,7 +40,7 @@ extension BTree {
4040
/// - Complexity:
4141
/// - O(min(`self.count`, `other.count`)) in general.
4242
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
43-
public func union(_ other: BTree, by strategy: BTreeMergeStrategy) -> BTree {
43+
public func union(_ other: BTree, by strategy: BTreeMatchStrategy) -> BTree {
4444
var m = BTreeMerger(first: self, second: other)
4545
switch strategy {
4646
case .groupingMatches:
@@ -84,7 +84,7 @@ extension BTree {
8484
/// - Complexity:
8585
/// - O(min(`self.count`, `tree.count`)) in general.
8686
/// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs.
87-
public func subtracting(_ other: BTree, by strategy: BTreeMergeStrategy) -> BTree {
87+
public func subtracting(_ other: BTree, by strategy: BTreeMatchStrategy) -> BTree {
8888
var m = BTreeMerger(first: self, second: other)
8989
while !m.done {
9090
m.copyFromFirst(.excludingOtherKey)
@@ -127,7 +127,7 @@ extension BTree {
127127
/// - Complexity:
128128
/// - O(min(`self.count`, `other.count`)) in general.
129129
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
130-
public func symmetricDifference(_ other: BTree, by strategy: BTreeMergeStrategy) -> BTree {
130+
public func symmetricDifference(_ other: BTree, by strategy: BTreeMatchStrategy) -> BTree {
131131
var m = BTreeMerger(first: self, second: other)
132132
while !m.done {
133133
m.copyFromFirst(.excludingOtherKey)
@@ -171,7 +171,7 @@ extension BTree {
171171
/// - Complexity:
172172
/// - O(min(`self.count`, `other.count`)) in general.
173173
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
174-
public func intersection(_ other: BTree, by strategy: BTreeMergeStrategy) -> BTree {
174+
public func intersection(_ other: BTree, by strategy: BTreeMatchStrategy) -> BTree {
175175
var m = BTreeMerger(first: self, second: other)
176176
while !m.done {
177177
m.skipFromFirst(.excludingOtherKey)
@@ -195,7 +195,7 @@ extension BTree {
195195
///
196196
/// - Requires: `sortedKeys` is sorted in ascending order.
197197
/// - Complexity: O(*n* + `self.count`), where *n* is the number of keys in `sortedKeys`.
198-
public func subtracting<S: Sequence>(sortedKeys: S, by strategy: BTreeMergeStrategy) -> BTree where S.Iterator.Element == Key {
198+
public func subtracting<S: Sequence>(sortedKeys: S, by strategy: BTreeMatchStrategy) -> BTree where S.Iterator.Element == Key {
199199
if self.isEmpty { return self }
200200

201201
var b = BTreeBuilder<Key, Value>(order: self.order)
@@ -237,7 +237,7 @@ extension BTree {
237237
///
238238
/// - Requires: `sortedKeys` is sorted in ascending order.
239239
/// - Complexity: O(*n* + `self.count`), where *n* is the number of keys in `sortedKeys`.
240-
public func intersection<S: Sequence>(sortedKeys: S, by strategy: BTreeMergeStrategy) -> BTree where S.Iterator.Element == Key {
240+
public func intersection<S: Sequence>(sortedKeys: S, by strategy: BTreeMatchStrategy) -> BTree where S.Iterator.Element == Key {
241241
if self.isEmpty { return self }
242242

243243
var b = BTreeBuilder<Key, Value>(order: self.order)
@@ -481,8 +481,9 @@ internal struct BTreeMerger<Key: Comparable, Value> {
481481
var common: Node
482482
repeat {
483483
key = a.node.last!.0
484+
common = a.node
484485
a.ascendOneLevel()
485-
common = b.ascendOneLevel()
486+
b.ascendOneLevel()
486487
} while !a.isAtEnd && !b.isAtEnd && a.node === b.node && a.slot == 0 && b.slot == 0
487488
builder.append(common)
488489
if !a.isAtEnd {
@@ -518,8 +519,9 @@ internal struct BTreeMerger<Key: Comparable, Value> {
518519
/// the shared subtree. The slot & leaf checks above & below ensure that this isn't the case.
519520
var common: Node
520521
repeat {
522+
common = a.node
521523
a.ascendOneLevel()
522-
common = b.ascendOneLevel()
524+
b.ascendOneLevel()
523525
} while !a.isAtEnd && !b.isAtEnd && a.node === b.node && a.slot == 0 && b.slot == 0
524526
builder.append(common)
525527
if !a.isAtEnd { a.ascendToKey() }
@@ -589,7 +591,7 @@ internal struct BTreeMerger<Key: Comparable, Value> {
589591

590592
mutating func skipMatchingNumberOfCommonElements() {
591593
while !done && a.key == b.key {
592-
if a.node === b.node && a.node.isLeaf && a.slot == 0 && b.slot == 0 {
594+
if a.node === b.node && a.node.isLeaf && a.slot == b.slot {
593595
/// We're at the first element of a shared subtree. Find the ancestor at which the shared subtree
594596
/// starts, and append it in a single step.
595597
///
@@ -602,7 +604,7 @@ internal struct BTreeMerger<Key: Comparable, Value> {
602604
if a.isAtEnd || b.isAtEnd {
603605
done = true
604606
}
605-
} while !done && a.node === b.node && a.slot == 0 && b.slot == 0
607+
} while !done && a.node === b.node && a.slot == b.slot
606608
if !a.isAtEnd { a.ascendToKey() }
607609
if !b.isAtEnd { b.ascendToKey() }
608610
}
@@ -622,6 +624,25 @@ internal enum BTreePart<Key: Comparable, Value> {
622624
case nodeRange(BTreeNode<Key, Value>, CountableRange<Int>)
623625
}
624626

627+
extension BTreePart {
628+
var count: Int {
629+
switch self {
630+
case .element(_, _):
631+
return 1
632+
case .node(let node):
633+
return node.count
634+
case .nodeRange(let parent, let range):
635+
var count = range.count
636+
if !parent.isLeaf {
637+
for i in range.lowerBound ... range.upperBound {
638+
count += parent.children[i].count
639+
}
640+
}
641+
return count
642+
}
643+
}
644+
}
645+
625646
extension BTreeBuilder {
626647
mutating func append(_ part: BTreePart<Key, Value>) {
627648
switch part {
@@ -668,16 +689,14 @@ internal extension BTreeStrongPath {
668689

669690
/// Remove the deepest path component, leaving the path at the element following the node that was previously focused,
670691
/// or the spot after all elements if the node was the rightmost child.
671-
@discardableResult
672-
mutating func ascendOneLevel() -> Node {
692+
mutating func ascendOneLevel() {
673693
if length == 1 {
674694
offset = count
675695
slot = node.elements.count
676-
return node
696+
return
677697
}
678698
popFromSlots()
679699
popFromPath()
680-
return node.children[slot!]
681700
}
682701

683702
/// If this path got to a slot at the end of a node but it hasn't reached the end of the tree yet,
@@ -704,10 +723,9 @@ internal extension BTreeStrongPath {
704723
assert(!isAtEnd)
705724
var includeLeftmostSubtree = false
706725
if slot == 0 && node.isLeaf {
707-
while slot == 0 {
708-
guard let pk = parentKey else { break }
709-
guard limit.match(pk) else { break }
710-
ascendOneLevel()
726+
while slot == 0, let pk = parentKey, limit.match(pk) {
727+
popFromSlots()
728+
popFromPath()
711729
includeLeftmostSubtree = true
712730
}
713731
}

Sources/SortedBag.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ extension SortedBag {
536536
/// - O(min(`self.count`, `other.count`)) in general.
537537
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
538538
public func isSubset(of other: SortedBag<Element>) -> Bool {
539-
return tree.isSubset(of: other.tree)
539+
return tree.isSubset(of: other.tree, by: .countingMatches)
540540
}
541541

542542
/// Returns `true` iff all members in this bag are also included in `other`, but the two bags aren't equal.
@@ -549,7 +549,7 @@ extension SortedBag {
549549
/// - O(min(`self.count`, `other.count`)) in general.
550550
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
551551
public func isStrictSubset(of other: SortedBag<Element>) -> Bool {
552-
return tree.isStrictSubset(of: other.tree)
552+
return tree.isStrictSubset(of: other.tree, by: .countingMatches)
553553
}
554554

555555
/// Returns `true` iff all members in `other` are also included in this bag.
@@ -562,7 +562,7 @@ extension SortedBag {
562562
/// - O(min(`self.count`, `other.count`)) in general.
563563
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
564564
public func isSuperset(of other: SortedBag<Element>) -> Bool {
565-
return tree.isSuperset(of: other.tree)
565+
return tree.isSuperset(of: other.tree, by: .countingMatches)
566566
}
567567

568568
/// Returns `true` iff all members in `other` are also included in this bag, but the two bags aren't equal.
@@ -575,7 +575,7 @@ extension SortedBag {
575575
/// - O(min(`self.count`, `other.count`)) in general.
576576
/// - O(log(`self.count` + `other.count`)) if there are only a constant amount of interleaving element runs.
577577
public func isStrictSuperset(of other: SortedBag<Element>) -> Bool {
578-
return tree.isStrictSuperset(of: other.tree)
578+
return tree.isStrictSuperset(of: other.tree, by: .countingMatches)
579579
}
580580
}
581581

0 commit comments

Comments
 (0)