From 8a54021aa7e1e8ba89f7cf103ed4dad869fcd450 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 2 Aug 2023 14:06:35 -0700 Subject: [PATCH 01/67] ART: Adaptive Radix Tree implementation This PR implements Adaptive Radix Tree (ART)[1], a compact key-value search tree. It maps byte-sequences to arbitrary values while maintaining data in sorted order. ART can compress multiple nodes into single node and adaptively expand or shrink its internal nodes to avoid worse-case space consumption. Within the broader swift-collection project, it can be considered a compact variant of SortedSet/SortedDictionary but at the cost of keys being convertible to byte-sequences. [1] https://ieeexplore.ieee.org/abstract/document/6544812 --- Package.swift | 10 + Sources/ARTreeModule/ARTree+Sequence.swift | 64 ++++ Sources/ARTreeModule/ARTree+delete.swift | 56 +++ Sources/ARTreeModule/ARTree+get.swift | 37 ++ Sources/ARTreeModule/ARTree+insert.swift | 92 +++++ Sources/ARTreeModule/ARTree.swift | 24 ++ Sources/ARTreeModule/Const.swift | 5 + Sources/ARTreeModule/FixedSizedArray.swift | 58 ++++ .../ARTreeModule/Node+StringConverter.swift | 136 ++++++++ Sources/ARTreeModule/Node+basics.swift | 83 +++++ Sources/ARTreeModule/Node.swift | 42 +++ Sources/ARTreeModule/Node16.swift | 163 +++++++++ Sources/ARTreeModule/Node256.swift | 125 +++++++ Sources/ARTreeModule/Node4.swift | 143 ++++++++ Sources/ARTreeModule/Node48.swift | 183 ++++++++++ Sources/ARTreeModule/NodeBuffer.swift | 64 ++++ Sources/ARTreeModule/NodeHeader.swift | 12 + Sources/ARTreeModule/NodeLeaf.swift | 116 +++++++ Sources/ARTreeModule/NodeType.swift | 7 + Sources/ARTreeModule/Utils.swift | 18 + Tests/ARTreeModuleTests/DeleteTests.swift | 171 +++++++++ Tests/ARTreeModuleTests/GetValueTests.swift | 87 +++++ Tests/ARTreeModuleTests/InsertTests.swift | 326 ++++++++++++++++++ Tests/ARTreeModuleTests/Node16Tests.swift | 68 ++++ Tests/ARTreeModuleTests/Node256Tests.swift | 35 ++ Tests/ARTreeModuleTests/Node48Tests.swift | 35 ++ Tests/ARTreeModuleTests/Node4Tests.swift | 90 +++++ Tests/ARTreeModuleTests/NodeBasicTests.swift | 30 ++ Tests/ARTreeModuleTests/NodeLeafTests.swift | 80 +++++ Tests/ARTreeModuleTests/SequenceTests.swift | 33 ++ .../ARTreeModuleTests/TreeRefCountTests.swift | 29 ++ 31 files changed, 2422 insertions(+) create mode 100644 Sources/ARTreeModule/ARTree+Sequence.swift create mode 100644 Sources/ARTreeModule/ARTree+delete.swift create mode 100644 Sources/ARTreeModule/ARTree+get.swift create mode 100644 Sources/ARTreeModule/ARTree+insert.swift create mode 100644 Sources/ARTreeModule/ARTree.swift create mode 100644 Sources/ARTreeModule/Const.swift create mode 100644 Sources/ARTreeModule/FixedSizedArray.swift create mode 100644 Sources/ARTreeModule/Node+StringConverter.swift create mode 100644 Sources/ARTreeModule/Node+basics.swift create mode 100644 Sources/ARTreeModule/Node.swift create mode 100644 Sources/ARTreeModule/Node16.swift create mode 100644 Sources/ARTreeModule/Node256.swift create mode 100644 Sources/ARTreeModule/Node4.swift create mode 100644 Sources/ARTreeModule/Node48.swift create mode 100644 Sources/ARTreeModule/NodeBuffer.swift create mode 100644 Sources/ARTreeModule/NodeHeader.swift create mode 100644 Sources/ARTreeModule/NodeLeaf.swift create mode 100644 Sources/ARTreeModule/NodeType.swift create mode 100644 Sources/ARTreeModule/Utils.swift create mode 100644 Tests/ARTreeModuleTests/DeleteTests.swift create mode 100644 Tests/ARTreeModuleTests/GetValueTests.swift create mode 100644 Tests/ARTreeModuleTests/InsertTests.swift create mode 100644 Tests/ARTreeModuleTests/Node16Tests.swift create mode 100644 Tests/ARTreeModuleTests/Node256Tests.swift create mode 100644 Tests/ARTreeModuleTests/Node48Tests.swift create mode 100644 Tests/ARTreeModuleTests/Node4Tests.swift create mode 100644 Tests/ARTreeModuleTests/NodeBasicTests.swift create mode 100644 Tests/ARTreeModuleTests/NodeLeafTests.swift create mode 100644 Tests/ARTreeModuleTests/SequenceTests.swift create mode 100644 Tests/ARTreeModuleTests/TreeRefCountTests.swift diff --git a/Package.swift b/Package.swift index 130288daa..95e673c07 100644 --- a/Package.swift +++ b/Package.swift @@ -278,6 +278,16 @@ let targets: [CustomTarget] = [ name: "SortedCollectionsTests", dependencies: ["SortedCollections", "_CollectionsTestSupport"]), + .target( + kind: .exported, + name: "ARTreeModule", + dependencies: ["_CollectionsUtilities"], + exclude: ["CMakeLists.txt"]), + .target( + kind: .test, + name: "ARTreeModuleTests", + dependencies: ["ARTreeModule", "_CollectionsTestSupport"]), + .target( kind: .exported, name: "Collections", diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift new file mode 100644 index 000000000..4eccec235 --- /dev/null +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -0,0 +1,64 @@ +extension ARTree: Sequence { + public typealias Element = (Key, Value) + + public struct Iterator { + typealias _ChildIndex = Node.Index + + private let tree: ARTree + private var path: [(Node, _ChildIndex?)] + + init(tree: ARTree) { + self.tree = tree + self.path = [] + if let node = tree.root?.asNode(of: Value.self) { + self.path = [(node, node.index())] + } + } + } + + public func makeIterator() -> Iterator { + return Iterator(tree: self) + } +} + +// TODO: Instead of index, use node iterators, to advance to next child. +extension ARTree.Iterator: IteratorProtocol { + public typealias Element = (Key, Value) + + // Exhausted childs on the tip of path. Forward to sibling. + mutating private func advanceToSibling() { + let _ = path.popLast() + advanceToNextChild() + } + + mutating private func advanceToNextChild() { + guard let (node, index) = path.popLast() else { + return + } + + path.append((node, node.next(index: index!))) + } + + mutating public func next() -> Element? { + while !path.isEmpty { + while let (node, _index) = path.last { + guard let index = _index else { + advanceToSibling() + break + } + + let next = node.child(at: index)! + if next.type() == .leaf { + let leaf: NodeLeaf = next.asLeaf() + let result = (leaf.key, leaf.value) + advanceToNextChild() + return result + } + + path.append((next.asNode(of: Value.self)!, node.index())) + } + } + + return nil + } +} diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift new file mode 100644 index 000000000..c7003aecc --- /dev/null +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -0,0 +1,56 @@ +extension ARTree { + public mutating func delete(key: Key) -> Bool { + var ref: ChildSlotPtr? = ChildSlotPtr(&root) + if let node = root?.asNode(of: Value.self) { + return _delete(node: node, ref: &ref, key: key, depth: 0) + } + + return false + } + + public mutating func deleteRange(start: Key, end: Key) { + // TODO + fatalError("not implemented") + } + + private mutating func _delete(node: Node, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool + { + var newDepth = depth + var node = node + + if node.type() == .leaf { + let leaf = node as! NodeLeaf + + if !leaf.keyEquals(with: key, depth: depth) { + return false + } + + ref?.pointee = nil + leaf.valuePtr.deinitialize(count: 1) + return true + } + + if node.partialLength > 0 { + let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) + assert(matchedBytes <= node.partialLength) + newDepth += matchedBytes + } + + guard let childPosition = node.index(forKey: key[newDepth]) else { + // Key not found, nothing to do. + return false + } + + var childRef: ChildSlotPtr? + let child = node.child(at: childPosition, ref: &childRef)!.asNode(of: Value.self)! + if !_delete(node: child, ref: &childRef, key: key, depth: newDepth + 1) { + return false + } + + let shouldDeleteNode = node.count == 1 + node.deleteChild(at: childPosition, ref: ref) + + // NOTE: node can be invalid because of node shrinking. Hence, we get count before. + return shouldDeleteNode + } +} diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift new file mode 100644 index 000000000..3b0cc7fdd --- /dev/null +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -0,0 +1,37 @@ +extension ARTree { + public func getValue(key: Key) -> Value? { + var current = root + var depth = 0 + while depth <= key.count { + guard let node = current?.asNode(of: Value.self) else { + return nil + } + + if node.type() == .leaf { + let leaf: NodeLeaf = node.pointer.asLeaf() + return leaf.keyEquals(with: key) + ? leaf.value + : nil + } + + if node.partialLength > 0 { + let prefixLen = node.prefixMismatch(withKey: key, fromIndex: depth) + assert(prefixLen <= Const.maxPartialLength, "partial length is always bounded") + if prefixLen != node.partialLength { + return nil + } + depth = depth + node.partialLength + } + + current = node.child(forKey: key[depth]) + depth += 1 + } + + return nil + } + + public mutating func getRange(start: Key, end: Key) { + // TODO + fatalError("not implemented") + } +} diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift new file mode 100644 index 000000000..db54b438c --- /dev/null +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -0,0 +1,92 @@ +extension ARTree { + public mutating func insert(key: Key, value: Value) -> Bool { + var current: NodePtr? = root + + // Location of child (current) pointer in parent, i.e. memory address where the + // address of current node is stored inside the parent node. + // TODO: Fix warning here? + var ref: ChildSlotPtr? = ChildSlotPtr(&root) + + var depth = 0 + while depth < key.count { + guard var node = current?.asNode(of: Value.self) else { + assert(false, "current node can never be nil") + } + + // Reached leaf already, replace it with a new node, or update the existing value. + if node.type() == .leaf { + let leaf = node as! NodeLeaf + if leaf.keyEquals(with: key) { + //TODO: Replace value. + fatalError("replace not supported") + } + + let newLeaf = NodeLeaf.allocate(key: key, value: value) + var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) + + var newNode = Node4.allocate() + newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) + newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) + + while longestPrefix > 0 { + let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) + let start = depth + longestPrefix - nBytes + newNode.partialLength = nBytes + newNode.partialBytes.copy(src: key[...], start: start, count: nBytes) + longestPrefix -= nBytes + 1 + + if longestPrefix <= 0 { + break + } + + var nextNode = Node4.allocate() + nextNode.addChild(forKey: key[start - 1], node: newNode) + newNode = nextNode + } + + ref?.pointee = newNode.pointer // Replace child in parent. + return true + } + + if node.partialLength > 0 { + let partialLength = node.partialLength + let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) + if prefixDiff >= partialLength { + // Matched all partial bytes. Continue to next child. + depth += partialLength + } else { + // Incomplete match with partial bytes, hence needs splitting. + var newNode = Node4.allocate() + newNode.partialLength = prefixDiff + // TODO: Just copy min(maxPartialLength, prefixDiff) bytes + newNode.partialBytes = node.partialBytes + + assert( + node.partialLength <= Const.maxPartialLength, + "partial length is always bounded") + newNode.addChild(forKey: node.partialBytes[prefixDiff], node: node) + node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) + node.partialLength -= prefixDiff + 1 + + let newLeaf = NodeLeaf.allocate(key: key, value: value) + newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) + ref?.pointee = newNode.pointer + return true + } + } + + // Find next child to continue. + guard let next = node.child(forKey: key[depth], ref: &ref) else { + // No child, insert leaf within us. + let newLeaf = NodeLeaf.allocate(key: key, value: value) + node.addChild(forKey: key[depth], node: newLeaf, ref: ref) + return true + } + + depth += 1 + current = next + } + + return false + } +} diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift new file mode 100644 index 000000000..974bb2005 --- /dev/null +++ b/Sources/ARTreeModule/ARTree.swift @@ -0,0 +1,24 @@ +// TODO: +// * Check deallocate of nodes. +// * Path compression when deleting. +// * Range delete. +// * Delete node should delete all sub-childs (for range deletes) +// * Confirm to Swift Dictionary/Iterator protocols. +// * Fixed sized array. +// * Generic/any serializable type? +// * Binary search Node16. +// * SIMD instructions for Node4. +// * Replace some loops with memcpy. +// * Better test cases. +// * Fuzz testing. +// * Leaf don't need to store entire key. +// * Memory safety in Swift? +// * Values should be whatever. + +public struct ARTree { + var root: NodePtr? + + public init() { + self.root = Node4.allocate().pointer + } +} diff --git a/Sources/ARTreeModule/Const.swift b/Sources/ARTreeModule/Const.swift new file mode 100644 index 000000000..c4d43833e --- /dev/null +++ b/Sources/ARTreeModule/Const.swift @@ -0,0 +1,5 @@ +struct Const { + static let indentWidth = 4 + static let defaultAlignment = 4 + static let maxPartialLength = 8 +} diff --git a/Sources/ARTreeModule/FixedSizedArray.swift b/Sources/ARTreeModule/FixedSizedArray.swift new file mode 100644 index 000000000..e542d147c --- /dev/null +++ b/Sources/ARTreeModule/FixedSizedArray.swift @@ -0,0 +1,58 @@ +private let FixedArray8Count = 8 + +struct FixedSizedArray8 { + var storage: (Elem, Elem, Elem, Elem, Elem, Elem, Elem, Elem) + + init(val: Elem) { + self.storage = (val, val, val, val, val, val, val, val) + } + + mutating func copy(src: ArraySlice, start: Int, count: Int) { + // TODO: memcpy? + for ii in 0.., start: Int, count: Int) { + for ii in 0.. Elem { + get { + precondition(0 <= position && position < FixedArray8Count, "\(position)") + return self[unchecked: position] + } + + set { + precondition(0 <= position && position < 8) + self[unchecked: position] = newValue + } + } + + subscript(unchecked position: Int) -> Elem { + get { + return withUnsafeBytes(of: storage) { (ptr) -> Elem in + let offset = MemoryLayout.stride &* position + return ptr.load(fromByteOffset: offset, as: Elem.self) + } + } + set { + let offset = MemoryLayout.stride &* position + withUnsafeMutableBytes(of: &storage) { (ptr) -> Void in + ptr.storeBytes( + of: newValue, + toByteOffset: offset, + as: Elem.self) + } + } + } +} diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift new file mode 100644 index 000000000..ba1fb537f --- /dev/null +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -0,0 +1,136 @@ +protocol NodePrettyPrinter { + func print(value: Value.Type) -> String + func prettyPrint(depth: Int, value: Value.Type) -> String +} + +extension NodePrettyPrinter { + func print(value: Value.Type) -> String { + return "○ " + prettyPrint(depth: 0, value: value) + } +} + +func indent(_ width: Int, last: Bool) -> String { + let end = last ? "└──○ " : "├──○ " + if width > 0 { + // let rep = "" + String(repeating: " ", count: Const.indentWidth - 1) + return String(repeating: "│ ", count: width) + end + } else { + return end + } +} + +extension ARTree: CustomStringConvertible { + public var description: String { + if let node = root?.asNode(of: Value.self) { + return "○ " + node.prettyPrint(depth: 0, value: Value.self) + } else { + return "<>" + } + } +} + +extension Node { + fileprivate var partial: [UInt8] { + var arr = [UInt8](repeating: 0, count: partialLength) + let bytes = partialBytes + for idx in 0..(depth: Int, value: Value.Type) -> String { + "\(self.keyLength)\(self.key) -> \(self.value)" + } +} + +extension Node4: NodePrettyPrinter { + func prettyPrint(depth: Int, value: Value.Type) -> String { + var output = "Node4 {childs=\(count), partial=\(partial)}\n" + for idx in 0..(depth: Int, value: Value.Type) -> String { + var output = "Node16 {childs=\(count), partial=\(partial)}\n" + for idx in 0..(depth: Int, value: Value.Type) -> String { + var output = "Node48 {childs=\(count), partial=\(partial)}\n" + var total = 0 + for (key, slot) in keys.enumerated() { + if slot >= 0xFF { + continue + } + + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child(at: Int(slot))!.asNode(of: Value.self)!.prettyPrint( + depth: depth + 1, + value: value) + if !last { + output += "\n" + } + } + + return output + } +} + +extension Node256: NodePrettyPrinter { + func prettyPrint(depth: Int, value: Value.Type) -> String { + var output = "Node256 {childs=\(count), partial=\(partial)}\n" + var total = 0 + for (key, child) in childs.enumerated() { + if child == nil { + continue + } + + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child!.asNode(of: Value.self)!.prettyPrint( + depth: depth + 1, + value: value) + if !last { + output += "\n" + } + } + return output + } +} diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/Node+basics.swift new file mode 100644 index 000000000..7e844f52e --- /dev/null +++ b/Sources/ARTreeModule/Node+basics.swift @@ -0,0 +1,83 @@ +extension Node { + fileprivate var header: NodeHeaderPtr { self.pointer.assumingMemoryBound(to: NodeHeader.self) } + + var partialLength: Int { + get { Int(self.header.pointee.partialLength) } + set { + assert(newValue <= Const.maxPartialLength) + self.header.pointee.partialLength = KeyPart(newValue) + } + } + + var partialBytes: PartialBytes { + get { header.pointee.partialBytes } + set { header.pointee.partialBytes = newValue } + } + + var count: Int { + get { Int(header.pointee.count) } + set { header.pointee.count = UInt16(newValue) } + } + + func child(forKey k: KeyPart) -> NodePtr? { + var ref = UnsafeMutablePointer(nil) + return child(forKey: k, ref: &ref) + } + + mutating func addChild(forKey k: KeyPart, node: NodePtr) { + let ref = UnsafeMutablePointer(nil) + addChild(forKey: k, node: node, ref: ref) + } + + mutating func addChild(forKey k: KeyPart, node: Node) { + let ref = UnsafeMutablePointer(nil) + addChild(forKey: k, node: node, ref: ref) + } + + mutating func addChild( + forKey k: KeyPart, + node: Node, + ref: ChildSlotPtr? + ) { + addChild(forKey: k, node: node.pointer, ref: ref) + } + + mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) { + let index = index(forKey: k) + assert(index != nil, "trying to delete key that doesn't exist") + if index != nil { + deleteChild(at: index!, ref: ref) + } + } + + mutating func deleteChild(forKey k: KeyPart) { + var ptr: NodePtr? = self.pointer + return deleteChild(forKey: k, ref: &ptr) + } + + mutating func deleteChild(at index: Index) { + var ptr: NodePtr? = self.pointer + deleteChild(at: index, ref: &ptr) + } + + mutating func copyHeader(from: any Node) { + let src = from.header.pointee + header.pointee.count = src.count + header.pointee.partialLength = src.partialLength + header.pointee.partialBytes = src.partialBytes + } + + // Calculates the index at which prefix mismatches. + func prefixMismatch(withKey key: Key, fromIndex depth: Int) -> Int { + assert(partialLength <= Const.maxPartialLength, "partial length is always bounded") + let maxComp = min(partialLength, key.count - depth) + + for index in 0.. +typealias NodePtr = UnsafeMutableRawPointer +typealias ChildSlotPtr = UnsafeMutablePointer + +/// Shared protocol implementation for Node types in an Adaptive Radix Tree +protocol Node: NodePrettyPrinter { + typealias Index = Int + + var pointer: NodePtr { get } + var count: Int { get set } + var partialLength: Int { get } + var partialBytes: PartialBytes { get set } + + func type() -> NodeType + + func index(forKey k: KeyPart) -> Index? + func index() -> Index? + func next(index: Index) -> Index? + + func child(forKey k: KeyPart) -> NodePtr? + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? + func child(at: Index) -> NodePtr? + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? + + mutating func addChild(forKey k: KeyPart, node: NodePtr) + mutating func addChild(forKey k: KeyPart, node: Node) + mutating func addChild( + forKey k: KeyPart, + node: Node, + ref: ChildSlotPtr?) + mutating func addChild( + forKey k: KeyPart, + node: NodePtr, + ref: ChildSlotPtr?) + + // TODO: Shrinking/expand logic can be moved out. + mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) + mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) +} diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift new file mode 100644 index 000000000..3dd9b61e6 --- /dev/null +++ b/Sources/ARTreeModule/Node16.swift @@ -0,0 +1,163 @@ +struct Node16 { + static let numKeys = 16 + + var pointer: NodePtr + var keys: UnsafeMutableBufferPointer + var childs: UnsafeMutableBufferPointer + + init(ptr: NodePtr) { + self.pointer = ptr + let body = ptr + MemoryLayout.stride + self.keys = UnsafeMutableBufferPointer( + start: body.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + + let childPtr = (body + Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: NodePtr?.self) + self.childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + } +} + +extension Node16 { + static func allocate() -> Self { + let buf = NodeBuffer.allocate(type: .node16, size: size) + return Self(ptr: buf) + } + + static func allocate(copyFrom: Node4) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + UnsafeMutableRawBufferPointer(node.keys).copyBytes(from: copyFrom.keys) + UnsafeMutableRawBufferPointer(node.childs).copyBytes( + from: UnsafeMutableRawBufferPointer(copyFrom.childs)) + return node + } + + static func allocate(copyFrom: Node48) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + + var slot = 0 + for key: UInt8 in 0...255 { + let childPosition = Int(copyFrom.keys[Int(key)]) + if childPosition == 0xFF { + continue + } + + node.keys[slot] = key + node.childs[slot] = copyFrom.childs[childPosition] + slot += 1 + } + + assert(slot == node.count) + return node + } + + static var size: Int { + MemoryLayout.stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout.stride) + } +} + +extension Node16: Node { + func type() -> NodeType { .node16 } + + func index(forKey k: KeyPart) -> Index? { + for (index, key) in keys.enumerated() { + if key == k { + return index + } + } + + return nil + } + + func index() -> Index? { + return 0 + } + + func next(index: Index) -> Index? { + let next = index + 1 + return next < count ? next : nil + } + + func _insertSlot(forKey k: KeyPart) -> Int? { + // TODO: Binary search. + if count >= Self.numKeys { + return nil + } + + for idx in 0..= Int(k) { + return idx + } + } + + return count + } + + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + guard let index = index(forKey: k) else { + return nil + } + + ref = childs.baseAddress! + index + return child(at: index) + } + + func child(at: Index) -> NodePtr? { + assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") + return childs[at] + } + + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + assert( + index < Self.numKeys, + "maximum \(Self.numKeys) childs allowed, given index = \(index)") + ref = childs.baseAddress! + index + return childs[index] + } + + mutating func addChild( + forKey k: KeyPart, + node: NodePtr, + ref: ChildSlotPtr? + ) { + if let slot = _insertSlot(forKey: k) { + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 + } else { + var newNode = Node48.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } + + mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + assert(index < Self.numKeys, "index can't >= 16 in Node16") + assert(index < count, "not enough childs in node") + + let childBuf = child(at: index) + childBuf?.deallocate() + + keys[self.count] = 0 + childs[self.count] = nil + + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + + if count == 3 { + // Shrink to Node4. + let newNode = Node4.allocate(copyFrom: self) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } +} diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift new file mode 100644 index 000000000..54dd003d5 --- /dev/null +++ b/Sources/ARTreeModule/Node256.swift @@ -0,0 +1,125 @@ +struct ChildPointers { + private var childs: UnsafeMutableBufferPointer + + init(ptr: UnsafeMutableRawPointer) { + self.childs = UnsafeMutableBufferPointer( + start: ptr.assumingMemoryBound(to: Int.self), + count: 256 + ) + for idx in 0..<256 { + childs[idx] = 0 + } + } + + subscript(key: UInt8) -> NodePtr? { + get { + NodePtr(bitPattern: childs[Int(key)]) + } + + set { + if let ptr = newValue { + childs[Int(key)] = Int(bitPattern: UnsafeRawPointer(ptr)) + } else { + childs[Int(key)] = 0 + } + } + } +} + +struct Node256 { + var pointer: NodePtr + var childs: UnsafeMutableBufferPointer + + init(ptr: NodePtr) { + self.pointer = ptr + let body = ptr + MemoryLayout.stride + self.childs = UnsafeMutableBufferPointer( + start: body.assumingMemoryBound(to: NodePtr?.self), + count: 256 + ) + } +} + +extension Node256 { + static func allocate() -> Node256 { + let buf = NodeBuffer.allocate(type: .node256, size: size) + return Node256(ptr: buf) + } + + static func allocate(copyFrom: Node48) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + for key in 0..<256 { + let slot = Int(copyFrom.keys[key]) + if slot < 0xFF { + node.childs[key] = copyFrom.childs[slot] + } + } + assert(node.count == 48, "should have exactly 48 childs") + return node + } + + static var size: Int { + MemoryLayout.stride + 256 * MemoryLayout.stride + } +} + +extension Node256: Node { + func type() -> NodeType { .node256 } + + func index(forKey k: KeyPart) -> Index? { + return childs[Int(k)] != nil ? Int(k) : nil + } + + func index() -> Index? { + return next(index: -1) + } + + func next(index: Index) -> Index? { + for idx in index + 1..<256 { + if childs[idx] != nil { + return idx + } + } + + return nil + } + + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + ref = childs.baseAddress! + Int(k) + return childs[Int(k)] + } + + func child(at: Int) -> NodePtr? { + assert(at < 256, "maximum 256 childs allowed") + return childs[at] + } + + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + assert(index < 256, "maximum 256 childs allowed") + ref = childs.baseAddress! + index + return childs[index] + } + + mutating func addChild( + forKey k: KeyPart, + node: NodePtr, + ref: ChildSlotPtr? + ) { + assert(self.childs[Int(k)] == nil, "node for key \(k) already exists") + self.childs[Int(k)] = node + count += 1 + } + + public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + childs[index]?.deallocate() + childs[index] = nil + count -= 1 + + if count == 40 { + var newNode = Node48.allocate(copyFrom: self) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } +} diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift new file mode 100644 index 000000000..b42fbaee9 --- /dev/null +++ b/Sources/ARTreeModule/Node4.swift @@ -0,0 +1,143 @@ +struct Node4 { + static let numKeys: Int = 4 + + var pointer: NodePtr + var keys: UnsafeMutableBufferPointer + var childs: UnsafeMutableBufferPointer + + init(ptr: NodePtr) { + self.pointer = ptr + let body = ptr + MemoryLayout.stride + self.keys = UnsafeMutableBufferPointer( + start: body.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + + let childPtr = (body + Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: NodePtr?.self) + self.childs = UnsafeMutableBufferPointer(start: childPtr, count: 4) + } +} + +extension Node4 { + static func allocate() -> Self { + let buf = NodeBuffer.allocate(type: .node4, size: size) + return Self(ptr: buf) + } + + static func allocate(copyFrom: Node16) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + UnsafeMutableRawBufferPointer(node.keys).copyBytes( + from: UnsafeBufferPointer(rebasing: copyFrom.keys[0...stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout.stride) + } +} + +extension Node4: Node { + func type() -> NodeType { .node4 } + + func index(forKey k: KeyPart) -> Index? { + for (index, key) in keys.enumerated() { + if key == k { + return index + } + } + + return nil + } + + func index() -> Index? { + return 0 + } + + func next(index: Index) -> Index? { + let next = index + 1 + return next < count ? next : nil + } + + func _insertSlot(forKey k: KeyPart) -> Int? { + if count >= Self.numKeys { + return nil + } + + for idx in 0..= Int(k) { + return idx + } + } + + return count + } + + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + guard let index = index(forKey: k) else { + return nil + } + + ref = childs.baseAddress! + index + return child(at: index) + } + + func child(at: Index) -> NodePtr? { + assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") + return childs[at] + } + + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + assert( + index < Self.numKeys, + "maximum \(Self.numKeys) childs allowed, given index = \(index)") + ref = childs.baseAddress! + index + return childs[index] + } + + mutating func addChild( + forKey k: KeyPart, + node: NodePtr, + ref: ChildSlotPtr? + ) { + if let slot = _insertSlot(forKey: k) { + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 + } else { + var newNode = Node16.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } + + mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + assert(index < 4, "index can't >= 4 in Node4") + assert(index < count, "not enough childs in node") + + let childBuf = child(at: index) + childBuf?.deallocate() + + keys[self.count] = 0 + childs[self.count] = nil + + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + + if count == 1 { + // Shrink to leaf node. + ref?.pointee = childs[0] + pointer.deallocate() + } + } +} diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift new file mode 100644 index 000000000..fc4f3b5d6 --- /dev/null +++ b/Sources/ARTreeModule/Node48.swift @@ -0,0 +1,183 @@ +struct Node48 { + static let numKeys = 48 + + var pointer: NodePtr + var keys: UnsafeMutableBufferPointer + var childs: UnsafeMutableBufferPointer + + init(ptr: NodePtr) { + self.pointer = ptr + let body = ptr + MemoryLayout.stride + self.keys = UnsafeMutableBufferPointer( + start: body.assumingMemoryBound(to: KeyPart.self), + count: 256 + ) + + // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will + // refer to the first child. + // TODO: Can we initialize buffer using any stdlib method? + let childPtr = (body + 256 * MemoryLayout.stride) + .assumingMemoryBound(to: NodePtr?.self) + self.childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + } +} + +extension Node48 { + static func allocate() -> Self { + let buf = NodeBuffer.allocate(type: .node48, size: size) + let node = Self(ptr: buf) + for idx in 0..<256 { + node.keys[idx] = 0xFF + } + return node + } + + static func allocate(copyFrom: Node16) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + UnsafeMutableRawBufferPointer(node.childs).copyBytes( + from: UnsafeMutableRawBufferPointer(copyFrom.childs)) + for (idx, key) in copyFrom.keys.enumerated() { + node.keys[Int(key)] = UInt8(idx) + } + return node + } + + static func allocate(copyFrom: Node256) -> Self { + var node = Self.allocate() + node.copyHeader(from: copyFrom) + + var slot = 0 + for (key, child) in copyFrom.childs.enumerated() { + if child == nil { + continue + } + + node.keys[key] = UInt8(slot) + node.childs[slot] = child + slot += 1 + } + + return node + } + + static var size: Int { + MemoryLayout.stride + 256 * MemoryLayout.stride + Self.numKeys + * MemoryLayout.stride + } +} + +extension Node48: Node { + func type() -> NodeType { .node48 } + + func index(forKey k: KeyPart) -> Index? { + let childIndex = Int(keys[Int(k)]) + return childIndex == 0xFF ? nil : childIndex + } + + func index() -> Index? { + return next(index: -1) + } + + func next(index: Index) -> Index? { + for idx: Int in index + 1..<256 { + if keys[idx] != 0xFF { + return Int(keys[idx]) + } + } + + return nil + } + + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + let childIndex = Int(keys[Int(k)]) + if childIndex == 0xFF { + return nil + } + + ref = childs.baseAddress! + childIndex + return child(at: childIndex) + } + + func child(at: Int) -> NodePtr? { + assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") + return childs[at] + } + + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed") + ref = childs.baseAddress! + index + return childs[index] + } + + mutating func addChild( + forKey k: KeyPart, + node: NodePtr, + ref: ChildSlotPtr? + ) { + if count < Self.numKeys { + assert(self.keys[Int(k)] == 0xFF, "node for key \(k) already exists") + + guard let slot = findFreeSlot() else { + assert(false, "cannot find free slot in Node48") + return + } + + self.keys[Int(k)] = KeyPart(slot) + self.childs[slot] = node + self.count += 1 + } else { + var newNode = Node256.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } + + public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + let targetSlot = Int(keys[index]) + assert(targetSlot != 0xFF, "slot is empty already") + let targetChildBuf = childs[targetSlot] + targetChildBuf?.deallocate() + + // 1. Find out who has the last slot. + var lastSlotKey = 0 + for k in 0..<256 { + if keys[k] == count - 1 { + lastSlotKey = k + break + } + } + + // 2. Move last child slot into current child slot, and reset last child slot. + childs[targetSlot] = childs[count - 1] + childs[count - 1] = nil + + // 3. Map that key to current slot. + keys[lastSlotKey] = UInt8(targetSlot) + + // 4. Clear input key. + keys[index] = 0xFF + + // 5. Reduce number of children. + count -= 1 + + // 6. Shrink the node to Node16 if needed. + if count == 13 { // TODO: Should be made tunable. + var newNode = Node16.allocate(copyFrom: self) + ref?.pointee = newNode.pointer + pointer.deallocate() + } + } + + private func findFreeSlot() -> Int? { + for (index, child) in childs.enumerated() { + if child == nil { + return index + } + } + + return nil + } + +} diff --git a/Sources/ARTreeModule/NodeBuffer.swift b/Sources/ARTreeModule/NodeBuffer.swift new file mode 100644 index 000000000..28dee3568 --- /dev/null +++ b/Sources/ARTreeModule/NodeBuffer.swift @@ -0,0 +1,64 @@ +typealias NodeBuffer = UnsafeMutableRawPointer + +extension NodeBuffer { + static func allocate(type: NodeType, size: Int) -> NodeBuffer { + let size = size + let buf = NodeBuffer.allocate(byteCount: size, alignment: Const.defaultAlignment) + buf.initializeMemory(as: UInt8.self, repeating: 0, count: size) + + let header = buf.bindMemory(to: NodeHeader.self, capacity: MemoryLayout.stride) + header.pointee.type = type + header.pointee.count = 0 + header.pointee.partialLength = 0 + return buf + } + + func asNode(of: Value.Type) -> Node? { + switch self.type() { + case .leaf: + return NodeLeaf(ptr: self) + case .node4: + return Node4(ptr: self) + case .node16: + return Node16(ptr: self) + case .node48: + return Node48(ptr: self) + case .node256: + return Node256(ptr: self) + } + } + + func asNode4() -> Node4 { + let type: NodeType = load(as: NodeType.self) + assert(type == .node4, "node is not a node4") + return Node4(ptr: self) + } + + func asNode16() -> Node16 { + let type: NodeType = load(as: NodeType.self) + assert(type == .node16, "node is not a node16") + return Node16(ptr: self) + } + + func asNode48() -> Node48 { + let type: NodeType = load(as: NodeType.self) + assert(type == .node48, "node is not a node48") + return Node48(ptr: self) + } + + func asNode256() -> Node256 { + let type: NodeType = load(as: NodeType.self) + assert(type == .node256, "node is not a node256") + return Node256(ptr: self) + } + + func asLeaf() -> NodeLeaf { + let type: NodeType = load(as: NodeType.self) + assert(type == .leaf, "node is not a leaf") + return NodeLeaf(ptr: self) + } + + func type() -> NodeType { + load(as: NodeType.self) + } +} diff --git a/Sources/ARTreeModule/NodeHeader.swift b/Sources/ARTreeModule/NodeHeader.swift new file mode 100644 index 000000000..2d4c2846b --- /dev/null +++ b/Sources/ARTreeModule/NodeHeader.swift @@ -0,0 +1,12 @@ +typealias PartialBytes = FixedSizedArray8 + +struct NodeHeader { + var type: NodeType + var count: UInt16 = 0 // TODO: Make it smaller. Node256 issue. + var partialLength: UInt8 = 0 + var partialBytes: PartialBytes = PartialBytes(val: 0) + + init(_ type: NodeType) { + self.type = type + } +} diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift new file mode 100644 index 000000000..2eb0e69fc --- /dev/null +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -0,0 +1,116 @@ +struct NodeLeaf { + let pointer: NodePtr + + init(ptr: UnsafeMutableRawPointer) { + self.pointer = ptr + } +} + +extension NodeLeaf { + var key: Key { Array(keyPtr) } + var value: Value { valuePtr.pointee } + + var keyLength: UInt32 { + get { header.pointee.keyLength } + set { header.pointee.keyLength = newValue } + } + + private struct Header { + var type: NodeType + var keyLength: UInt32 + } + + private var header: UnsafeMutablePointer
{ + pointer.assumingMemoryBound(to: Header.self) + } + + var keyPtr: UnsafeMutableBufferPointer { + UnsafeMutableBufferPointer( + start: (self.pointer + MemoryLayout
.stride) + .assumingMemoryBound(to: KeyPart.self), + count: Int(keyLength)) + } + + var valuePtr: UnsafeMutablePointer { + let ptr = UnsafeMutableRawPointer(self.keyPtr.baseAddress?.advanced(by: Int(keyLength)))! + return ptr.assumingMemoryBound(to: Value.self) + } +} + +extension NodeLeaf { + static func allocate(key: Key, value: Value) -> Self { + let size = MemoryLayout
.stride + key.count + MemoryLayout.stride + let buf = NodeBuffer.allocate(byteCount: size, alignment: Const.defaultAlignment) + var leaf = Self(ptr: buf) + + leaf.keyLength = UInt32(key.count) + key.withUnsafeBytes { + UnsafeMutableRawBufferPointer(leaf.keyPtr).copyBytes(from: $0) + } + leaf.valuePtr.pointee = value + + return leaf + } + + func keyEquals(with key: Key, depth: Int = 0) -> Bool { + if key.count != keyLength { + return false + } + + for ii in depth.. Int { + let maxComp = Int(min(keyLength, other.keyLength) - UInt32(fromIndex)) + for index in 0.. NodeType { .leaf } + + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + fatalError(_failMsg) + } + + func index() -> Index? { + fatalError(_failMsg) + } + + func index(forKey k: KeyPart) -> Index? { + fatalError(_failMsg) + } + + func next(index: Index) -> Index? { + fatalError(_failMsg) + } + + func child(at index: Index) -> NodePtr? { + fatalError(_failMsg) + } + + func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + fatalError(_failMsg) + } + + func addChild(forKey k: KeyPart, node: NodePtr, ref: ChildSlotPtr?) { + fatalError(_failMsg) + } + + func deleteChild(at index: Index, ref: ChildSlotPtr?) { + fatalError(_failMsg) + } +} diff --git a/Sources/ARTreeModule/NodeType.swift b/Sources/ARTreeModule/NodeType.swift new file mode 100644 index 000000000..bff4d5c14 --- /dev/null +++ b/Sources/ARTreeModule/NodeType.swift @@ -0,0 +1,7 @@ +enum NodeType { + case leaf + case node4 + case node16 + case node48 + case node256 +} diff --git a/Sources/ARTreeModule/Utils.swift b/Sources/ARTreeModule/Utils.swift new file mode 100644 index 000000000..860ee94c0 --- /dev/null +++ b/Sources/ARTreeModule/Utils.swift @@ -0,0 +1,18 @@ +extension UnsafeMutableBufferPointer { + // Doesn't clear the shifted bytes. + func shiftRight(startIndex: Int, endIndex: Int, by: Int) { + var idx = endIndex + while idx >= startIndex { + self[idx + by] = self[idx] + idx -= 1 + } + } + + func shiftLeft(startIndex: Int, endIndex: Int, by: Int) { + var idx = startIndex + while idx <= endIndex { + self[idx - by] = self[idx] + idx += 1 + } + } +} diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift new file mode 100644 index 000000000..f587ab4d3 --- /dev/null +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -0,0 +1,171 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeDeleteTests: XCTestCase { + func testDeleteBasic() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [11, 21, 31]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + t.delete(key: [11, 21, 31]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + } + + func testDeleteAll() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [11, 21, 31]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + t.delete(key: [10, 20, 30]) + t.delete(key: [11, 21, 31]) + t.delete(key: [12, 22, 32]) + XCTAssertEqual(t.description, "<>") + } + + func testDeleteNested1() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3], value: [1]) + t.insert(key: [4, 5, 6], value: [2]) + t.insert(key: [1, 2, 4], value: [3]) + t.delete(key: [1, 2, 4]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 3[1, 2, 3] -> [1]\n" + + "└──○ 4: 3[4, 5, 6] -> [2]") + t.delete(key: [1, 2, 3]) + XCTAssertEqual(t.description, "○ 3[4, 5, 6] -> [2]") + t.delete(key: [4, 5, 6]) + XCTAssertEqual(t.description, "<>") + } + + func testDeleteNested2() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) + t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) + t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) + t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) + t.delete(key: [1, 2, 3, 4, 5, 6]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + "│ ├──○ 3: Node4 {childs=2, partial=[4]}\n" + + "│ │ ├──○ 8: 6[1, 2, 3, 4, 8, 9] -> [4]\n" + + "│ │ └──○ 9: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + } + + func testDeleteNonExistWithCommonPrefix() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) + t.delete(key: [1, 2, 3]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + } + + func testDeleteCompressToLeaf() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) + t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) + t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) + t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) + t.delete(key: [1, 2, 3, 4, 5, 6]) + t.delete(key: [1, 2, 3, 4, 8, 9]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + } + + func testDeleteCompressToNode4() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3, 4, 5], value: [1]) + t.insert(key: [2, 3, 4, 5, 5], value: [2]) + t.insert(key: [3, 4, 5, 6, 7], value: [3]) + t.insert(key: [4, 5, 6, 7, 8], value: [4]) + t.insert(key: [5, 6, 7, 8, 9], value: [5]) + XCTAssertEqual( + t.description, + "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + "├──○ 3: 5[3, 4, 5, 6, 7] -> [3]\n" + + "├──○ 4: 5[4, 5, 6, 7, 8] -> [4]\n" + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") + t.delete(key: [3, 4, 5, 6, 7]) + t.delete(key: [4, 5, 6, 7, 8]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=3, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") + } + + func testDeleteCompressToNode16() throws { + var t = ARTree<[UInt8]>() + for i: UInt8 in 0...16 { + t.insert(key: [i, i + 1], value: [i]) + } + XCTAssertEqual(t.root?.type(), .node48) + t.delete(key: [3, 4]) + t.delete(key: [4, 5]) + XCTAssertEqual( + t.description, + "○ Node48 {childs=15, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 5: 2[5, 6] -> [5]\n" + "├──○ 6: 2[6, 7] -> [6]\n" + + "├──○ 7: 2[7, 8] -> [7]\n" + "├──○ 8: 2[8, 9] -> [8]\n" + "├──○ 9: 2[9, 10] -> [9]\n" + + "├──○ 10: 2[10, 11] -> [10]\n" + "├──○ 11: 2[11, 12] -> [11]\n" + + "├──○ 12: 2[12, 13] -> [12]\n" + "├──○ 13: 2[13, 14] -> [13]\n" + + "├──○ 14: 2[14, 15] -> [14]\n" + "├──○ 15: 2[15, 16] -> [15]\n" + + "└──○ 16: 2[16, 17] -> [16]") + t.delete(key: [5, 6]) + t.delete(key: [6, 7]) + XCTAssertEqual( + t.description, + "○ Node16 {childs=13, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 7: 2[7, 8] -> [7]\n" + "├──○ 8: 2[8, 9] -> [8]\n" + + "├──○ 9: 2[9, 10] -> [9]\n" + "├──○ 10: 2[10, 11] -> [10]\n" + + "├──○ 11: 2[11, 12] -> [11]\n" + "├──○ 12: 2[12, 13] -> [12]\n" + + "├──○ 13: 2[13, 14] -> [13]\n" + "├──○ 14: 2[14, 15] -> [14]\n" + + "├──○ 15: 2[15, 16] -> [15]\n" + "└──○ 16: 2[16, 17] -> [16]") + } + + func testDeleteCompressToNode48() throws { + var t = ARTree<[UInt8]>() + for i: UInt8 in 0...48 { + t.insert(key: [i, i + 1], value: [i]) + } + XCTAssertEqual(t.root?.type(), .node256) + for i: UInt8 in 24...40 { + if i % 2 == 0 { + t.delete(key: [i, i + 1]) + } + } + XCTAssertEqual( + t.description, + "○ Node48 {childs=40, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 3: 2[3, 4] -> [3]\n" + "├──○ 4: 2[4, 5] -> [4]\n" + + "├──○ 5: 2[5, 6] -> [5]\n" + "├──○ 6: 2[6, 7] -> [6]\n" + "├──○ 7: 2[7, 8] -> [7]\n" + + "├──○ 8: 2[8, 9] -> [8]\n" + "├──○ 9: 2[9, 10] -> [9]\n" + "├──○ 10: 2[10, 11] -> [10]\n" + + "├──○ 11: 2[11, 12] -> [11]\n" + "├──○ 12: 2[12, 13] -> [12]\n" + + "├──○ 13: 2[13, 14] -> [13]\n" + "├──○ 14: 2[14, 15] -> [14]\n" + + "├──○ 15: 2[15, 16] -> [15]\n" + "├──○ 16: 2[16, 17] -> [16]\n" + + "├──○ 17: 2[17, 18] -> [17]\n" + "├──○ 18: 2[18, 19] -> [18]\n" + + "├──○ 19: 2[19, 20] -> [19]\n" + "├──○ 20: 2[20, 21] -> [20]\n" + + "├──○ 21: 2[21, 22] -> [21]\n" + "├──○ 22: 2[22, 23] -> [22]\n" + + "├──○ 23: 2[23, 24] -> [23]\n" + "├──○ 25: 2[25, 26] -> [25]\n" + + "├──○ 27: 2[27, 28] -> [27]\n" + "├──○ 29: 2[29, 30] -> [29]\n" + + "├──○ 31: 2[31, 32] -> [31]\n" + "├──○ 33: 2[33, 34] -> [33]\n" + + "├──○ 35: 2[35, 36] -> [35]\n" + "├──○ 37: 2[37, 38] -> [37]\n" + + "├──○ 39: 2[39, 40] -> [39]\n" + "├──○ 41: 2[41, 42] -> [41]\n" + + "├──○ 42: 2[42, 43] -> [42]\n" + "├──○ 43: 2[43, 44] -> [43]\n" + + "├──○ 44: 2[44, 45] -> [44]\n" + "├──○ 45: 2[45, 46] -> [45]\n" + + "├──○ 46: 2[46, 47] -> [46]\n" + "├──○ 47: 2[47, 48] -> [47]\n" + + "└──○ 48: 2[48, 49] -> [48]") + } +} diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift new file mode 100644 index 000000000..70cee05ee --- /dev/null +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -0,0 +1,87 @@ +import XCTest + +@testable import ARTreeModule + +func randomByteArray(minSize: Int, maxSize: Int, minByte: UInt8, maxByte: UInt8) -> [UInt8] { + let size = Int.random(in: minSize...maxSize) + var result: [UInt8] = (0..() + t.insert(key: [10, 20, 30], value: [11, 21, 31]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + XCTAssertEqual(t.getValue(key: [10, 20, 30]), [11, 21, 31]) + XCTAssertEqual(t.getValue(key: [11, 21, 31]), [12, 22, 32]) + XCTAssertEqual(t.getValue(key: [12, 22, 32]), [13, 23, 33]) + } + + func testGetValueRandom() throws { + // (0) Parameters. + let reps = 100 + let numKv = 10000 + let minSize = 10 + let maxSize = 100 + let minByte: UInt8 = 1 + let maxByte: UInt8 = 30 + + for iteration in 1...reps { + if iteration == (reps / 10) * iteration { + print("Iteration: \(iteration)") + } + + // (1) Generate test set. + var tree = ARTree<[UInt8]>() + var testSet: [[UInt8]: ([UInt8], [UInt8])] = [:] + for _ in 0.. \(value)") + // for (k, v) in testSetArray[0...idx] { + // let obs = tree.getValue(key: k) + // if obs ?? [] != v { + // print("Missed After Insert: \(k): \(obs) instead of \(v)") + // print(tree) + // XCTAssert(false) + // return + // } + // } + } + + // (3) Shuffle test-set. + testSetArray.shuffle() + + // (4) Check the entries in tree. + var missed = 0 + for (key, value) in testSetArray { + let obs = tree.getValue(key: key) ?? [] + if obs != value { + print("Missed: \(key): \(value) got \(obs)") + missed += 1 + } + } + + XCTAssertEqual(missed, 0) + if missed > 0 { + print("Total = \(numKv), Matched = \(numKv - missed), Missed = \(missed)") + print(tree) + return + } + } + } +} diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift new file mode 100644 index 000000000..b2ba0dd6b --- /dev/null +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -0,0 +1,326 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeInsertTests: XCTestCase { + func testInsertBasic() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [11, 21, 31]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + } + + func testInsertSharedPrefix() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [11, 21, 31]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + t.insert(key: [10, 20, 32], value: [1]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: Node4 {childs=2, partial=[20]}\n" + + "│ ├──○ 30: 3[10, 20, 30] -> [11, 21, 31]\n" + "│ └──○ 32: 3[10, 20, 32] -> [1]\n" + + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + } + + func testInsertExpandTo16() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1], value: [1]) + t.insert(key: [2], value: [2]) + t.insert(key: [3], value: [3]) + t.insert(key: [4], value: [4]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") + t.insert(key: [5], value: [5]) + XCTAssertEqual( + t.description, + "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") + } + + func testInsertExpandTo48() throws { + var t = ARTree<[UInt8]>() + for ii: UInt8 in 0..<40 { + t.insert(key: [ii + 1], value: [ii + 1]) + if ii < 4 { + XCTAssertEqual(t.root?.type(), .node4) + } else if ii < 16 { + XCTAssertEqual(t.root?.type(), .node16) + } else if ii < 48 { + XCTAssertEqual(t.root?.type(), .node48) + } + } + + let root = t.root!.asNode(of: [UInt].self)! + XCTAssertEqual(root.count, 40) + XCTAssertEqual( + t.description, + "○ Node48 {childs=40, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "├──○ 5: 1[5] -> [5]\n" + + "├──○ 6: 1[6] -> [6]\n" + "├──○ 7: 1[7] -> [7]\n" + "├──○ 8: 1[8] -> [8]\n" + + "├──○ 9: 1[9] -> [9]\n" + "├──○ 10: 1[10] -> [10]\n" + "├──○ 11: 1[11] -> [11]\n" + + "├──○ 12: 1[12] -> [12]\n" + "├──○ 13: 1[13] -> [13]\n" + "├──○ 14: 1[14] -> [14]\n" + + "├──○ 15: 1[15] -> [15]\n" + "├──○ 16: 1[16] -> [16]\n" + "├──○ 17: 1[17] -> [17]\n" + + "├──○ 18: 1[18] -> [18]\n" + "├──○ 19: 1[19] -> [19]\n" + "├──○ 20: 1[20] -> [20]\n" + + "├──○ 21: 1[21] -> [21]\n" + "├──○ 22: 1[22] -> [22]\n" + "├──○ 23: 1[23] -> [23]\n" + + "├──○ 24: 1[24] -> [24]\n" + "├──○ 25: 1[25] -> [25]\n" + "├──○ 26: 1[26] -> [26]\n" + + "├──○ 27: 1[27] -> [27]\n" + "├──○ 28: 1[28] -> [28]\n" + "├──○ 29: 1[29] -> [29]\n" + + "├──○ 30: 1[30] -> [30]\n" + "├──○ 31: 1[31] -> [31]\n" + "├──○ 32: 1[32] -> [32]\n" + + "├──○ 33: 1[33] -> [33]\n" + "├──○ 34: 1[34] -> [34]\n" + "├──○ 35: 1[35] -> [35]\n" + + "├──○ 36: 1[36] -> [36]\n" + "├──○ 37: 1[37] -> [37]\n" + "├──○ 38: 1[38] -> [38]\n" + + "├──○ 39: 1[39] -> [39]\n" + "└──○ 40: 1[40] -> [40]") + } + + func testInsertExpandTo256() throws { + var t = ARTree<[UInt8]>() + for ii: UInt8 in 0..<70 { + t.insert(key: [ii + 1], value: [ii + 1]) + if ii < 4 { + XCTAssertEqual(t.root?.type(), .node4) + } else if ii < 16 { + XCTAssertEqual(t.root?.type(), .node16) + } else if ii < 48 { + XCTAssertEqual(t.root?.type(), .node48) + } + } + + let root = t.root!.asNode(of: [UInt8].self)! + XCTAssertEqual(root.count, 70) + XCTAssertEqual( + t.description, + "○ Node256 {childs=70, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "├──○ 5: 1[5] -> [5]\n" + + "├──○ 6: 1[6] -> [6]\n" + "├──○ 7: 1[7] -> [7]\n" + "├──○ 8: 1[8] -> [8]\n" + + "├──○ 9: 1[9] -> [9]\n" + "├──○ 10: 1[10] -> [10]\n" + "├──○ 11: 1[11] -> [11]\n" + + "├──○ 12: 1[12] -> [12]\n" + "├──○ 13: 1[13] -> [13]\n" + "├──○ 14: 1[14] -> [14]\n" + + "├──○ 15: 1[15] -> [15]\n" + "├──○ 16: 1[16] -> [16]\n" + "├──○ 17: 1[17] -> [17]\n" + + "├──○ 18: 1[18] -> [18]\n" + "├──○ 19: 1[19] -> [19]\n" + "├──○ 20: 1[20] -> [20]\n" + + "├──○ 21: 1[21] -> [21]\n" + "├──○ 22: 1[22] -> [22]\n" + "├──○ 23: 1[23] -> [23]\n" + + "├──○ 24: 1[24] -> [24]\n" + "├──○ 25: 1[25] -> [25]\n" + "├──○ 26: 1[26] -> [26]\n" + + "├──○ 27: 1[27] -> [27]\n" + "├──○ 28: 1[28] -> [28]\n" + "├──○ 29: 1[29] -> [29]\n" + + "├──○ 30: 1[30] -> [30]\n" + "├──○ 31: 1[31] -> [31]\n" + "├──○ 32: 1[32] -> [32]\n" + + "├──○ 33: 1[33] -> [33]\n" + "├──○ 34: 1[34] -> [34]\n" + "├──○ 35: 1[35] -> [35]\n" + + "├──○ 36: 1[36] -> [36]\n" + "├──○ 37: 1[37] -> [37]\n" + "├──○ 38: 1[38] -> [38]\n" + + "├──○ 39: 1[39] -> [39]\n" + "├──○ 40: 1[40] -> [40]\n" + "├──○ 41: 1[41] -> [41]\n" + + "├──○ 42: 1[42] -> [42]\n" + "├──○ 43: 1[43] -> [43]\n" + "├──○ 44: 1[44] -> [44]\n" + + "├──○ 45: 1[45] -> [45]\n" + "├──○ 46: 1[46] -> [46]\n" + "├──○ 47: 1[47] -> [47]\n" + + "├──○ 48: 1[48] -> [48]\n" + "├──○ 49: 1[49] -> [49]\n" + "├──○ 50: 1[50] -> [50]\n" + + "├──○ 51: 1[51] -> [51]\n" + "├──○ 52: 1[52] -> [52]\n" + "├──○ 53: 1[53] -> [53]\n" + + "├──○ 54: 1[54] -> [54]\n" + "├──○ 55: 1[55] -> [55]\n" + "├──○ 56: 1[56] -> [56]\n" + + "├──○ 57: 1[57] -> [57]\n" + "├──○ 58: 1[58] -> [58]\n" + "├──○ 59: 1[59] -> [59]\n" + + "├──○ 60: 1[60] -> [60]\n" + "├──○ 61: 1[61] -> [61]\n" + "├──○ 62: 1[62] -> [62]\n" + + "├──○ 63: 1[63] -> [63]\n" + "├──○ 64: 1[64] -> [64]\n" + "├──○ 65: 1[65] -> [65]\n" + + "├──○ 66: 1[66] -> [66]\n" + "├──○ 67: 1[67] -> [67]\n" + "├──○ 68: 1[68] -> [68]\n" + + "├──○ 69: 1[69] -> [69]\n" + "└──○ 70: 1[70] -> [70]") + } + + func testInsertAndGetSmallSetRepeat48() throws { + for ii in 0...10000 { + if ii % 1000 == 0 { + print("testInsertAndGet: Iteration: \(ii)") + } + + var testCase: [([UInt8], [UInt8])] = [ + ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), + ([45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), + ([132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), + ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), + ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), + ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node16) + + testCase.shuffle() + for (k, v) in testCase { + XCTAssertEqual(tree.getValue(key: k), v) + } + } + } + + func testInsertAndGetSmallSetRepeat256() throws { + for ii in 0...10000 { + if ii % 1000 == 0 { + print("testInsertAndGet: Iteration: \(ii)") + } + + var testCase: [([UInt8], [UInt8])] = [ + ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), + ([15, 45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), + ([15, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([15, 45, 201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), + ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), + ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), + ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ([15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ( + [99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243] + ), + ([118, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([24, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([45, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([12, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([22, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([15, 15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ( + [57, 99, 118, 20, 190, 173, 101, 67, 245], + [161, 154, 111, 179, 216, 198, 248, 206, 164, 243] + ), + ([57, 83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ([16, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([17, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([18, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([28, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([88, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([78, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([19, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node48) + + testCase.shuffle() + for (k, v) in testCase { + XCTAssertEqual(tree.getValue(key: k), v) + } + } + } + + func testInsertPrefixSmallLong() throws { + var testCase: [([UInt8], [UInt8])] = [ + ( + [1, 2, 1, 2, 3, 2, 3, 3, 1, 3, 1, 0, 0, 2, 0, 3, 0, 1, 1], + [1, 1, 2, 0, 3, 1, 3, 1, 0, 1, 3, 3, 1, 2, 3, 1, 1, 0, 1] + ), + ([1, 3, 2, 2, 1, 0, 0, 2, 3, 2, 0], [0, 0, 2, 0, 3, 3, 0, 3, 3, 0, 2, 3, 3, 1, 2, 3, 2]), + ([2, 0, 1, 0, 1, 2, 2, 3], [3, 3, 1, 3, 2, 2, 2, 1, 2, 0, 2, 1, 0, 0, 0, 2, 0, 1, 1, 1]), + ( + [3, 1, 2, 0, 3, 1, 0, 2, 1, 0, 0, 3, 3, 3, 3, 0, 2, 3], + [1, 0, 1, 0, 3, 3, 2, 2, 2, 1, 2, 1, 0, 3, 1, 3, 3, 1, 3, 3] + ), + ([2, 0, 2, 2, 0, 2, 2, 0, 3, 2, 2, 3, 1, 0, 1], [1, 1, 3, 2, 2, 1]), + ([2, 1, 2, 3, 1, 2, 2, 1, 2, 1, 0, 2, 2, 1], [3, 2, 2, 3, 0, 1, 0, 3, 3, 0, 1, 2]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node4) + + testCase.shuffle() + for (k, v) in testCase { + XCTAssertEqual(tree.getValue(key: k), v) + } + } + + func testInsertPrefixSmall2() throws { + var testCase: [([UInt8], [UInt8])] = [ + ([3, 1, 3, 1, 1, 2, 0], [2, 4, 0]), + ([2, 4, 4, 0], [4, 1, 0]), + ([3, 2, 1, 3, 1, 1, 0], [4, 3, 0]), + ([4, 4, 4, 4, 0], [4, 3, 0]), + ([1, 1, 2, 2, 4, 0], [3, 2, 0]), + ([3, 3, 1, 1, 3, 0], [4, 4, 4, 4, 0]), + ([3, 4, 4, 4, 0], [1, 3, 3, 2, 2, 2, 2, 0]), + ([3, 1, 3, 3, 0], [4, 3, 0]), + ([3, 1, 0], [2, 2, 3, 3, 4, 0]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + print("Inserting \(k) \(v)") + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node4) + + testCase.reverse() + for (k, v) in testCase { + print("Checking \(k) \(v)") + XCTAssertEqual(tree.getValue(key: k), v) + } + } + + func testInsertLongSharedPrefix1() throws { + var testCase: [([UInt8], [UInt8])] = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + print("Inserting \(k) \(v)") + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node4) + + testCase.reverse() + for (k, v) in testCase { + print("Checking \(k) \(v)") + XCTAssertEqual(tree.getValue(key: k), v) + } + } + + func testInsertLongSharedPrefix2() throws { + var testCase: [([UInt8], [UInt8])] = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), + ([1, 4, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [4]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20], [3]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + print("Inserting \(k) \(v)") + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node4) + + testCase.reverse() + for (k, v) in testCase { + print("Checking \(k) \(v)") + XCTAssertEqual(tree.getValue(key: k), v) + } + } + + func testInsertLongSharedPrefix3() throws { + var testCase: [([UInt8], [UInt8])] = [ + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), + ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20], [3]), + ] + + var tree = ARTree<[UInt8]>() + for (k, v) in testCase { + print("Inserting \(k) \(v)") + tree.insert(key: k, value: v) + } + XCTAssertEqual(tree.root?.type(), .node4) + + testCase.reverse() + for (k, v) in testCase { + print("Checking \(k) \(v)") + XCTAssertEqual(tree.getValue(key: k), v) + } + } +} diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift new file mode 100644 index 000000000..9851b93a2 --- /dev/null +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -0,0 +1,68 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNode16Tests: XCTestCase { + func test16Basic() throws { + var node = Node16.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") + } + + func test4AddInMiddle() throws { + var node = Node16.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) + node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" + + "├──○ 20: 1[20] -> [2]\n" + "└──○ 30: 1[30] -> [3]") + } + + func test16DeleteAtIndex() throws { + var node = Node16.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 0) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 1) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") + node.deleteChild(at: 0) + XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") + } + + func test16DeleteKey() throws { + var node = Node16.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 10) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 15) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 20) + XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift new file mode 100644 index 000000000..806757f9a --- /dev/null +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -0,0 +1,35 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNode256Tests: XCTestCase { + func test256Basic() throws { + var node = Node256.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node256 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") + } + + func test48DeleteAtIndex() throws { + var node = Node256.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node256 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 10) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 15) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 20) + XCTAssertEqual(node.print(value: [UInt8].self), "○ Node256 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift new file mode 100644 index 000000000..b504dd41b --- /dev/null +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -0,0 +1,35 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNode48Tests: XCTestCase { + func test48Basic() throws { + var node = Node48.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node48 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "└──○ 20: 1[20] -> [2]") + } + + func test48DeleteAtIndex() throws { + var node = Node48.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node48 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 10) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 15) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 20) + XCTAssertEqual(node.print(value: [UInt8].self), "○ Node48 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift new file mode 100644 index 000000000..03d8a4171 --- /dev/null +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -0,0 +1,90 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNode4Tests: XCTestCase { + func test4Basic() throws { + var node = Node4.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [11]\n" + "└──○ 20: 1[20] -> [22]") + } + + func test4AddInMiddle() throws { + var node = Node4.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) + node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" + + "├──○ 20: 1[20] -> [2]\n" + "└──○ 30: 1[30] -> [3]") + } + + func test4DeleteAtIndex() throws { + var node = Node4.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(at: 0) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + + var ptr: NodePtr? = node.pointer + node.deleteChild(at: 1, ref: &ptr) + XCTAssertNotNil(ptr) + XCTAssertEqual(ptr?.type(), .leaf) + } + + func test4ExapandTo16() throws { + var node = Node4.allocate() + + node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1])) + node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2])) + node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3])) + node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") + + var addr = Optional(node.pointer) + withUnsafeMutablePointer(to: &addr) { + let ref: ChildSlotPtr? = $0 + node.addChild(forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5]), ref: ref) + XCTAssertEqual( + ref!.pointee!.asNode(of: [UInt8].self)!.print(value: [UInt8].self), + "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") + } + } + + func test4DeleteKey() throws { + var node = Node4.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 10) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 15) + XCTAssertEqual( + node.print(value: [UInt8].self), + "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + node.deleteChild(forKey: 20) + XCTAssertEqual(node.print(value: [UInt8].self), "○ Node4 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift new file mode 100644 index 000000000..7304ccd27 --- /dev/null +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -0,0 +1,30 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNodeBasicTests: XCTestCase { + func testNodeSizes() throws { + let header = MemoryLayout.stride + XCTAssertEqual(header, 14) + + let childSlotSize = MemoryLayout.stride + let ptrSize = MemoryLayout.stride + let size4 = Node4.size + let size16 = Node16.size + let size48 = Node48.size + let size256 = Node256.size + + print("sizeOf(Int) = \(ptrSize)") + print("sizeOf(childSlot) = \(childSlotSize)") + print("sizeOf(Header) = \(header)") + print("sizeOf(.node4) = \(size4)") + print("sizeOf(.node16) = \(size16)") + print("sizeOf(.node48) = \(size48)") + print("sizeOf(.node256) = \(size256)") + + XCTAssertEqual(size4, header + 4 + 4 * ptrSize) + XCTAssertEqual(size16, header + 16 + 16 * ptrSize) + XCTAssertEqual(size48, header + 256 + 48 * ptrSize) + XCTAssertEqual(size256, header + 256 * ptrSize) + } +} diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift new file mode 100644 index 000000000..84df92e7f --- /dev/null +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -0,0 +1,80 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeNodeLeafTests: XCTestCase { + func testLeafBasic() throws { + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) + XCTAssertEqual(leaf1.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0]") + + let leaf2 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + XCTAssertEqual(leaf2.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") + + let leaf3 = NodeLeaf.allocate(key: [], value: []) + XCTAssertEqual(leaf3.print(value: [UInt8].self), "○ 0[] -> []") + } + + func testLeafKeyEquals() throws { + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) + XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 50])) + XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30])) + XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 40, 50])) + XCTAssertTrue(leaf1.keyEquals(with: [10, 20, 30, 40])) + } + + func testCasts() throws { + let ptr = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]).pointer + let leaf: NodeLeaf<[UInt8]> = ptr.asLeaf() + XCTAssertEqual(leaf.key, [10, 20, 30, 40]) + XCTAssertEqual(leaf.value, [0]) + } + + func testLeafLcp() throws { + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [0, 1, 2, 3], value: [0]), + fromIndex: 0), + 0) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [0], value: [0]), + fromIndex: 0), + 0) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [0, 1], value: [0]), + fromIndex: 0), + 0) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [10, 1], value: [0]), + fromIndex: 0), + 1) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [10, 20], value: [0]), + fromIndex: 0), + 2) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [10, 20], value: [0]), + fromIndex: 1), + 1) + XCTAssertEqual( + leaf1.longestCommonPrefix( + with: NodeLeaf.allocate(key: [10, 20], value: [0]), + fromIndex: 2), + 0) + + // Breaks the contract, so its OK that these fail. + // XCTAssertEqual( + // leaf1.longestCommonPrefix(with: NodeLeaf.allocate(key: [], value: [0]), + // fromIndex: 0), + // 0) + // XCTAssertEqual( + // leaf1.longestCommonPrefix(with: NodeLeaf.allocate(key: [10], value: [0]), + // fromIndex: 2), + // 0) + } +} diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift new file mode 100644 index 000000000..f92bfb31e --- /dev/null +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -0,0 +1,33 @@ +import XCTest + +@testable import ARTreeModule + +final class ARTreeSequenceTests: XCTestCase { + func testSequenceBasic() throws { + let sizes = [3, 12, 30, 70] + for size in sizes { + print("With \(size) nodes") + var t = ARTree<[UInt8]>() + var pairs: [(Key, [UInt8])] = [] + for i in 0...size { + let s = UInt8(i) + pairs.append(([s, s + 1, s + 2, s + 3], [s])) + } + + for (k, v) in pairs { + t.insert(key: k, value: v) + } + + var newPairs: [(Key, [UInt8])] = [] + for (k, v) in t { + newPairs.append((k, v)) + } + + XCTAssertEqual(pairs.count, newPairs.count) + for ((k1, v1), (k2, v2)) in zip(pairs, newPairs) { + XCTAssertEqual(k1, k2) + XCTAssertEqual(v1, v2) + } + } + } +} diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift new file mode 100644 index 000000000..085960114 --- /dev/null +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -0,0 +1,29 @@ +import XCTest + +@testable import ARTreeModule + +private class TestBox { + var d: String + + init(_ d: String) { + self.d = d + } +} + +final class ARTreeRefCountTest: XCTestCase { + func testRefCountBasic() throws { + // TODO: Why is it 2? + var x = TestBox("foo") + XCTAssertEqual(CFGetRetainCount(x), 2) + var t = ARTree() + XCTAssertEqual(CFGetRetainCount(x), 2) + t.insert(key: [10, 20, 30], value: x) + XCTAssertEqual(CFGetRetainCount(x), 3) + x = TestBox("bar") + XCTAssertEqual(CFGetRetainCount(x), 2) + x = t.getValue(key: [10, 20, 30])! + XCTAssertEqual(CFGetRetainCount(x), 3) + t.delete(key: [10, 20, 30]) + XCTAssertEqual(CFGetRetainCount(x), 2) + } +} From b6c1de1ea7126078f05e5782c2fec7fc8a585eb8 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 4 Aug 2023 10:51:52 -0700 Subject: [PATCH 02/67] Add license headers --- Sources/ARTreeModule/ARTree+Sequence.swift | 11 +++++++++++ Sources/ARTreeModule/ARTree+delete.swift | 11 +++++++++++ Sources/ARTreeModule/ARTree+get.swift | 11 +++++++++++ Sources/ARTreeModule/ARTree+insert.swift | 11 +++++++++++ Sources/ARTreeModule/ARTree.swift | 11 +++++++++++ Sources/ARTreeModule/Const.swift | 11 +++++++++++ Sources/ARTreeModule/FixedSizedArray.swift | 11 +++++++++++ Sources/ARTreeModule/Node+StringConverter.swift | 13 ++++++++++++- Sources/ARTreeModule/Node+basics.swift | 11 +++++++++++ Sources/ARTreeModule/Node.swift | 11 +++++++++++ Sources/ARTreeModule/Node16.swift | 11 +++++++++++ Sources/ARTreeModule/Node256.swift | 13 ++++++++++++- Sources/ARTreeModule/Node4.swift | 11 +++++++++++ Sources/ARTreeModule/Node48.swift | 13 ++++++++++++- Sources/ARTreeModule/NodeBuffer.swift | 11 +++++++++++ Sources/ARTreeModule/NodeHeader.swift | 11 +++++++++++ Sources/ARTreeModule/NodeLeaf.swift | 11 +++++++++++ Sources/ARTreeModule/NodeType.swift | 11 +++++++++++ Sources/ARTreeModule/Utils.swift | 11 +++++++++++ Tests/ARTreeModuleTests/DeleteTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/GetValueTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/InsertTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/Node16Tests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/Node256Tests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/Node48Tests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/Node4Tests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/NodeBasicTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/NodeLeafTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/SequenceTests.swift | 11 +++++++++++ Tests/ARTreeModuleTests/TreeRefCountTests.swift | 11 +++++++++++ 30 files changed, 333 insertions(+), 3 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 4eccec235..6e43084a0 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 ARTree: Sequence { public typealias Element = (Key, Value) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index c7003aecc..53ad436c1 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 ARTree { public mutating func delete(key: Key) -> Bool { var ref: ChildSlotPtr? = ChildSlotPtr(&root) diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index 3b0cc7fdd..2014f0729 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 ARTree { public func getValue(key: Key) -> Value? { var current = root diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index db54b438c..0b11a8caa 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 ARTree { public mutating func insert(key: Key, value: Value) -> Bool { var current: NodePtr? = root diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 974bb2005..e46c05330 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + // TODO: // * Check deallocate of nodes. // * Path compression when deleting. diff --git a/Sources/ARTreeModule/Const.swift b/Sources/ARTreeModule/Const.swift index c4d43833e..bfdfd5402 100644 --- a/Sources/ARTreeModule/Const.swift +++ b/Sources/ARTreeModule/Const.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct Const { static let indentWidth = 4 static let defaultAlignment = 4 diff --git a/Sources/ARTreeModule/FixedSizedArray.swift b/Sources/ARTreeModule/FixedSizedArray.swift index e542d147c..f90348366 100644 --- a/Sources/ARTreeModule/FixedSizedArray.swift +++ b/Sources/ARTreeModule/FixedSizedArray.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + private let FixedArray8Count = 8 struct FixedSizedArray8 { diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index ba1fb537f..e8b6c57c5 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + protocol NodePrettyPrinter { func print(value: Value.Type) -> String func prettyPrint(depth: Int, value: Value.Type) -> String @@ -41,7 +52,7 @@ extension Node { } extension NodeLeaf: NodePrettyPrinter { - func prettyPrint(depth: Int, value: Value.Type) -> String { + func prettyPrint<_Value>(depth: Int, value: _Value.Type) -> String { "\(self.keyLength)\(self.key) -> \(self.value)" } } diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/Node+basics.swift index 7e844f52e..3591636be 100644 --- a/Sources/ARTreeModule/Node+basics.swift +++ b/Sources/ARTreeModule/Node+basics.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 Node { fileprivate var header: NodeHeaderPtr { self.pointer.assumingMemoryBound(to: NodeHeader.self) } diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 8e7dfbe57..1bf43253c 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 typealias KeyPart = UInt8 public typealias Key = [KeyPart] diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 3dd9b61e6..a438414d6 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct Node16 { static let numKeys = 16 diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 54dd003d5..865d4b229 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct ChildPointers { private var childs: UnsafeMutableBufferPointer @@ -117,7 +128,7 @@ extension Node256: Node { count -= 1 if count == 40 { - var newNode = Node48.allocate(copyFrom: self) + let newNode = Node48.allocate(copyFrom: self) ref?.pointee = newNode.pointer pointer.deallocate() } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index b42fbaee9..2a6fb65a6 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct Node4 { static let numKeys: Int = 4 diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index fc4f3b5d6..0602d78b4 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct Node48 { static let numKeys = 48 @@ -164,7 +175,7 @@ extension Node48: Node { // 6. Shrink the node to Node16 if needed. if count == 13 { // TODO: Should be made tunable. - var newNode = Node16.allocate(copyFrom: self) + let newNode = Node16.allocate(copyFrom: self) ref?.pointee = newNode.pointer pointer.deallocate() } diff --git a/Sources/ARTreeModule/NodeBuffer.swift b/Sources/ARTreeModule/NodeBuffer.swift index 28dee3568..811f050a3 100644 --- a/Sources/ARTreeModule/NodeBuffer.swift +++ b/Sources/ARTreeModule/NodeBuffer.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + typealias NodeBuffer = UnsafeMutableRawPointer extension NodeBuffer { diff --git a/Sources/ARTreeModule/NodeHeader.swift b/Sources/ARTreeModule/NodeHeader.swift index 2d4c2846b..e8bc4d749 100644 --- a/Sources/ARTreeModule/NodeHeader.swift +++ b/Sources/ARTreeModule/NodeHeader.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + typealias PartialBytes = FixedSizedArray8 struct NodeHeader { diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 2eb0e69fc..40d26e251 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + struct NodeLeaf { let pointer: NodePtr diff --git a/Sources/ARTreeModule/NodeType.swift b/Sources/ARTreeModule/NodeType.swift index bff4d5c14..c3c8367fb 100644 --- a/Sources/ARTreeModule/NodeType.swift +++ b/Sources/ARTreeModule/NodeType.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + enum NodeType { case leaf case node4 diff --git a/Sources/ARTreeModule/Utils.swift b/Sources/ARTreeModule/Utils.swift index 860ee94c0..114efd23a 100644 --- a/Sources/ARTreeModule/Utils.swift +++ b/Sources/ARTreeModule/Utils.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 UnsafeMutableBufferPointer { // Doesn't clear the shifted bytes. func shiftRight(startIndex: Int, endIndex: Int, by: Int) { diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index f587ab4d3..2812744d7 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index 70cee05ee..9db979018 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index b2ba0dd6b..cdcbb4243 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index 9851b93a2..c93d8650f 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index 806757f9a..eabf3ea53 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index b504dd41b..5c0bc754e 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 03d8a4171..448568bf3 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index 7304ccd27..05c9e2e6e 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index 84df92e7f..725c97dc5 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift index f92bfb31e..104edf20a 100644 --- a/Tests/ARTreeModuleTests/SequenceTests.swift +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 085960114..e7ba28391 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -1,3 +1,14 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 @testable import ARTreeModule From 66a6727430481309d4fb56ebbf4b231c4e5f64a5 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 17 Aug 2023 20:02:08 -0700 Subject: [PATCH 03/67] Use ManagedBuffer for underlying storage --- Sources/ARTreeModule/ARTree+Sequence.swift | 15 +- Sources/ARTreeModule/ARTree+delete.swift | 13 +- Sources/ARTreeModule/ARTree+get.swift | 7 +- Sources/ARTreeModule/ARTree+insert.swift | 13 +- Sources/ARTreeModule/ARTree.swift | 4 +- Sources/ARTreeModule/Node+Storage.swift | 71 +++++ .../ARTreeModule/Node+StringConverter.swift | 104 +++---- Sources/ARTreeModule/Node+basics.swift | 27 +- Sources/ARTreeModule/Node.swift | 42 +-- Sources/ARTreeModule/Node16.swift | 204 ++++++++------ Sources/ARTreeModule/Node256.swift | 156 ++++++----- Sources/ARTreeModule/Node4.swift | 179 +++++++----- Sources/ARTreeModule/Node48.swift | 257 +++++++++++------- Sources/ARTreeModule/NodeBuffer.swift | 75 ----- Sources/ARTreeModule/NodeHeader.swift | 9 +- Sources/ARTreeModule/NodeLeaf.swift | 55 +--- Tests/ARTreeModuleTests/DeleteTests.swift | 157 +++++++---- Tests/ARTreeModuleTests/InsertTests.swift | 30 +- Tests/ARTreeModuleTests/Node4Tests.swift | 12 +- Tests/ARTreeModuleTests/NodeBasicTests.swift | 4 +- Tests/ARTreeModuleTests/NodeLeafTests.swift | 3 +- 21 files changed, 809 insertions(+), 628 deletions(-) create mode 100644 Sources/ARTreeModule/Node+Storage.swift delete mode 100644 Sources/ARTreeModule/NodeBuffer.swift diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 6e43084a0..92d4e01a6 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -13,16 +13,17 @@ extension ARTree: Sequence { public typealias Element = (Key, Value) public struct Iterator { - typealias _ChildIndex = Node.Index + typealias _ChildIndex = InternalNode.Index private let tree: ARTree - private var path: [(Node, _ChildIndex?)] + private var path: [(any InternalNode, _ChildIndex?)] init(tree: ARTree) { self.tree = tree self.path = [] - if let node = tree.root?.asNode(of: Value.self) { - self.path = [(node, node.index())] + if let node = tree.root { + let n = node as! any InternalNode + self.path = [(n, n.index())] } } } @@ -59,14 +60,14 @@ extension ARTree.Iterator: IteratorProtocol { } let next = node.child(at: index)! - if next.type() == .leaf { - let leaf: NodeLeaf = next.asLeaf() + if next.type == .leaf { + let leaf = next as! NodeLeaf let result = (leaf.key, leaf.value) advanceToNextChild() return result } - path.append((next.asNode(of: Value.self)!, node.index())) + path.append((next as! any InternalNode, node.index())) } } diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 53ad436c1..edbf6c65c 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -12,7 +12,7 @@ extension ARTree { public mutating func delete(key: Key) -> Bool { var ref: ChildSlotPtr? = ChildSlotPtr(&root) - if let node = root?.asNode(of: Value.self) { + if let node = root { return _delete(node: node, ref: &ref, key: key, depth: 0) } @@ -24,13 +24,13 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(node: Node, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool + private mutating func _delete(node: any Node, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool { var newDepth = depth - var node = node + var _node = node - if node.type() == .leaf { - let leaf = node as! NodeLeaf + if _node.type == .leaf { + let leaf = _node as! NodeLeaf if !leaf.keyEquals(with: key, depth: depth) { return false @@ -41,6 +41,7 @@ extension ARTree { return true } + var node = _node as! any InternalNode if node.partialLength > 0 { let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) assert(matchedBytes <= node.partialLength) @@ -53,7 +54,7 @@ extension ARTree { } var childRef: ChildSlotPtr? - let child = node.child(at: childPosition, ref: &childRef)!.asNode(of: Value.self)! + let child = node.child(at: childPosition, ref: &childRef)! if !_delete(node: child, ref: &childRef, key: key, depth: newDepth + 1) { return false } diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index 2014f0729..3fe8db58a 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -14,17 +14,18 @@ extension ARTree { var current = root var depth = 0 while depth <= key.count { - guard let node = current?.asNode(of: Value.self) else { + guard let _node = current else { return nil } - if node.type() == .leaf { - let leaf: NodeLeaf = node.pointer.asLeaf() + if _node.type == .leaf { + let leaf = _node as! NodeLeaf return leaf.keyEquals(with: key) ? leaf.value : nil } + let node = _node as! any InternalNode if node.partialLength > 0 { let prefixLen = node.prefixMismatch(withKey: key, fromIndex: depth) assert(prefixLen <= Const.maxPartialLength, "partial length is always bounded") diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 0b11a8caa..4e9e32314 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -11,7 +11,7 @@ extension ARTree { public mutating func insert(key: Key, value: Value) -> Bool { - var current: NodePtr? = root + var current: (any Node)? = root // Location of child (current) pointer in parent, i.e. memory address where the // address of current node is stored inside the parent node. @@ -20,13 +20,13 @@ extension ARTree { var depth = 0 while depth < key.count { - guard var node = current?.asNode(of: Value.self) else { + guard var _node = current else { assert(false, "current node can never be nil") } // Reached leaf already, replace it with a new node, or update the existing value. - if node.type() == .leaf { - let leaf = node as! NodeLeaf + if _node.type == .leaf { + let leaf = _node as! NodeLeaf if leaf.keyEquals(with: key) { //TODO: Replace value. fatalError("replace not supported") @@ -55,10 +55,11 @@ extension ARTree { newNode = nextNode } - ref?.pointee = newNode.pointer // Replace child in parent. + ref?.pointee = newNode // Replace child in parent. return true } + var node = _node as! any InternalNode if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) @@ -81,7 +82,7 @@ extension ARTree { let newLeaf = NodeLeaf.allocate(key: key, value: value) newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) - ref?.pointee = newNode.pointer + ref?.pointee = newNode return true } } diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index e46c05330..b2a74037d 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -27,9 +27,9 @@ // * Values should be whatever. public struct ARTree { - var root: NodePtr? + var root: (any Node)? public init() { - self.root = Node4.allocate().pointer + self.root = Node4.allocate() } } diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift new file mode 100644 index 000000000..477ee16fa --- /dev/null +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -0,0 +1,71 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +typealias RawNodeBuffer = ManagedBuffer + +final class NodeBuffer: RawNodeBuffer { + deinit { + ArtNode.deinitialize(NodeStorage(self)) + } +} + +struct NodeStorage { + typealias Header = InternalNodeHeader + var buf: NodeBuffer +} + +extension NodeStorage { + fileprivate init(_ buf: NodeBuffer) { + self.buf = buf + } + + init(_ raw: RawNodeBuffer) { + self.buf = unsafeDowncast(raw, to: NodeBuffer.self) + } +} + +extension NodeStorage { + static func create(type: NodeType, size: Int) -> RawNodeBuffer { + return NodeBuffer.create(minimumCapacity: size, + makingHeaderWith: {_ in type }) + } +} + +extension NodeStorage where ArtNode: InternalNode { + static func allocate() -> NodeStorage { + let size = ArtNode.size + let buf = NodeStorage.create(type: ArtNode.type, size: size) + let storage = NodeStorage(buf) + storage.withBodyPointer { $0.initializeMemory(as: UInt8.self, repeating: 0, count: size) } + return storage + } + + func withHeaderPointer(_ body: (UnsafeMutablePointer
) throws -> R) rethrows -> R { + return try buf.withUnsafeMutablePointerToElements { + return try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Header.self)) + } + } + + func withBodyPointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { + return try buf.withUnsafeMutablePointerToElements { + return try body(UnsafeMutableRawPointer($0).advanced(by: MemoryLayout
.stride)) + } + } +} + + +extension NodeStorage { + func getPointer() -> UnsafeMutableRawPointer { + return self.buf.withUnsafeMutablePointerToElements { + UnsafeMutableRawPointer($0) + } + } +} diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index e8b6c57c5..c54788605 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -32,7 +32,7 @@ func indent(_ width: Int, last: Bool) -> String { extension ARTree: CustomStringConvertible { public var description: String { - if let node = root?.asNode(of: Value.self) { + if let node = root { return "○ " + node.prettyPrint(depth: 0, value: Value.self) } else { return "<>" @@ -40,7 +40,7 @@ extension ARTree: CustomStringConvertible { } } -extension Node { +extension InternalNode { fileprivate var partial: [UInt8] { var arr = [UInt8](repeating: 0, count: partialLength) let bytes = partialBytes @@ -60,16 +60,18 @@ extension NodeLeaf: NodePrettyPrinter { extension Node4: NodePrettyPrinter { func prettyPrint(depth: Int, value: Value.Type) -> String { var output = "Node4 {childs=\(count), partial=\(partial)}\n" - for idx in 0..(depth: Int, value: Value.Type) -> String { var output = "Node16 {childs=\(count), partial=\(partial)}\n" - for idx in 0..(depth: Int, value: Value.Type) -> String { var output = "Node48 {childs=\(count), partial=\(partial)}\n" var total = 0 - for (key, slot) in keys.enumerated() { - if slot >= 0xFF { - continue - } + withBody { keys, childs in + for (key, slot) in keys.enumerated() { + if slot >= 0xFF { + continue + } - total += 1 - let last = total == count - output += indent(depth, last: last) - output += String(key) + ": " - output += child(at: Int(slot))!.asNode(of: Value.self)!.prettyPrint( - depth: depth + 1, - value: value) - if !last { - output += "\n" + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child(at: Int(slot))!.prettyPrint( + depth: depth + 1, + value: value) + if !last { + output += "\n" + } } } @@ -126,20 +132,22 @@ extension Node256: NodePrettyPrinter { func prettyPrint(depth: Int, value: Value.Type) -> String { var output = "Node256 {childs=\(count), partial=\(partial)}\n" var total = 0 - for (key, child) in childs.enumerated() { - if child == nil { - continue - } + withBody { childs in + for (key, child) in childs.enumerated() { + if child == nil { + continue + } - total += 1 - let last = total == count - output += indent(depth, last: last) - output += String(key) + ": " - output += child!.asNode(of: Value.self)!.prettyPrint( - depth: depth + 1, - value: value) - if !last { - output += "\n" + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child!.prettyPrint( + depth: depth + 1, + value: value) + if !last { + output += "\n" + } } } return output diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/Node+basics.swift index 3591636be..a84e50b8e 100644 --- a/Sources/ARTreeModule/Node+basics.swift +++ b/Sources/ARTreeModule/Node+basics.swift @@ -9,9 +9,7 @@ // //===----------------------------------------------------------------------===// -extension Node { - fileprivate var header: NodeHeaderPtr { self.pointer.assumingMemoryBound(to: NodeHeader.self) } - +extension InternalNode { var partialLength: Int { get { Int(self.header.pointee.partialLength) } set { @@ -30,27 +28,22 @@ extension Node { set { header.pointee.count = UInt16(newValue) } } - func child(forKey k: KeyPart) -> NodePtr? { - var ref = UnsafeMutablePointer(nil) + func child(forKey k: KeyPart) -> (any Node)? { + var ref = UnsafeMutablePointer<(any Node)?>(nil) return child(forKey: k, ref: &ref) } - mutating func addChild(forKey k: KeyPart, node: NodePtr) { - let ref = UnsafeMutablePointer(nil) - addChild(forKey: k, node: node, ref: ref) - } - - mutating func addChild(forKey k: KeyPart, node: Node) { - let ref = UnsafeMutablePointer(nil) + mutating func addChild(forKey k: KeyPart, node: any Node) { + let ref = UnsafeMutablePointer<(any Node)?>(nil) addChild(forKey: k, node: node, ref: ref) } mutating func addChild( forKey k: KeyPart, - node: Node, + node: any Node, ref: ChildSlotPtr? ) { - addChild(forKey: k, node: node.pointer, ref: ref) + addChild(forKey: k, node: node, ref: ref) } mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) { @@ -62,16 +55,16 @@ extension Node { } mutating func deleteChild(forKey k: KeyPart) { - var ptr: NodePtr? = self.pointer + var ptr: (any Node)? = self return deleteChild(forKey: k, ref: &ptr) } mutating func deleteChild(at index: Index) { - var ptr: NodePtr? = self.pointer + var ptr: (any Node)? = self deleteChild(at: index, ref: &ptr) } - mutating func copyHeader(from: any Node) { + mutating func copyHeader(from: any InternalNode) { let src = from.header.pointee header.pointee.count = src.count header.pointee.partialLength = src.partialLength diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 1bf43253c..664d2c45c 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -12,42 +12,50 @@ public typealias KeyPart = UInt8 public typealias Key = [KeyPart] -typealias NodeHeaderPtr = UnsafeMutablePointer -typealias NodePtr = UnsafeMutableRawPointer -typealias ChildSlotPtr = UnsafeMutablePointer +typealias ChildSlotPtr = UnsafeMutablePointer<(any Node)?> /// Shared protocol implementation for Node types in an Adaptive Radix Tree protocol Node: NodePrettyPrinter { + var storage: NodeStorage { get } + + var type: NodeType { get } +} + +protocol InternalNode: Node { typealias Index = Int + typealias Header = InternalNodeHeader + + static var type: NodeType { get } + static var size: Int { get } + + var header: UnsafeMutablePointer { get } - var pointer: NodePtr { get } var count: Int { get set } var partialLength: Int { get } var partialBytes: PartialBytes { get set } - func type() -> NodeType - func index(forKey k: KeyPart) -> Index? func index() -> Index? func next(index: Index) -> Index? - func child(forKey k: KeyPart) -> NodePtr? - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? - func child(at: Index) -> NodePtr? - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? + func child(forKey k: KeyPart) -> (any Node)? + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? + func child(at: Index) -> (any Node)? + func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? - mutating func addChild(forKey k: KeyPart, node: NodePtr) - mutating func addChild(forKey k: KeyPart, node: Node) + mutating func addChild(forKey k: KeyPart, node: any Node) mutating func addChild( forKey k: KeyPart, - node: Node, - ref: ChildSlotPtr?) - mutating func addChild( - forKey k: KeyPart, - node: NodePtr, + node: any Node, ref: ChildSlotPtr?) // TODO: Shrinking/expand logic can be moved out. mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) } + +extension Node { + static func deinitialize(_ storage: NodeStorage) { + // TODO + } +} diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index a438414d6..a07246364 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -10,38 +10,56 @@ //===----------------------------------------------------------------------===// struct Node16 { - static let numKeys = 16 + typealias Storage = NodeStorage - var pointer: NodePtr - var keys: UnsafeMutableBufferPointer - var childs: UnsafeMutableBufferPointer + var storage: Storage - init(ptr: NodePtr) { - self.pointer = ptr - let body = ptr + MemoryLayout.stride - self.keys = UnsafeMutableBufferPointer( - start: body.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) + init(ptr: RawNodeBuffer) { + self.init(storage: Storage(ptr)) + } + + init(storage: Storage) { + self.storage = storage + } +} - let childPtr = (body + Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: NodePtr?.self) - self.childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) +extension Node16 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + let childPtr = bodyPtr + .advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: (any Node)?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + + return try body(keys, childs) + } } } extension Node16 { static func allocate() -> Self { - let buf = NodeBuffer.allocate(type: .node16, size: size) - return Self(ptr: buf) + let buf: NodeStorage = NodeStorage.allocate() + return Self(storage: buf) } static func allocate(copyFrom: Node4) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) - UnsafeMutableRawBufferPointer(node.keys).copyBytes(from: copyFrom.keys) - UnsafeMutableRawBufferPointer(node.childs).copyBytes( - from: UnsafeMutableRawBufferPointer(copyFrom.childs)) + + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + UnsafeMutableRawBufferPointer(newKeys).copyBytes(from: fromKeys) + UnsafeMutableRawBufferPointer(newChilds).copyBytes( + from: UnsafeMutableRawBufferPointer(fromChilds)) + } + } return node } @@ -49,39 +67,55 @@ extension Node16 { var node = Self.allocate() node.copyHeader(from: copyFrom) - var slot = 0 - for key: UInt8 in 0...255 { - let childPosition = Int(copyFrom.keys[Int(key)]) - if childPosition == 0xFF { - continue + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + var slot = 0 + for key: UInt8 in 0...255 { + let childPosition = Int(fromKeys[Int(key)]) + if childPosition == 0xFF { + continue + } + + newKeys[slot] = key + newChilds[slot] = fromChilds[childPosition] + slot += 1 + } + + assert(slot == node.count) } - - node.keys[slot] = key - node.childs[slot] = copyFrom.childs[childPosition] - slot += 1 } - assert(slot == node.count) return node } +} + +extension Node16: Node { + static let type: NodeType = .node16 + var type: NodeType { .node16 } +} + +extension Node16: InternalNode { + static let numKeys: Int = 16 static var size: Int { - MemoryLayout.stride + Self.numKeys - * (MemoryLayout.stride + MemoryLayout.stride) + MemoryLayout.stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) } -} -extension Node16: Node { - func type() -> NodeType { .node16 } + var header: UnsafeMutablePointer { + storage.withHeaderPointer { $0 } + } func index(forKey k: KeyPart) -> Index? { - for (index, key) in keys.enumerated() { - if key == k { - return index + return withBody { keys, _ in + for (index, key) in keys.enumerated() { + if key == k { + return index + } } - } - return nil + return nil + } } func index() -> Index? { @@ -95,58 +129,68 @@ extension Node16: Node { func _insertSlot(forKey k: KeyPart) -> Int? { // TODO: Binary search. - if count >= Self.numKeys { - return nil - } + return withBody { keys, _ in + if count >= Self.numKeys { + return nil + } - for idx in 0..= Int(k) { - return idx + for idx in 0..= Int(k) { + return idx + } } - } - return count + return count + } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { guard let index = index(forKey: k) else { return nil } - ref = childs.baseAddress! + index - return child(at: index) + return withBody {_, childs in + ref = childs.baseAddress! + index + return child(at: index) + } } - func child(at: Index) -> NodePtr? { + func child(at: Index) -> (any Node)? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return childs[at] + return withBody { _, childs in + return childs[at] + } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { assert( index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") - ref = childs.baseAddress! + index - return childs[index] + return withBody { _, childs in + ref = childs.baseAddress! + index + return childs[index] + } } mutating func addChild( forKey k: KeyPart, - node: NodePtr, + node: any Node, ref: ChildSlotPtr? ) { - if let slot = _insertSlot(forKey: k) { - assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") - keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - keys[slot] = k - childs[slot] = node - count += 1 - } else { - var newNode = Node48.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode.pointer - pointer.deallocate() + withBody {keys, childs in + if let slot = _insertSlot(forKey: k) { + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 + } else { + var newNode = Node48.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode + // pointer.deallocate() + } } } @@ -155,20 +199,22 @@ extension Node16: Node { assert(index < count, "not enough childs in node") let childBuf = child(at: index) - childBuf?.deallocate() + // childBuf?.deallocate() - keys[self.count] = 0 - childs[self.count] = nil + withBody { keys, childs in + keys[self.count] = 0 + childs[self.count] = nil - count -= 1 - keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - if count == 3 { - // Shrink to Node4. - let newNode = Node4.allocate(copyFrom: self) - ref?.pointee = newNode.pointer - pointer.deallocate() + if count == 3 { + // Shrink to Node4. + let newNode = Node4.allocate(copyFrom: self) + ref?.pointee = newNode + // pointer.deallocate() + } } } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 865d4b229..d529a872c 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -9,77 +9,81 @@ // //===----------------------------------------------------------------------===// -struct ChildPointers { - private var childs: UnsafeMutableBufferPointer - - init(ptr: UnsafeMutableRawPointer) { - self.childs = UnsafeMutableBufferPointer( - start: ptr.assumingMemoryBound(to: Int.self), - count: 256 - ) - for idx in 0..<256 { - childs[idx] = 0 - } - } +struct Node256 { + typealias Storage = NodeStorage - subscript(key: UInt8) -> NodePtr? { - get { - NodePtr(bitPattern: childs[Int(key)]) - } + var storage: Storage - set { - if let ptr = newValue { - childs[Int(key)] = Int(bitPattern: UnsafeRawPointer(ptr)) - } else { - childs[Int(key)] = 0 - } - } + init(ptr: RawNodeBuffer) { + self.init(storage: Storage(ptr)) + } + + init(storage: Storage) { + self.storage = storage } } -struct Node256 { - var pointer: NodePtr - var childs: UnsafeMutableBufferPointer - - init(ptr: NodePtr) { - self.pointer = ptr - let body = ptr + MemoryLayout.stride - self.childs = UnsafeMutableBufferPointer( - start: body.assumingMemoryBound(to: NodePtr?.self), - count: 256 - ) +extension Node256 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + + func withBody(body: (Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { + return try body( + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: (any Node)?.self), + count: 256)) + } } } extension Node256 { static func allocate() -> Node256 { - let buf = NodeBuffer.allocate(type: .node256, size: size) - return Node256(ptr: buf) + let buf: NodeStorage = NodeStorage.allocate() + return Node256(storage: buf) } static func allocate(copyFrom: Node48) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) - for key in 0..<256 { - let slot = Int(copyFrom.keys[key]) - if slot < 0xFF { - node.childs[key] = copyFrom.childs[slot] + + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newChilds in + for key in 0..<256 { + let slot = Int(fromKeys[key]) + if slot < 0xFF { + newChilds[key] = fromChilds[slot] + } + } } } + assert(node.count == 48, "should have exactly 48 childs") return node } +} + + +extension Node256: Node { + static let type: NodeType = .node256 + var type: NodeType { .node256 } +} + +extension Node256: InternalNode { + static let numKeys: Int = 256 static var size: Int { - MemoryLayout.stride + 256 * MemoryLayout.stride + MemoryLayout.stride + 256 * MemoryLayout<(any Node)?>.stride } -} -extension Node256: Node { - func type() -> NodeType { .node256 } + var header: UnsafeMutablePointer { + storage.withHeaderPointer { $0 } + } func index(forKey k: KeyPart) -> Index? { - return childs[Int(k)] != nil ? Int(k) : nil + return withBody { childs in + return childs[Int(k)] != nil ? Int(k) : nil + } } func index() -> Index? { @@ -87,50 +91,62 @@ extension Node256: Node { } func next(index: Index) -> Index? { - for idx in index + 1..<256 { - if childs[idx] != nil { - return idx + return withBody { childs in + for idx in index + 1..<256 { + if childs[idx] != nil { + return idx + } } - } - return nil + return nil + } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { - ref = childs.baseAddress! + Int(k) - return childs[Int(k)] + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + return withBody { childs in + ref = childs.baseAddress! + Int(k) + return childs[Int(k)] + } } - func child(at: Int) -> NodePtr? { + func child(at: Int) -> (any Node)? { assert(at < 256, "maximum 256 childs allowed") - return childs[at] + return withBody { childs in + return childs[at] + } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { assert(index < 256, "maximum 256 childs allowed") - ref = childs.baseAddress! + index - return childs[index] + return withBody { childs in + ref = childs.baseAddress! + index + return childs[index] + } } mutating func addChild( forKey k: KeyPart, - node: NodePtr, + node: any Node, ref: ChildSlotPtr? ) { - assert(self.childs[Int(k)] == nil, "node for key \(k) already exists") - self.childs[Int(k)] = node - count += 1 + return withBody { childs in + assert(childs[Int(k)] == nil, "node for key \(k) already exists") + childs[Int(k)] = node + count += 1 + } } public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { - childs[index]?.deallocate() - childs[index] = nil - count -= 1 - - if count == 40 { - let newNode = Node48.allocate(copyFrom: self) - ref?.pointee = newNode.pointer - pointer.deallocate() + return withBody { childs in + // childs[index]?.deallocate() + childs[index] = nil + count -= 1 + + if count == 40 { + let newNode = Node48.allocate(copyFrom: self) + ref?.pointee = newNode + // pointer.deallocate() + } } } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 2a6fb65a6..195917857 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -12,58 +12,84 @@ struct Node4 { static let numKeys: Int = 4 - var pointer: NodePtr - var keys: UnsafeMutableBufferPointer - var childs: UnsafeMutableBufferPointer - - init(ptr: NodePtr) { - self.pointer = ptr - let body = ptr + MemoryLayout.stride - self.keys = UnsafeMutableBufferPointer( - start: body.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) - - let childPtr = (body + Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: NodePtr?.self) - self.childs = UnsafeMutableBufferPointer(start: childPtr, count: 4) + typealias Storage = NodeStorage + + var storage: Storage +} + +extension Node4 { + init(ptr: RawNodeBuffer) { + self.init(storage: Storage(ptr)) + } +} + +extension Node4: Node { + static let type: NodeType = .node4 + var type: NodeType { .node4 } +} + +extension Node4 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + let childPtr = bodyPtr + .advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: (any Node)?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + + return try body(keys, childs) + } } } extension Node4 { static func allocate() -> Self { - let buf = NodeBuffer.allocate(type: .node4, size: size) - return Self(ptr: buf) + let buf: NodeStorage = NodeStorage.allocate() + return Self(storage: buf) } static func allocate(copyFrom: Node16) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) - UnsafeMutableRawBufferPointer(node.keys).copyBytes( - from: UnsafeBufferPointer(rebasing: copyFrom.keys[0...stride + Self.numKeys - * (MemoryLayout.stride + MemoryLayout.stride) + MemoryLayout.stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) } -} -extension Node4: Node { - func type() -> NodeType { .node4 } + var header: UnsafeMutablePointer { + storage.withHeaderPointer { $0 } + } func index(forKey k: KeyPart) -> Index? { - for (index, key) in keys.enumerated() { - if key == k { - return index + return withBody { keys, _ in + for (index, key) in keys.enumerated() { + if key == k { + return index + } } - } - return nil + return nil + } } func index() -> Index? { @@ -76,58 +102,68 @@ extension Node4: Node { } func _insertSlot(forKey k: KeyPart) -> Int? { - if count >= Self.numKeys { - return nil - } + return withBody { keys, _ in + if count >= Self.numKeys { + return nil + } - for idx in 0..= Int(k) { - return idx + for idx in 0..= Int(k) { + return idx + } } - } - return count + return count + } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { guard let index = index(forKey: k) else { return nil } - ref = childs.baseAddress! + index - return child(at: index) + return withBody {_, childs in + ref = childs.baseAddress! + index + return child(at: index) + } } - func child(at: Index) -> NodePtr? { + func child(at: Index) -> (any Node)? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") - return childs[at] + return withBody { _, childs in + return childs[at] + } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { assert( index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") - ref = childs.baseAddress! + index - return childs[index] + return withBody { _, childs in + ref = childs.baseAddress! + index + return childs[index] + } } mutating func addChild( forKey k: KeyPart, - node: NodePtr, + node: any Node, ref: ChildSlotPtr? ) { - if let slot = _insertSlot(forKey: k) { - assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") - keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - keys[slot] = k - childs[slot] = node - count += 1 - } else { - var newNode = Node16.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode.pointer - pointer.deallocate() + withBody { keys, childs in + if let slot = _insertSlot(forKey: k) { + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 + } else { + var newNode = Node16.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode + // pointer.deallocate() + } } } @@ -136,19 +172,20 @@ extension Node4: Node { assert(index < count, "not enough childs in node") let childBuf = child(at: index) - childBuf?.deallocate() + // childBuf?.deallocate() - keys[self.count] = 0 - childs[self.count] = nil + withBody { keys, childs in + keys[self.count] = 0 + childs[self.count] = nil - count -= 1 - keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - if count == 1 { - // Shrink to leaf node. - ref?.pointee = childs[0] - pointer.deallocate() + if count == 1 { + // Shrink to leaf node. + ref?.pointee = childs[0] + } } } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 0602d78b4..99a0e89ae 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -10,47 +10,71 @@ //===----------------------------------------------------------------------===// struct Node48 { - static let numKeys = 48 - - var pointer: NodePtr - var keys: UnsafeMutableBufferPointer - var childs: UnsafeMutableBufferPointer - - init(ptr: NodePtr) { - self.pointer = ptr - let body = ptr + MemoryLayout.stride - self.keys = UnsafeMutableBufferPointer( - start: body.assumingMemoryBound(to: KeyPart.self), - count: 256 - ) - - // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will - // refer to the first child. - // TODO: Can we initialize buffer using any stdlib method? - let childPtr = (body + 256 * MemoryLayout.stride) - .assumingMemoryBound(to: NodePtr?.self) - self.childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + typealias Storage = NodeStorage + + var storage: Storage + + init(ptr: RawNodeBuffer) { + self.init(storage: Storage(ptr)) + } + + init(storage: Storage) { + self.storage = storage + } +} + + +extension Node48 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: 256 + ) + + // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will + // refer to the first child. + // TODO: Can we initialize buffer using any stdlib method? + let childPtr = bodyPtr + .advanced(by: 256 * MemoryLayout.stride) + .assumingMemoryBound(to: (any Node)?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + + return try body(keys, childs) + } } } extension Node48 { static func allocate() -> Self { - let buf = NodeBuffer.allocate(type: .node48, size: size) - let node = Self(ptr: buf) - for idx in 0..<256 { - node.keys[idx] = 0xFF + let buf: NodeStorage = NodeStorage.allocate() + let storage = Self(storage: buf) + + storage.withBody { keys, _ in + for idx in 0..<256 { + keys[idx] = 0xFF + } } - return node + return storage } static func allocate(copyFrom: Node16) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) - UnsafeMutableRawBufferPointer(node.childs).copyBytes( - from: UnsafeMutableRawBufferPointer(copyFrom.childs)) - for (idx, key) in copyFrom.keys.enumerated() { - node.keys[Int(key)] = UInt8(idx) + + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + UnsafeMutableRawBufferPointer(newChilds).copyBytes( + from: UnsafeMutableRawBufferPointer(fromChilds)) + for (idx, key) in fromKeys.enumerated() { + newKeys[Int(key)] = UInt8(idx) + } + } } + return node } @@ -58,32 +82,48 @@ extension Node48 { var node = Self.allocate() node.copyHeader(from: copyFrom) - var slot = 0 - for (key, child) in copyFrom.childs.enumerated() { - if child == nil { - continue + copyFrom.withBody { fromChilds in + node.withBody { newKeys, newChilds in + var slot = 0 + for (key, child) in fromChilds.enumerated() { + if child == nil { + continue + } + + newKeys[key] = UInt8(slot) + newChilds[slot] = child + slot += 1 + } } - - node.keys[key] = UInt8(slot) - node.childs[slot] = child - slot += 1 } return node } +} + + +extension Node48: Node { + static let type: NodeType = .node48 + var type: NodeType { .node48 } +} + +extension Node48: InternalNode { + static let numKeys: Int = 48 static var size: Int { - MemoryLayout.stride + 256 * MemoryLayout.stride + Self.numKeys - * MemoryLayout.stride + MemoryLayout.stride + 256*MemoryLayout.stride + + Self.numKeys*MemoryLayout<(any Node)?>.stride } -} -extension Node48: Node { - func type() -> NodeType { .node48 } + var header: UnsafeMutablePointer { + storage.withHeaderPointer { $0 } + } func index(forKey k: KeyPart) -> Index? { - let childIndex = Int(keys[Int(k)]) - return childIndex == 0xFF ? nil : childIndex + return withBody { keys, _ in + let childIndex = Int(keys[Int(k)]) + return childIndex == 0xFF ? nil : childIndex + } } func index() -> Index? { @@ -91,104 +131,119 @@ extension Node48: Node { } func next(index: Index) -> Index? { - for idx: Int in index + 1..<256 { - if keys[idx] != 0xFF { - return Int(keys[idx]) + return withBody { keys, _ in + for idx: Int in index + 1..<256 { + if keys[idx] != 0xFF { + return Int(keys[idx]) + } } - } - - return nil - } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { - let childIndex = Int(keys[Int(k)]) - if childIndex == 0xFF { return nil } + } - ref = childs.baseAddress! + childIndex - return child(at: childIndex) + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + return withBody { keys, childs in + let childIndex = Int(keys[Int(k)]) + if childIndex == 0xFF { + return nil + } + + ref = childs.baseAddress! + childIndex + return child(at: childIndex) + } } - func child(at: Int) -> NodePtr? { + func child(at: Int) -> (any Node)? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return childs[at] + return withBody { _, childs in + return childs[at] + } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - ref = childs.baseAddress! + index - return childs[index] + return withBody { _, childs in + ref = childs.baseAddress! + index + return childs[index] + } } mutating func addChild( forKey k: KeyPart, - node: NodePtr, + node: any Node, ref: ChildSlotPtr? ) { if count < Self.numKeys { - assert(self.keys[Int(k)] == 0xFF, "node for key \(k) already exists") + withBody { keys, childs in + assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") + + guard let slot = findFreeSlot() else { + assert(false, "cannot find free slot in Node48") + return + } - guard let slot = findFreeSlot() else { - assert(false, "cannot find free slot in Node48") - return + keys[Int(k)] = KeyPart(slot) + childs[slot] = node } - self.keys[Int(k)] = KeyPart(slot) - self.childs[slot] = node self.count += 1 } else { var newNode = Node256.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode.pointer - pointer.deallocate() + ref?.pointee = newNode + // pointer.deallocate() } } public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { - let targetSlot = Int(keys[index]) - assert(targetSlot != 0xFF, "slot is empty already") - let targetChildBuf = childs[targetSlot] - targetChildBuf?.deallocate() - - // 1. Find out who has the last slot. - var lastSlotKey = 0 - for k in 0..<256 { - if keys[k] == count - 1 { - lastSlotKey = k - break + withBody { keys, childs in + let targetSlot = Int(keys[index]) + assert(targetSlot != 0xFF, "slot is empty already") + let targetChildBuf = childs[targetSlot] + // targetChildBuf?.deallocate() + + // 1. Find out who has the last slot. + var lastSlotKey = 0 + for k in 0..<256 { + if keys[k] == count - 1 { + lastSlotKey = k + break + } } - } - // 2. Move last child slot into current child slot, and reset last child slot. - childs[targetSlot] = childs[count - 1] - childs[count - 1] = nil + // 2. Move last child slot into current child slot, and reset last child slot. + childs[targetSlot] = childs[count - 1] + childs[count - 1] = nil - // 3. Map that key to current slot. - keys[lastSlotKey] = UInt8(targetSlot) + // 3. Map that key to current slot. + keys[lastSlotKey] = UInt8(targetSlot) - // 4. Clear input key. - keys[index] = 0xFF + // 4. Clear input key. + keys[index] = 0xFF - // 5. Reduce number of children. - count -= 1 + // 5. Reduce number of children. + count -= 1 - // 6. Shrink the node to Node16 if needed. - if count == 13 { // TODO: Should be made tunable. - let newNode = Node16.allocate(copyFrom: self) - ref?.pointee = newNode.pointer - pointer.deallocate() + // 6. Shrink the node to Node16 if needed. + if count == 13 { // TODO: Should be made tunable. + let newNode = Node16.allocate(copyFrom: self) + ref?.pointee = newNode + // pointer.deallocate() + } } } private func findFreeSlot() -> Int? { - for (index, child) in childs.enumerated() { - if child == nil { - return index + return withBody { _, childs in + for (index, child) in childs.enumerated() { + if child == nil { + return index + } } - } - return nil + return nil + } } } diff --git a/Sources/ARTreeModule/NodeBuffer.swift b/Sources/ARTreeModule/NodeBuffer.swift deleted file mode 100644 index 811f050a3..000000000 --- a/Sources/ARTreeModule/NodeBuffer.swift +++ /dev/null @@ -1,75 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 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 -// -//===----------------------------------------------------------------------===// - -typealias NodeBuffer = UnsafeMutableRawPointer - -extension NodeBuffer { - static func allocate(type: NodeType, size: Int) -> NodeBuffer { - let size = size - let buf = NodeBuffer.allocate(byteCount: size, alignment: Const.defaultAlignment) - buf.initializeMemory(as: UInt8.self, repeating: 0, count: size) - - let header = buf.bindMemory(to: NodeHeader.self, capacity: MemoryLayout.stride) - header.pointee.type = type - header.pointee.count = 0 - header.pointee.partialLength = 0 - return buf - } - - func asNode(of: Value.Type) -> Node? { - switch self.type() { - case .leaf: - return NodeLeaf(ptr: self) - case .node4: - return Node4(ptr: self) - case .node16: - return Node16(ptr: self) - case .node48: - return Node48(ptr: self) - case .node256: - return Node256(ptr: self) - } - } - - func asNode4() -> Node4 { - let type: NodeType = load(as: NodeType.self) - assert(type == .node4, "node is not a node4") - return Node4(ptr: self) - } - - func asNode16() -> Node16 { - let type: NodeType = load(as: NodeType.self) - assert(type == .node16, "node is not a node16") - return Node16(ptr: self) - } - - func asNode48() -> Node48 { - let type: NodeType = load(as: NodeType.self) - assert(type == .node48, "node is not a node48") - return Node48(ptr: self) - } - - func asNode256() -> Node256 { - let type: NodeType = load(as: NodeType.self) - assert(type == .node256, "node is not a node256") - return Node256(ptr: self) - } - - func asLeaf() -> NodeLeaf { - let type: NodeType = load(as: NodeType.self) - assert(type == .leaf, "node is not a leaf") - return NodeLeaf(ptr: self) - } - - func type() -> NodeType { - load(as: NodeType.self) - } -} diff --git a/Sources/ARTreeModule/NodeHeader.swift b/Sources/ARTreeModule/NodeHeader.swift index e8bc4d749..6e0197ea5 100644 --- a/Sources/ARTreeModule/NodeHeader.swift +++ b/Sources/ARTreeModule/NodeHeader.swift @@ -11,13 +11,8 @@ typealias PartialBytes = FixedSizedArray8 -struct NodeHeader { - var type: NodeType - var count: UInt16 = 0 // TODO: Make it smaller. Node256 issue. +struct InternalNodeHeader { + var count: UInt16 = 0 var partialLength: UInt8 = 0 var partialBytes: PartialBytes = PartialBytes(val: 0) - - init(_ type: NodeType) { - self.type = type - } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 40d26e251..fd8782534 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -10,10 +10,13 @@ //===----------------------------------------------------------------------===// struct NodeLeaf { - let pointer: NodePtr + typealias Storage = NodeStorage + var storage: Storage +} - init(ptr: UnsafeMutableRawPointer) { - self.pointer = ptr +extension NodeLeaf { + init(ptr: RawNodeBuffer) { + self.init(storage: Storage(ptr)) } } @@ -32,12 +35,14 @@ extension NodeLeaf { } private var header: UnsafeMutablePointer
{ - pointer.assumingMemoryBound(to: Header.self) + let pointer = storage.getPointer() + return pointer.assumingMemoryBound(to: Header.self) } var keyPtr: UnsafeMutableBufferPointer { - UnsafeMutableBufferPointer( - start: (self.pointer + MemoryLayout
.stride) + let pointer = storage.getPointer() + return UnsafeMutableBufferPointer( + start: (pointer + MemoryLayout
.stride) .assumingMemoryBound(to: KeyPart.self), count: Int(keyLength)) } @@ -51,7 +56,7 @@ extension NodeLeaf { extension NodeLeaf { static func allocate(key: Key, value: Value) -> Self { let size = MemoryLayout
.stride + key.count + MemoryLayout.stride - let buf = NodeBuffer.allocate(byteCount: size, alignment: Const.defaultAlignment) + let buf = NodeStorage.create(type: .leaf, size: size) var leaf = Self(ptr: buf) leaf.keyLength = UInt32(key.count) @@ -88,40 +93,6 @@ extension NodeLeaf { } } -private let _failMsg = "NodeLeaf: should never be called" - extension NodeLeaf: Node { - func type() -> NodeType { .leaf } - - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> NodePtr? { - fatalError(_failMsg) - } - - func index() -> Index? { - fatalError(_failMsg) - } - - func index(forKey k: KeyPart) -> Index? { - fatalError(_failMsg) - } - - func next(index: Index) -> Index? { - fatalError(_failMsg) - } - - func child(at index: Index) -> NodePtr? { - fatalError(_failMsg) - } - - func child(at index: Index, ref: inout ChildSlotPtr?) -> NodePtr? { - fatalError(_failMsg) - } - - func addChild(forKey k: KeyPart, node: NodePtr, ref: ChildSlotPtr?) { - fatalError(_failMsg) - } - - func deleteChild(at index: Index, ref: ChildSlotPtr?) { - fatalError(_failMsg) - } + var type: NodeType { .leaf } } diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 2812744d7..d98ecbc20 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -22,8 +22,9 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [11, 21, 31]) XCTAssertEqual( t.description, - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" - + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") } func testDeleteAll() throws { @@ -45,8 +46,9 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [1, 2, 4]) XCTAssertEqual( t.description, - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 3[1, 2, 3] -> [1]\n" - + "└──○ 4: 3[4, 5, 6] -> [2]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 1: 3[1, 2, 3] -> [1]\n" + + "└──○ 4: 3[4, 5, 6] -> [2]") t.delete(key: [1, 2, 3]) XCTAssertEqual(t.description, "○ 3[4, 5, 6] -> [2]") t.delete(key: [4, 5, 6]) @@ -63,11 +65,13 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [1, 2, 3, 4, 5, 6]) XCTAssertEqual( t.description, - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" - + "│ ├──○ 3: Node4 {childs=2, partial=[4]}\n" - + "│ │ ├──○ 8: 6[1, 2, 3, 4, 8, 9] -> [4]\n" - + "│ │ └──○ 9: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" - + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + "│ ├──○ 3: Node4 {childs=2, partial=[4]}\n" + + "│ │ ├──○ 8: 6[1, 2, 3, 4, 8, 9] -> [4]\n" + + "│ │ └──○ 9: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") } func testDeleteNonExistWithCommonPrefix() throws { @@ -77,8 +81,9 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [1, 2, 3]) XCTAssertEqual( t.description, - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]\n" - + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") } func testDeleteCompressToLeaf() throws { @@ -92,9 +97,11 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [1, 2, 3, 4, 8, 9]) XCTAssertEqual( t.description, - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" - + "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" - + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") } func testDeleteCompressToNode4() throws { @@ -106,15 +113,20 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [5, 6, 7, 8, 9], value: [5]) XCTAssertEqual( t.description, - "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" - + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + "├──○ 3: 5[3, 4, 5, 6, 7] -> [3]\n" - + "├──○ 4: 5[4, 5, 6, 7, 8] -> [4]\n" + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") + "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> [3]\n" + + "├──○ 4: 5[4, 5, 6, 7, 8] -> [4]\n" + + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") t.delete(key: [3, 4, 5, 6, 7]) t.delete(key: [4, 5, 6, 7, 8]) XCTAssertEqual( t.description, - "○ Node4 {childs=3, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" - + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + + "├──○ 2: 5[2, 3, 4, 5, 5] -> [2]\n" + + "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") } func testDeleteCompressToNode16() throws { @@ -122,28 +134,45 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...16 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t.root?.type(), .node48) + XCTAssertEqual(t.root?.type, .node48) t.delete(key: [3, 4]) t.delete(key: [4, 5]) XCTAssertEqual( t.description, - "○ Node48 {childs=15, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" - + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 5: 2[5, 6] -> [5]\n" + "├──○ 6: 2[6, 7] -> [6]\n" - + "├──○ 7: 2[7, 8] -> [7]\n" + "├──○ 8: 2[8, 9] -> [8]\n" + "├──○ 9: 2[9, 10] -> [9]\n" - + "├──○ 10: 2[10, 11] -> [10]\n" + "├──○ 11: 2[11, 12] -> [11]\n" - + "├──○ 12: 2[12, 13] -> [12]\n" + "├──○ 13: 2[13, 14] -> [13]\n" - + "├──○ 14: 2[14, 15] -> [14]\n" + "├──○ 15: 2[15, 16] -> [15]\n" - + "└──○ 16: 2[16, 17] -> [16]") + "○ Node48 {childs=15, partial=[]}\n" + + "├──○ 0: 2[0, 1] -> [0]\n" + + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + + "├──○ 5: 2[5, 6] -> [5]\n" + + "├──○ 6: 2[6, 7] -> [6]\n" + + "├──○ 7: 2[7, 8] -> [7]\n" + + "├──○ 8: 2[8, 9] -> [8]\n" + + "├──○ 9: 2[9, 10] -> [9]\n" + + "├──○ 10: 2[10, 11] -> [10]\n" + + "├──○ 11: 2[11, 12] -> [11]\n" + + "├──○ 12: 2[12, 13] -> [12]\n" + + "├──○ 13: 2[13, 14] -> [13]\n" + + "├──○ 14: 2[14, 15] -> [14]\n" + + "├──○ 15: 2[15, 16] -> [15]\n" + + "└──○ 16: 2[16, 17] -> [16]") t.delete(key: [5, 6]) t.delete(key: [6, 7]) XCTAssertEqual( t.description, - "○ Node16 {childs=13, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" - + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 7: 2[7, 8] -> [7]\n" + "├──○ 8: 2[8, 9] -> [8]\n" - + "├──○ 9: 2[9, 10] -> [9]\n" + "├──○ 10: 2[10, 11] -> [10]\n" - + "├──○ 11: 2[11, 12] -> [11]\n" + "├──○ 12: 2[12, 13] -> [12]\n" - + "├──○ 13: 2[13, 14] -> [13]\n" + "├──○ 14: 2[14, 15] -> [14]\n" - + "├──○ 15: 2[15, 16] -> [15]\n" + "└──○ 16: 2[16, 17] -> [16]") + "○ Node16 {childs=13, partial=[]}\n" + + "├──○ 0: 2[0, 1] -> [0]\n" + + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + + "├──○ 7: 2[7, 8] -> [7]\n" + + "├──○ 8: 2[8, 9] -> [8]\n" + + "├──○ 9: 2[9, 10] -> [9]\n" + + "├──○ 10: 2[10, 11] -> [10]\n" + + "├──○ 11: 2[11, 12] -> [11]\n" + + "├──○ 12: 2[12, 13] -> [12]\n" + + "├──○ 13: 2[13, 14] -> [13]\n" + + "├──○ 14: 2[14, 15] -> [14]\n" + + "├──○ 15: 2[15, 16] -> [15]\n" + + "└──○ 16: 2[16, 17] -> [16]") } func testDeleteCompressToNode48() throws { @@ -151,7 +180,7 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...48 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t.root?.type(), .node256) + XCTAssertEqual(t.root?.type, .node256) for i: UInt8 in 24...40 { if i % 2 == 0 { t.delete(key: [i, i + 1]) @@ -159,24 +188,46 @@ final class ARTreeDeleteTests: XCTestCase { } XCTAssertEqual( t.description, - "○ Node48 {childs=40, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + "├──○ 1: 2[1, 2] -> [1]\n" - + "├──○ 2: 2[2, 3] -> [2]\n" + "├──○ 3: 2[3, 4] -> [3]\n" + "├──○ 4: 2[4, 5] -> [4]\n" - + "├──○ 5: 2[5, 6] -> [5]\n" + "├──○ 6: 2[6, 7] -> [6]\n" + "├──○ 7: 2[7, 8] -> [7]\n" - + "├──○ 8: 2[8, 9] -> [8]\n" + "├──○ 9: 2[9, 10] -> [9]\n" + "├──○ 10: 2[10, 11] -> [10]\n" - + "├──○ 11: 2[11, 12] -> [11]\n" + "├──○ 12: 2[12, 13] -> [12]\n" - + "├──○ 13: 2[13, 14] -> [13]\n" + "├──○ 14: 2[14, 15] -> [14]\n" - + "├──○ 15: 2[15, 16] -> [15]\n" + "├──○ 16: 2[16, 17] -> [16]\n" - + "├──○ 17: 2[17, 18] -> [17]\n" + "├──○ 18: 2[18, 19] -> [18]\n" - + "├──○ 19: 2[19, 20] -> [19]\n" + "├──○ 20: 2[20, 21] -> [20]\n" - + "├──○ 21: 2[21, 22] -> [21]\n" + "├──○ 22: 2[22, 23] -> [22]\n" - + "├──○ 23: 2[23, 24] -> [23]\n" + "├──○ 25: 2[25, 26] -> [25]\n" - + "├──○ 27: 2[27, 28] -> [27]\n" + "├──○ 29: 2[29, 30] -> [29]\n" - + "├──○ 31: 2[31, 32] -> [31]\n" + "├──○ 33: 2[33, 34] -> [33]\n" - + "├──○ 35: 2[35, 36] -> [35]\n" + "├──○ 37: 2[37, 38] -> [37]\n" - + "├──○ 39: 2[39, 40] -> [39]\n" + "├──○ 41: 2[41, 42] -> [41]\n" - + "├──○ 42: 2[42, 43] -> [42]\n" + "├──○ 43: 2[43, 44] -> [43]\n" - + "├──○ 44: 2[44, 45] -> [44]\n" + "├──○ 45: 2[45, 46] -> [45]\n" - + "├──○ 46: 2[46, 47] -> [46]\n" + "├──○ 47: 2[47, 48] -> [47]\n" - + "└──○ 48: 2[48, 49] -> [48]") + "○ Node48 {childs=40, partial=[]}\n" + + "├──○ 0: 2[0, 1] -> [0]\n" + + "├──○ 1: 2[1, 2] -> [1]\n" + + "├──○ 2: 2[2, 3] -> [2]\n" + + "├──○ 3: 2[3, 4] -> [3]\n" + + "├──○ 4: 2[4, 5] -> [4]\n" + + "├──○ 5: 2[5, 6] -> [5]\n" + + "├──○ 6: 2[6, 7] -> [6]\n" + + "├──○ 7: 2[7, 8] -> [7]\n" + + "├──○ 8: 2[8, 9] -> [8]\n" + + "├──○ 9: 2[9, 10] -> [9]\n" + + "├──○ 10: 2[10, 11] -> [10]\n" + + "├──○ 11: 2[11, 12] -> [11]\n" + + "├──○ 12: 2[12, 13] -> [12]\n" + + "├──○ 13: 2[13, 14] -> [13]\n" + + "├──○ 14: 2[14, 15] -> [14]\n" + + "├──○ 15: 2[15, 16] -> [15]\n" + + "├──○ 16: 2[16, 17] -> [16]\n" + + "├──○ 17: 2[17, 18] -> [17]\n" + + "├──○ 18: 2[18, 19] -> [18]\n" + + "├──○ 19: 2[19, 20] -> [19]\n" + + "├──○ 20: 2[20, 21] -> [20]\n" + + "├──○ 21: 2[21, 22] -> [21]\n" + + "├──○ 22: 2[22, 23] -> [22]\n" + + "├──○ 23: 2[23, 24] -> [23]\n" + + "├──○ 25: 2[25, 26] -> [25]\n" + + "├──○ 27: 2[27, 28] -> [27]\n" + + "├──○ 29: 2[29, 30] -> [29]\n" + + "├──○ 31: 2[31, 32] -> [31]\n" + + "├──○ 33: 2[33, 34] -> [33]\n" + + "├──○ 35: 2[35, 36] -> [35]\n" + + "├──○ 37: 2[37, 38] -> [37]\n" + + "├──○ 39: 2[39, 40] -> [39]\n" + + "├──○ 41: 2[41, 42] -> [41]\n" + + "├──○ 42: 2[42, 43] -> [42]\n" + + "├──○ 43: 2[43, 44] -> [43]\n" + + "├──○ 44: 2[44, 45] -> [44]\n" + + "├──○ 45: 2[45, 46] -> [45]\n" + + "├──○ 46: 2[46, 47] -> [46]\n" + + "├──○ 47: 2[47, 48] -> [47]\n" + + "└──○ 48: 2[48, 49] -> [48]") } } diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index cdcbb4243..a8c117da6 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -60,15 +60,15 @@ final class ARTreeInsertTests: XCTestCase { for ii: UInt8 in 0..<40 { t.insert(key: [ii + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t.root?.type(), .node4) + XCTAssertEqual(t.root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t.root?.type(), .node16) + XCTAssertEqual(t.root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t.root?.type(), .node48) + XCTAssertEqual(t.root?.type, .node48) } } - let root = t.root!.asNode(of: [UInt].self)! + let root = t.root! as! (any InternalNode) XCTAssertEqual(root.count, 40) XCTAssertEqual( t.description, @@ -93,15 +93,15 @@ final class ARTreeInsertTests: XCTestCase { for ii: UInt8 in 0..<70 { t.insert(key: [ii + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t.root?.type(), .node4) + XCTAssertEqual(t.root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t.root?.type(), .node16) + XCTAssertEqual(t.root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t.root?.type(), .node48) + XCTAssertEqual(t.root?.type, .node48) } } - let root = t.root!.asNode(of: [UInt8].self)! + let root = t.root! as! (any InternalNode) XCTAssertEqual(root.count, 70) XCTAssertEqual( t.description, @@ -154,7 +154,7 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node16) + XCTAssertEqual(tree.root?.type, .node16) testCase.shuffle() for (k, v) in testCase { @@ -208,7 +208,7 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node48) + XCTAssertEqual(tree.root?.type, .node48) testCase.shuffle() for (k, v) in testCase { @@ -237,7 +237,7 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node4) + XCTAssertEqual(tree.root?.type, .node4) testCase.shuffle() for (k, v) in testCase { @@ -263,7 +263,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node4) + XCTAssertEqual(tree.root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -283,7 +283,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node4) + XCTAssertEqual(tree.root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -305,7 +305,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node4) + XCTAssertEqual(tree.root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -326,7 +326,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type(), .node4) + XCTAssertEqual(tree.root?.type, .node4) testCase.reverse() for (k, v) in testCase { diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 448568bf3..af273ced4 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -20,7 +20,9 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [11]\n" + "└──○ 20: 1[20] -> [22]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [11]\n" + + "└──○ 20: 1[20] -> [22]") } func test4AddInMiddle() throws { @@ -49,10 +51,10 @@ final class ARTreeNode4Tests: XCTestCase { node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - var ptr: NodePtr? = node.pointer + var ptr: (any Node)? = node node.deleteChild(at: 1, ref: &ptr) XCTAssertNotNil(ptr) - XCTAssertEqual(ptr?.type(), .leaf) + XCTAssertEqual(ptr?.type, .leaf) } func test4ExapandTo16() throws { @@ -67,12 +69,12 @@ final class ARTreeNode4Tests: XCTestCase { "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - var addr = Optional(node.pointer) + var addr: (any Node)? = node withUnsafeMutablePointer(to: &addr) { let ref: ChildSlotPtr? = $0 node.addChild(forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5]), ref: ref) XCTAssertEqual( - ref!.pointee!.asNode(of: [UInt8].self)!.print(value: [UInt8].self), + ref!.pointee!.print(value: [UInt8].self), "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") } diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index 05c9e2e6e..f32ea878f 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -15,10 +15,10 @@ import XCTest final class ARTreeNodeBasicTests: XCTestCase { func testNodeSizes() throws { - let header = MemoryLayout.stride + let header = MemoryLayout.stride XCTAssertEqual(header, 14) - let childSlotSize = MemoryLayout.stride + let childSlotSize = MemoryLayout<(any Node)?>.stride let ptrSize = MemoryLayout.stride let size4 = Node4.size let size16 = Node16.size diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index 725c97dc5..a7590a329 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -34,8 +34,7 @@ final class ARTreeNodeLeafTests: XCTestCase { } func testCasts() throws { - let ptr = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]).pointer - let leaf: NodeLeaf<[UInt8]> = ptr.asLeaf() + let leaf = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) XCTAssertEqual(leaf.key, [10, 20, 30, 40]) XCTAssertEqual(leaf.value, [0]) } From 5346d299f2c0adaf70c73a496e174c1250f6075a Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 17 Aug 2023 20:10:22 -0700 Subject: [PATCH 04/67] Fix test formatting --- Tests/ARTreeModuleTests/InsertTests.swift | 188 +++++++++++++++------ Tests/ARTreeModuleTests/Node16Tests.swift | 37 ++-- Tests/ARTreeModuleTests/Node256Tests.swift | 17 +- Tests/ARTreeModuleTests/Node48Tests.swift | 17 +- 4 files changed, 189 insertions(+), 70 deletions(-) diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index a8c117da6..7d94a4ba0 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -21,8 +21,10 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [12, 22, 32], value: [13, 23, 33]) XCTAssertEqual( t.description, - "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" - + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") } func testInsertSharedPrefix() throws { @@ -33,9 +35,12 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [10, 20, 32], value: [1]) XCTAssertEqual( t.description, - "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: Node4 {childs=2, partial=[20]}\n" - + "│ ├──○ 30: 3[10, 20, 30] -> [11, 21, 31]\n" + "│ └──○ 32: 3[10, 20, 32] -> [1]\n" - + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 10: Node4 {childs=2, partial=[20]}\n" + + "│ ├──○ 30: 3[10, 20, 30] -> [11, 21, 31]\n" + + "│ └──○ 32: 3[10, 20, 32] -> [1]\n" + + "├──○ 11: 3[11, 21, 31] -> [12, 22, 32]\n" + + "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") } func testInsertExpandTo16() throws { @@ -46,19 +51,28 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [4], value: [4]) XCTAssertEqual( t.description, - "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "└──○ 4: 1[4] -> [4]") t.insert(key: [5], value: [5]) XCTAssertEqual( t.description, - "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") + "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "├──○ 4: 1[4] -> [4]\n" + + "└──○ 5: 1[5] -> [5]") } func testInsertExpandTo48() throws { var t = ARTree<[UInt8]>() for ii: UInt8 in 0..<40 { - t.insert(key: [ii + 1], value: [ii + 1]) + t.insert(key: [ii + + 1], value: [ii + + 1]) if ii < 4 { XCTAssertEqual(t.root?.type, .node4) } else if ii < 16 { @@ -72,26 +86,55 @@ final class ARTreeInsertTests: XCTestCase { XCTAssertEqual(root.count, 40) XCTAssertEqual( t.description, - "○ Node48 {childs=40, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "├──○ 5: 1[5] -> [5]\n" - + "├──○ 6: 1[6] -> [6]\n" + "├──○ 7: 1[7] -> [7]\n" + "├──○ 8: 1[8] -> [8]\n" - + "├──○ 9: 1[9] -> [9]\n" + "├──○ 10: 1[10] -> [10]\n" + "├──○ 11: 1[11] -> [11]\n" - + "├──○ 12: 1[12] -> [12]\n" + "├──○ 13: 1[13] -> [13]\n" + "├──○ 14: 1[14] -> [14]\n" - + "├──○ 15: 1[15] -> [15]\n" + "├──○ 16: 1[16] -> [16]\n" + "├──○ 17: 1[17] -> [17]\n" - + "├──○ 18: 1[18] -> [18]\n" + "├──○ 19: 1[19] -> [19]\n" + "├──○ 20: 1[20] -> [20]\n" - + "├──○ 21: 1[21] -> [21]\n" + "├──○ 22: 1[22] -> [22]\n" + "├──○ 23: 1[23] -> [23]\n" - + "├──○ 24: 1[24] -> [24]\n" + "├──○ 25: 1[25] -> [25]\n" + "├──○ 26: 1[26] -> [26]\n" - + "├──○ 27: 1[27] -> [27]\n" + "├──○ 28: 1[28] -> [28]\n" + "├──○ 29: 1[29] -> [29]\n" - + "├──○ 30: 1[30] -> [30]\n" + "├──○ 31: 1[31] -> [31]\n" + "├──○ 32: 1[32] -> [32]\n" - + "├──○ 33: 1[33] -> [33]\n" + "├──○ 34: 1[34] -> [34]\n" + "├──○ 35: 1[35] -> [35]\n" - + "├──○ 36: 1[36] -> [36]\n" + "├──○ 37: 1[37] -> [37]\n" + "├──○ 38: 1[38] -> [38]\n" - + "├──○ 39: 1[39] -> [39]\n" + "└──○ 40: 1[40] -> [40]") + "○ Node48 {childs=40, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "├──○ 4: 1[4] -> [4]\n" + + "├──○ 5: 1[5] -> [5]\n" + + "├──○ 6: 1[6] -> [6]\n" + + "├──○ 7: 1[7] -> [7]\n" + + "├──○ 8: 1[8] -> [8]\n" + + "├──○ 9: 1[9] -> [9]\n" + + "├──○ 10: 1[10] -> [10]\n" + + "├──○ 11: 1[11] -> [11]\n" + + "├──○ 12: 1[12] -> [12]\n" + + "├──○ 13: 1[13] -> [13]\n" + + "├──○ 14: 1[14] -> [14]\n" + + "├──○ 15: 1[15] -> [15]\n" + + "├──○ 16: 1[16] -> [16]\n" + + "├──○ 17: 1[17] -> [17]\n" + + "├──○ 18: 1[18] -> [18]\n" + + "├──○ 19: 1[19] -> [19]\n" + + "├──○ 20: 1[20] -> [20]\n" + + "├──○ 21: 1[21] -> [21]\n" + + "├──○ 22: 1[22] -> [22]\n" + + "├──○ 23: 1[23] -> [23]\n" + + "├──○ 24: 1[24] -> [24]\n" + + "├──○ 25: 1[25] -> [25]\n" + + "├──○ 26: 1[26] -> [26]\n" + + "├──○ 27: 1[27] -> [27]\n" + + "├──○ 28: 1[28] -> [28]\n" + + "├──○ 29: 1[29] -> [29]\n" + + "├──○ 30: 1[30] -> [30]\n" + + "├──○ 31: 1[31] -> [31]\n" + + "├──○ 32: 1[32] -> [32]\n" + + "├──○ 33: 1[33] -> [33]\n" + + "├──○ 34: 1[34] -> [34]\n" + + "├──○ 35: 1[35] -> [35]\n" + + "├──○ 36: 1[36] -> [36]\n" + + "├──○ 37: 1[37] -> [37]\n" + + "├──○ 38: 1[38] -> [38]\n" + + "├──○ 39: 1[39] -> [39]\n" + + "└──○ 40: 1[40] -> [40]") } func testInsertExpandTo256() throws { var t = ARTree<[UInt8]>() for ii: UInt8 in 0..<70 { - t.insert(key: [ii + 1], value: [ii + 1]) + t.insert(key: [ii + + 1], value: [ii + + 1]) if ii < 4 { XCTAssertEqual(t.root?.type, .node4) } else if ii < 16 { @@ -105,30 +148,77 @@ final class ARTreeInsertTests: XCTestCase { XCTAssertEqual(root.count, 70) XCTAssertEqual( t.description, - "○ Node256 {childs=70, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "├──○ 5: 1[5] -> [5]\n" - + "├──○ 6: 1[6] -> [6]\n" + "├──○ 7: 1[7] -> [7]\n" + "├──○ 8: 1[8] -> [8]\n" - + "├──○ 9: 1[9] -> [9]\n" + "├──○ 10: 1[10] -> [10]\n" + "├──○ 11: 1[11] -> [11]\n" - + "├──○ 12: 1[12] -> [12]\n" + "├──○ 13: 1[13] -> [13]\n" + "├──○ 14: 1[14] -> [14]\n" - + "├──○ 15: 1[15] -> [15]\n" + "├──○ 16: 1[16] -> [16]\n" + "├──○ 17: 1[17] -> [17]\n" - + "├──○ 18: 1[18] -> [18]\n" + "├──○ 19: 1[19] -> [19]\n" + "├──○ 20: 1[20] -> [20]\n" - + "├──○ 21: 1[21] -> [21]\n" + "├──○ 22: 1[22] -> [22]\n" + "├──○ 23: 1[23] -> [23]\n" - + "├──○ 24: 1[24] -> [24]\n" + "├──○ 25: 1[25] -> [25]\n" + "├──○ 26: 1[26] -> [26]\n" - + "├──○ 27: 1[27] -> [27]\n" + "├──○ 28: 1[28] -> [28]\n" + "├──○ 29: 1[29] -> [29]\n" - + "├──○ 30: 1[30] -> [30]\n" + "├──○ 31: 1[31] -> [31]\n" + "├──○ 32: 1[32] -> [32]\n" - + "├──○ 33: 1[33] -> [33]\n" + "├──○ 34: 1[34] -> [34]\n" + "├──○ 35: 1[35] -> [35]\n" - + "├──○ 36: 1[36] -> [36]\n" + "├──○ 37: 1[37] -> [37]\n" + "├──○ 38: 1[38] -> [38]\n" - + "├──○ 39: 1[39] -> [39]\n" + "├──○ 40: 1[40] -> [40]\n" + "├──○ 41: 1[41] -> [41]\n" - + "├──○ 42: 1[42] -> [42]\n" + "├──○ 43: 1[43] -> [43]\n" + "├──○ 44: 1[44] -> [44]\n" - + "├──○ 45: 1[45] -> [45]\n" + "├──○ 46: 1[46] -> [46]\n" + "├──○ 47: 1[47] -> [47]\n" - + "├──○ 48: 1[48] -> [48]\n" + "├──○ 49: 1[49] -> [49]\n" + "├──○ 50: 1[50] -> [50]\n" - + "├──○ 51: 1[51] -> [51]\n" + "├──○ 52: 1[52] -> [52]\n" + "├──○ 53: 1[53] -> [53]\n" - + "├──○ 54: 1[54] -> [54]\n" + "├──○ 55: 1[55] -> [55]\n" + "├──○ 56: 1[56] -> [56]\n" - + "├──○ 57: 1[57] -> [57]\n" + "├──○ 58: 1[58] -> [58]\n" + "├──○ 59: 1[59] -> [59]\n" - + "├──○ 60: 1[60] -> [60]\n" + "├──○ 61: 1[61] -> [61]\n" + "├──○ 62: 1[62] -> [62]\n" - + "├──○ 63: 1[63] -> [63]\n" + "├──○ 64: 1[64] -> [64]\n" + "├──○ 65: 1[65] -> [65]\n" - + "├──○ 66: 1[66] -> [66]\n" + "├──○ 67: 1[67] -> [67]\n" + "├──○ 68: 1[68] -> [68]\n" - + "├──○ 69: 1[69] -> [69]\n" + "└──○ 70: 1[70] -> [70]") + "○ Node256 {childs=70, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "├──○ 4: 1[4] -> [4]\n" + + "├──○ 5: 1[5] -> [5]\n" + + "├──○ 6: 1[6] -> [6]\n" + + "├──○ 7: 1[7] -> [7]\n" + + "├──○ 8: 1[8] -> [8]\n" + + "├──○ 9: 1[9] -> [9]\n" + + "├──○ 10: 1[10] -> [10]\n" + + "├──○ 11: 1[11] -> [11]\n" + + "├──○ 12: 1[12] -> [12]\n" + + "├──○ 13: 1[13] -> [13]\n" + + "├──○ 14: 1[14] -> [14]\n" + + "├──○ 15: 1[15] -> [15]\n" + + "├──○ 16: 1[16] -> [16]\n" + + "├──○ 17: 1[17] -> [17]\n" + + "├──○ 18: 1[18] -> [18]\n" + + "├──○ 19: 1[19] -> [19]\n" + + "├──○ 20: 1[20] -> [20]\n" + + "├──○ 21: 1[21] -> [21]\n" + + "├──○ 22: 1[22] -> [22]\n" + + "├──○ 23: 1[23] -> [23]\n" + + "├──○ 24: 1[24] -> [24]\n" + + "├──○ 25: 1[25] -> [25]\n" + + "├──○ 26: 1[26] -> [26]\n" + + "├──○ 27: 1[27] -> [27]\n" + + "├──○ 28: 1[28] -> [28]\n" + + "├──○ 29: 1[29] -> [29]\n" + + "├──○ 30: 1[30] -> [30]\n" + + "├──○ 31: 1[31] -> [31]\n" + + "├──○ 32: 1[32] -> [32]\n" + + "├──○ 33: 1[33] -> [33]\n" + + "├──○ 34: 1[34] -> [34]\n" + + "├──○ 35: 1[35] -> [35]\n" + + "├──○ 36: 1[36] -> [36]\n" + + "├──○ 37: 1[37] -> [37]\n" + + "├──○ 38: 1[38] -> [38]\n" + + "├──○ 39: 1[39] -> [39]\n" + + "├──○ 40: 1[40] -> [40]\n" + + "├──○ 41: 1[41] -> [41]\n" + + "├──○ 42: 1[42] -> [42]\n" + + "├──○ 43: 1[43] -> [43]\n" + + "├──○ 44: 1[44] -> [44]\n" + + "├──○ 45: 1[45] -> [45]\n" + + "├──○ 46: 1[46] -> [46]\n" + + "├──○ 47: 1[47] -> [47]\n" + + "├──○ 48: 1[48] -> [48]\n" + + "├──○ 49: 1[49] -> [49]\n" + + "├──○ 50: 1[50] -> [50]\n" + + "├──○ 51: 1[51] -> [51]\n" + + "├──○ 52: 1[52] -> [52]\n" + + "├──○ 53: 1[53] -> [53]\n" + + "├──○ 54: 1[54] -> [54]\n" + + "├──○ 55: 1[55] -> [55]\n" + + "├──○ 56: 1[56] -> [56]\n" + + "├──○ 57: 1[57] -> [57]\n" + + "├──○ 58: 1[58] -> [58]\n" + + "├──○ 59: 1[59] -> [59]\n" + + "├──○ 60: 1[60] -> [60]\n" + + "├──○ 61: 1[61] -> [61]\n" + + "├──○ 62: 1[62] -> [62]\n" + + "├──○ 63: 1[63] -> [63]\n" + + "├──○ 64: 1[64] -> [64]\n" + + "├──○ 65: 1[65] -> [65]\n" + + "├──○ 66: 1[66] -> [66]\n" + + "├──○ 67: 1[67] -> [67]\n" + + "├──○ 68: 1[68] -> [68]\n" + + "├──○ 69: 1[69] -> [69]\n" + + "└──○ 70: 1[70] -> [70]") } func testInsertAndGetSmallSetRepeat48() throws { diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index c93d8650f..7fc0b0505 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -20,7 +20,9 @@ final class ARTreeNode16Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [0]\n" + + "└──○ 20: 1[20] -> [3]") } func test4AddInMiddle() throws { @@ -31,8 +33,11 @@ final class ARTreeNode16Tests: XCTestCase { node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" - + "├──○ 20: 1[20] -> [2]\n" + "└──○ 30: 1[30] -> [3]") + "○ Node16 {childs=4, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [4]\n" + + "├──○ 20: 1[20] -> [2]\n" + + "└──○ 30: 1[30] -> [3]") } func test16DeleteAtIndex() throws { @@ -42,16 +47,21 @@ final class ARTreeNode16Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 0) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 1) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") + "○ Node16 {childs=1, partial=[]}\n" + + "└──○ 15: 1[15] -> [2]") node.deleteChild(at: 0) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") } @@ -63,16 +73,21 @@ final class ARTreeNode16Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 10) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 15) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + "○ Node16 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") } diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index eabf3ea53..949c731ba 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -20,7 +20,9 @@ final class ARTreeNode256Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node256 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node256 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [0]\n" + + "└──○ 20: 1[20] -> [3]") } func test48DeleteAtIndex() throws { @@ -30,16 +32,21 @@ final class ARTreeNode256Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node256 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node256 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 10) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node256 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 15) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + "○ Node256 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node256 {childs=0, partial=[]}\n") } diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index 5c0bc754e..519b3da69 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -20,7 +20,9 @@ final class ARTreeNode48Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node48 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "└──○ 20: 1[20] -> [2]") + "○ Node48 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "└──○ 20: 1[20] -> [2]") } func test48DeleteAtIndex() throws { @@ -30,16 +32,21 @@ final class ARTreeNode48Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node48 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node48 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 10) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node48 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 15) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + "○ Node48 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node48 {childs=0, partial=[]}\n") } From dd98a04e954b2f7ec2e95eb12e9b9b30d26fa42f Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 17 Aug 2023 23:49:42 -0700 Subject: [PATCH 05/67] Add FixedArray type using tuples --- Sources/ARTreeModule/FixedArray+Storage.swift | 122 ++++++++++++++++++ Sources/ARTreeModule/FixedArray+impl.swift | 87 +++++++++++++ Sources/ARTreeModule/FixedArray.swift | 43 ++++++ 3 files changed, 252 insertions(+) create mode 100644 Sources/ARTreeModule/FixedArray+Storage.swift create mode 100644 Sources/ARTreeModule/FixedArray+impl.swift create mode 100644 Sources/ARTreeModule/FixedArray.swift diff --git a/Sources/ARTreeModule/FixedArray+Storage.swift b/Sources/ARTreeModule/FixedArray+Storage.swift new file mode 100644 index 000000000..43efb3be5 --- /dev/null +++ b/Sources/ARTreeModule/FixedArray+Storage.swift @@ -0,0 +1,122 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 +// +//===----------------------------------------------------------------------===// + +protocol FixedStorage { + associatedtype Element + + static var capacity: Int { get } + init(repeating: Element) +} + +struct FixedStorage4: FixedStorage { + internal var items: (T, T, T, T) + + @inline(__always) + init(repeating v: T) { + self.items = (v, v, v, v) + } + + static var capacity: Int { + @inline(__always) get { 4 } + } +} + +struct FixedStorage8: FixedStorage { + internal var items: (T, T, T, T, T, T, T, T) + + @inline(__always) + init(repeating v: T) { + self.items = (v, v, v, v, v, v, v, v) + } + + static var capacity: Int { + @inline(__always) get { 8 } + } +} + +struct FixedStorage16: FixedStorage { + internal var items: (T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T) + + @inline(__always) + init(repeating v: T) { + self.items = (v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v) + } + + static var capacity: Int { + @inline(__always) get { 16 } + } +} + +struct FixedStorage48: FixedStorage { + internal var items: (T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) + + @inline(__always) + init(repeating v: T) { + self.items = (v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v) + } + + static var capacity: Int { + @inline(__always) get { 48 } + } +} + +struct FixedStorage256: FixedStorage { + internal var items: ( + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T + ) + + @inline(__always) + init(repeating v: T) { + self.items = ( + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v + ) + } + + static var capacity: Int { + @inline(__always) get { 256 } + } +} diff --git a/Sources/ARTreeModule/FixedArray+impl.swift b/Sources/ARTreeModule/FixedArray+impl.swift new file mode 100644 index 000000000..fc7f07c80 --- /dev/null +++ b/Sources/ARTreeModule/FixedArray+impl.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 FixedArray: RandomAccessCollection, MutableCollection { + typealias Index = Int + + internal var startIndex: Index { + return 0 + } + + internal var endIndex: Index { + return capacity + } + + internal subscript(i: Index) -> Element { + @inline(__always) + get { + let capacity = Self.capacity + assert(i >= 0 && i < capacity) + let res: Element = withUnsafeBytes(of: storage) { + (rawPtr: UnsafeRawBufferPointer) -> Element in + let stride = MemoryLayout.stride + assert(rawPtr.count == capacity*stride, "layout mismatch?") + let bufPtr = UnsafeBufferPointer( + start: rawPtr.baseAddress!.assumingMemoryBound(to: Element.self), + count: capacity) + return bufPtr[i] + } + return res + } + @inline(__always) + set { + assert(i >= 0 && i < count) + self.withUnsafeMutableBufferPointer { buffer in + buffer[i] = newValue + } + } + } + + @inline(__always) + internal func index(after i: Index) -> Index { + return i+1 + } + + @inline(__always) + internal func index(before i: Index) -> Index { + return i-1 + } +} + +extension FixedArray { + internal mutating func withUnsafeMutableBufferPointer( + _ body: (UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R { + let capacity = Self.capacity + return try withUnsafeMutableBytes(of: &storage) { rawBuffer in + assert(rawBuffer.count == capacity*MemoryLayout.stride, + "layout mismatch?") + let buffer = UnsafeMutableBufferPointer( + start: rawBuffer.baseAddress!.assumingMemoryBound(to: Element.self), + count: capacity) + return try body(buffer) + } + } + + internal mutating func withUnsafeBufferPointer( + _ body: (UnsafeBufferPointer) throws -> R + ) rethrows -> R { + let capacity = Self.capacity + return try withUnsafeBytes(of: &storage) { rawBuffer in + assert(rawBuffer.count == capacity*MemoryLayout.stride, + "layout mismatch?") + let buffer = UnsafeBufferPointer( + start: rawBuffer.baseAddress!.assumingMemoryBound(to: Element.self), + count: capacity) + return try body(buffer) + } + } +} diff --git a/Sources/ARTreeModule/FixedArray.swift b/Sources/ARTreeModule/FixedArray.swift new file mode 100644 index 000000000..35bb27f0e --- /dev/null +++ b/Sources/ARTreeModule/FixedArray.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 +// +//===----------------------------------------------------------------------===// +// +// A helper struct to provide fixed-sized array like functionality +// +//===----------------------------------------------------------------------===// + + +typealias FixedArray4 = FixedArray> +typealias FixedArray8 = FixedArray> +typealias FixedArray16 = FixedArray> +typealias FixedArray48 = FixedArray> +typealias FixedArray256 = FixedArray> + +internal struct FixedArray { + typealias Element = Storage.Element + internal var storage: Storage +} + +extension FixedArray { + @inline(__always) + init(repeating: Element) { + self.storage = Storage(repeating: (repeating)) + } +} + +extension FixedArray { + internal static var capacity: Int { + @inline(__always) get { return Storage.capacity } + } + + internal var capacity: Int { + @inline(__always) get { return Self.capacity } + } +} From 3fd67ac980ad31a9ffabc43bfec52778597a2cc9 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 17 Aug 2023 23:58:19 -0700 Subject: [PATCH 06/67] Fixed some test case formatting --- Tests/ARTreeModuleTests/Node4Tests.swift | 49 ++++++++++++++++-------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index af273ced4..b2a377d96 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -21,8 +21,8 @@ final class ARTreeNode4Tests: XCTestCase { XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + - "├──○ 10: 1[10] -> [11]\n" + - "└──○ 20: 1[20] -> [22]") + "├──○ 10: 1[10] -> [11]\n" + + "└──○ 20: 1[20] -> [22]") } func test4AddInMiddle() throws { @@ -33,8 +33,11 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" - + "├──○ 20: 1[20] -> [2]\n" + "└──○ 30: 1[30] -> [3]") + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [4]\n" + + "├──○ 20: 1[20] -> [2]\n" + + "└──○ 30: 1[30] -> [3]") } func test4DeleteAtIndex() throws { @@ -44,12 +47,16 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(at: 0) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") var ptr: (any Node)? = node node.deleteChild(at: 1, ref: &ptr) @@ -66,8 +73,11 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "└──○ 4: 1[4] -> [4]") var addr: (any Node)? = node withUnsafeMutablePointer(to: &addr) { @@ -75,8 +85,12 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5]), ref: ref) XCTAssertEqual( ref!.pointee!.print(value: [UInt8].self), - "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" - + "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") + "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "├──○ 4: 1[4] -> [4]\n" + + "└──○ 5: 1[5] -> [5]") } } @@ -87,16 +101,21 @@ final class ARTreeNode4Tests: XCTestCase { node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" - + "└──○ 20: 1[20] -> [3]") + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 10) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 15) XCTAssertEqual( node.print(value: [UInt8].self), - "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") + "○ Node4 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") node.deleteChild(forKey: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node4 {childs=0, partial=[]}\n") } From 21ebcb6b0f78ee11f5cc7ff7568942b7e760900c Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 00:48:05 -0700 Subject: [PATCH 07/67] Make NodeLeaf follow ManagedBuffer semantics --- Sources/ARTreeModule/ARTree+delete.swift | 4 +- Sources/ARTreeModule/Node+Storage.swift | 13 +--- Sources/ARTreeModule/Node.swift | 3 +- Sources/ARTreeModule/Node16.swift | 23 ++++--- Sources/ARTreeModule/Node4.swift | 22 +++--- Sources/ARTreeModule/NodeLeaf.swift | 86 ++++++++++++++---------- 6 files changed, 83 insertions(+), 68 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index edbf6c65c..c00385dae 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -37,7 +37,9 @@ extension ARTree { } ref?.pointee = nil - leaf.valuePtr.deinitialize(count: 1) + leaf.withKeyValue { _, valuePtr in + valuePtr.deinitialize(count: 1) + } return true } diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index 477ee16fa..2933059e0 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -18,7 +18,7 @@ final class NodeBuffer: RawNodeBuffer { } struct NodeStorage { - typealias Header = InternalNodeHeader + typealias Header = ArtNode.Header var buf: NodeBuffer } @@ -47,7 +47,9 @@ extension NodeStorage where ArtNode: InternalNode { storage.withBodyPointer { $0.initializeMemory(as: UInt8.self, repeating: 0, count: size) } return storage } +} +extension NodeStorage { func withHeaderPointer(_ body: (UnsafeMutablePointer
) throws -> R) rethrows -> R { return try buf.withUnsafeMutablePointerToElements { return try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Header.self)) @@ -60,12 +62,3 @@ extension NodeStorage where ArtNode: InternalNode { } } } - - -extension NodeStorage { - func getPointer() -> UnsafeMutableRawPointer { - return self.buf.withUnsafeMutablePointerToElements { - UnsafeMutableRawPointer($0) - } - } -} diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 664d2c45c..e246f2d6b 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -16,8 +16,9 @@ typealias ChildSlotPtr = UnsafeMutablePointer<(any Node)?> /// Shared protocol implementation for Node types in an Adaptive Radix Tree protocol Node: NodePrettyPrinter { - var storage: NodeStorage { get } + associatedtype Header + var storage: NodeStorage { get } var type: NodeType { get } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index a07246364..ecfdb8de0 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -60,6 +60,7 @@ extension Node16 { from: UnsafeMutableRawBufferPointer(fromChilds)) } } + return node } @@ -129,11 +130,11 @@ extension Node16: InternalNode { func _insertSlot(forKey k: KeyPart) -> Int? { // TODO: Binary search. - return withBody { keys, _ in - if count >= Self.numKeys { - return nil - } + if count >= Self.numKeys { + return nil + } + return withBody { keys, _ in for idx in 0..= Int(k) { return idx @@ -177,20 +178,20 @@ extension Node16: InternalNode { node: any Node, ref: ChildSlotPtr? ) { - withBody {keys, childs in - if let slot = _insertSlot(forKey: k) { + if let slot = _insertSlot(forKey: k) { + withBody {keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k childs[slot] = node count += 1 - } else { - var newNode = Node48.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode - // pointer.deallocate() } + } else { + var newNode = Node48.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode + // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 195917857..dd2195f73 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -102,11 +102,11 @@ extension Node4: InternalNode { } func _insertSlot(forKey k: KeyPart) -> Int? { - return withBody { keys, _ in - if count >= Self.numKeys { - return nil - } + if count >= Self.numKeys { + return nil + } + return withBody { keys, _ in for idx in 0..= Int(k) { return idx @@ -150,20 +150,20 @@ extension Node4: InternalNode { node: any Node, ref: ChildSlotPtr? ) { - withBody { keys, childs in - if let slot = _insertSlot(forKey: k) { + if let slot = _insertSlot(forKey: k) { + withBody { keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k childs[slot] = node count += 1 - } else { - var newNode = Node16.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode - // pointer.deallocate() } + } else { + var newNode = Node16.allocate(copyFrom: self) + newNode.addChild(forKey: k, node: node) + ref?.pointee = newNode + // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index fd8782534..84799a7a1 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -11,45 +11,54 @@ struct NodeLeaf { typealias Storage = NodeStorage + var storage: Storage } extension NodeLeaf { init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + self.init(storage: Storage(ptr)) } } extension NodeLeaf { - var key: Key { Array(keyPtr) } - var value: Value { valuePtr.pointee } - - var keyLength: UInt32 { - get { header.pointee.keyLength } - set { header.pointee.keyLength = newValue } - } - - private struct Header { + struct Header { var type: NodeType var keyLength: UInt32 } - private var header: UnsafeMutablePointer
{ - let pointer = storage.getPointer() - return pointer.assumingMemoryBound(to: Header.self) + typealias KeyPtr = UnsafeMutableBufferPointer + typealias ValuePtr = UnsafeMutablePointer + + func withKeyValue(body: (KeyPtr, ValuePtr) throws -> R) rethrows -> R { + return try storage.withBodyPointer { + let keyPtr = UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: KeyPart.self), + count: Int(keyLength) + ) + let valuePtr = UnsafeMutableRawPointer( + keyPtr.baseAddress?.advanced(by: Int(keyLength)))! + .assumingMemoryBound(to: Value.self) + return try body(keyPtr, valuePtr) + } } - var keyPtr: UnsafeMutableBufferPointer { - let pointer = storage.getPointer() - return UnsafeMutableBufferPointer( - start: (pointer + MemoryLayout
.stride) - .assumingMemoryBound(to: KeyPart.self), - count: Int(keyLength)) + var key: Key { + withKeyValue { k, _ in Array(k) } } - var valuePtr: UnsafeMutablePointer { - let ptr = UnsafeMutableRawPointer(self.keyPtr.baseAddress?.advanced(by: Int(keyLength)))! - return ptr.assumingMemoryBound(to: Value.self) + var value: Value { + get { withKeyValue { $1.pointee } } + set { withKeyValue { $1.pointee = newValue } } + } + + var keyLength: UInt32 { + get { + storage.withHeaderPointer { $0.pointee.keyLength } + } + set { + storage.withHeaderPointer { $0.pointee.keyLength = newValue } + } } } @@ -60,10 +69,12 @@ extension NodeLeaf { var leaf = Self(ptr: buf) leaf.keyLength = UInt32(key.count) - key.withUnsafeBytes { - UnsafeMutableRawBufferPointer(leaf.keyPtr).copyBytes(from: $0) + leaf.withKeyValue { keyPtr, valuePtr in + key.withUnsafeBytes { + UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) + } + valuePtr.pointee = value } - leaf.valuePtr.pointee = value return leaf } @@ -73,23 +84,30 @@ extension NodeLeaf { return false } - for ii in depth.. Int { let maxComp = Int(min(keyLength, other.keyLength) - UInt32(fromIndex)) - for index in 0.. Date: Fri, 18 Aug 2023 00:58:48 -0700 Subject: [PATCH 08/67] Remove Header pointer --- Sources/ARTreeModule/Node+basics.swift | 43 ++++++++++++++++++++------ Sources/ARTreeModule/Node.swift | 2 -- Sources/ARTreeModule/Node16.swift | 4 --- Sources/ARTreeModule/Node256.swift | 4 --- Sources/ARTreeModule/Node4.swift | 4 --- Sources/ARTreeModule/Node48.swift | 4 --- 6 files changed, 33 insertions(+), 28 deletions(-) diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/Node+basics.swift index a84e50b8e..8244fd713 100644 --- a/Sources/ARTreeModule/Node+basics.swift +++ b/Sources/ARTreeModule/Node+basics.swift @@ -11,21 +11,43 @@ extension InternalNode { var partialLength: Int { - get { Int(self.header.pointee.partialLength) } + get { + storage.withHeaderPointer { + Int($0.pointee.partialLength) + } + } set { assert(newValue <= Const.maxPartialLength) - self.header.pointee.partialLength = KeyPart(newValue) + storage.withHeaderPointer { + $0.pointee.partialLength = KeyPart(newValue) + } } } var partialBytes: PartialBytes { - get { header.pointee.partialBytes } - set { header.pointee.partialBytes = newValue } + get { + storage.withHeaderPointer { + $0.pointee.partialBytes + } + } + set { + storage.withHeaderPointer { + $0.pointee.partialBytes = newValue + } + } } var count: Int { - get { Int(header.pointee.count) } - set { header.pointee.count = UInt16(newValue) } + get { + storage.withHeaderPointer { + Int($0.pointee.count) + } + } + set { + storage.withHeaderPointer { + $0.pointee.count = UInt16(newValue) + } + } } func child(forKey k: KeyPart) -> (any Node)? { @@ -65,10 +87,11 @@ extension InternalNode { } mutating func copyHeader(from: any InternalNode) { - let src = from.header.pointee - header.pointee.count = src.count - header.pointee.partialLength = src.partialLength - header.pointee.partialBytes = src.partialBytes + self.storage.withHeaderPointer { header in + header.pointee.count = UInt16(from.count) + header.pointee.partialLength = UInt8(from.partialLength) + header.pointee.partialBytes = from.partialBytes + } } // Calculates the index at which prefix mismatches. diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index e246f2d6b..3be852e61 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -29,8 +29,6 @@ protocol InternalNode: Node { static var type: NodeType { get } static var size: Int { get } - var header: UnsafeMutablePointer { get } - var count: Int { get set } var partialLength: Int { get } var partialBytes: PartialBytes { get set } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index ecfdb8de0..3e07f31a9 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -103,10 +103,6 @@ extension Node16: InternalNode { * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) } - var header: UnsafeMutablePointer { - storage.withHeaderPointer { $0 } - } - func index(forKey k: KeyPart) -> Index? { return withBody { keys, _ in for (index, key) in keys.enumerated() { diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index d529a872c..2c0ce8932 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -76,10 +76,6 @@ extension Node256: InternalNode { MemoryLayout.stride + 256 * MemoryLayout<(any Node)?>.stride } - var header: UnsafeMutablePointer { - storage.withHeaderPointer { $0 } - } - func index(forKey k: KeyPart) -> Index? { return withBody { childs in return childs[Int(k)] != nil ? Int(k) : nil diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index dd2195f73..1e02933ba 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -76,10 +76,6 @@ extension Node4: InternalNode { * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) } - var header: UnsafeMutablePointer { - storage.withHeaderPointer { $0 } - } - func index(forKey k: KeyPart) -> Index? { return withBody { keys, _ in for (index, key) in keys.enumerated() { diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 99a0e89ae..9b11403c7 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -115,10 +115,6 @@ extension Node48: InternalNode { Self.numKeys*MemoryLayout<(any Node)?>.stride } - var header: UnsafeMutablePointer { - storage.withHeaderPointer { $0 } - } - func index(forKey k: KeyPart) -> Index? { return withBody { keys, _ in let childIndex = Int(keys[Int(k)]) From 8d3a75d7a950a296ca58da7a637465e23cf7e05e Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 09:09:14 -0700 Subject: [PATCH 09/67] Initialize and bindMemory properly --- Sources/ARTreeModule/Node+Storage.swift | 5 ++++- Sources/ARTreeModule/Node16.swift | 9 ++++++++- Sources/ARTreeModule/Node256.swift | 7 ++++++- Sources/ARTreeModule/Node4.swift | 9 ++++++++- Sources/ARTreeModule/Node48.swift | 12 +++++++++--- Sources/ARTreeModule/NodeLeaf.swift | 1 - 6 files changed, 35 insertions(+), 8 deletions(-) diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index 2933059e0..bf3b057cf 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -44,7 +44,10 @@ extension NodeStorage where ArtNode: InternalNode { let size = ArtNode.size let buf = NodeStorage.create(type: ArtNode.type, size: size) let storage = NodeStorage(buf) - storage.withBodyPointer { $0.initializeMemory(as: UInt8.self, repeating: 0, count: size) } + buf.withUnsafeMutablePointerToElements { + $0.initialize(repeating: 0, count: size) + UnsafeMutableRawPointer($0).bindMemory(to: Header.self, capacity: 1) + } return storage } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 3e07f31a9..f4890bf79 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -46,7 +46,14 @@ extension Node16 { extension Node16 { static func allocate() -> Self { let buf: NodeStorage = NodeStorage.allocate() - return Self(storage: buf) + let node = Self(storage: buf) + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + } + return node } static func allocate(copyFrom: Node4) -> Self { diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 2c0ce8932..d5dc7be79 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -40,7 +40,12 @@ extension Node256 { extension Node256 { static func allocate() -> Node256 { let buf: NodeStorage = NodeStorage.allocate() - return Node256(storage: buf) + let node = Self(storage: buf) + node.withBody { childs in + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + } + return node } static func allocate(copyFrom: Node48) -> Self { diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 1e02933ba..0f7f11724 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -51,7 +51,14 @@ extension Node4 { extension Node4 { static func allocate() -> Self { let buf: NodeStorage = NodeStorage.allocate() - return Self(storage: buf) + let node = Self(storage: buf) + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + } + return node } static func allocate(copyFrom: Node16) -> Self { diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 9b11403c7..f8c54bfce 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -51,14 +51,20 @@ extension Node48 { extension Node48 { static func allocate() -> Self { let buf: NodeStorage = NodeStorage.allocate() - let storage = Self(storage: buf) + let node = Self(storage: buf) + + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) - storage.withBody { keys, _ in for idx in 0..<256 { keys[idx] = 0xFF } } - return storage + + return node } static func allocate(copyFrom: Node16) -> Self { diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 84799a7a1..14fa30512 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -23,7 +23,6 @@ extension NodeLeaf { extension NodeLeaf { struct Header { - var type: NodeType var keyLength: UInt32 } From f52d6854e6340030d50e2522e6c121e066e55d7d Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 09:19:11 -0700 Subject: [PATCH 10/67] Update size test --- Tests/ARTreeModuleTests/NodeBasicTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index f32ea878f..e7e25a2a1 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -16,15 +16,17 @@ import XCTest final class ARTreeNodeBasicTests: XCTestCase { func testNodeSizes() throws { let header = MemoryLayout.stride - XCTAssertEqual(header, 14) + XCTAssertEqual(header, 12) let childSlotSize = MemoryLayout<(any Node)?>.stride let ptrSize = MemoryLayout.stride + let refSize = MemoryLayout<(any Node)?>.stride let size4 = Node4.size let size16 = Node16.size let size48 = Node48.size let size256 = Node256.size + print("sizeof((any Node)?) = \(refSize)") print("sizeOf(Int) = \(ptrSize)") print("sizeOf(childSlot) = \(childSlotSize)") print("sizeOf(Header) = \(header)") From 0ed8b2b2e568667ac217f7efe6913111812de0c2 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 15:07:56 -0700 Subject: [PATCH 11/67] Some refactor, cleanup and regression fixes --- Sources/ARTreeModule/ARTree+Sequence.swift | 9 ++- Sources/ARTreeModule/ARTree+delete.swift | 10 +-- Sources/ARTreeModule/ARTree+get.swift | 6 +- Sources/ARTreeModule/ARTree+insert.swift | 22 +++-- Sources/ARTreeModule/ARTree.swift | 4 +- Sources/ARTreeModule/Node+Storage.swift | 44 +++++----- .../ARTreeModule/Node+StringConverter.swift | 13 ++- Sources/ARTreeModule/Node+basics.swift | 20 ++--- Sources/ARTreeModule/Node.swift | 77 ++++++++++++++---- Sources/ARTreeModule/Node16.swift | 32 ++++---- Sources/ARTreeModule/Node256.swift | 30 ++++--- Sources/ARTreeModule/Node4.swift | 26 +++--- Sources/ARTreeModule/Node48.swift | 30 ++++--- Sources/ARTreeModule/NodeLeaf.swift | 80 ++++++++++++------- Tests/ARTreeModuleTests/DeleteTests.swift | 34 ++++---- Tests/ARTreeModuleTests/InsertTests.swift | 30 +++---- Tests/ARTreeModuleTests/Node16Tests.swift | 24 +++--- Tests/ARTreeModuleTests/Node256Tests.swift | 10 +-- Tests/ARTreeModuleTests/Node48Tests.swift | 10 +-- Tests/ARTreeModuleTests/Node4Tests.swift | 52 +++++++----- Tests/ARTreeModuleTests/NodeBasicTests.swift | 6 +- Tests/ARTreeModuleTests/NodeLeafTests.swift | 28 +++---- 22 files changed, 335 insertions(+), 262 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 92d4e01a6..a2d835b9f 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -22,7 +22,8 @@ extension ARTree: Sequence { self.tree = tree self.path = [] if let node = tree.root { - let n = node as! any InternalNode + assert(node.type != .leaf) + let n = node.toInternalNode() self.path = [(n, n.index())] } } @@ -61,13 +62,13 @@ extension ARTree.Iterator: IteratorProtocol { let next = node.child(at: index)! if next.type == .leaf { - let leaf = next as! NodeLeaf - let result = (leaf.key, leaf.value) + let leaf: NodeLeaf = next.toLeafNode() + let result: (Key, Value) = (leaf.key, leaf.value()) advanceToNextChild() return result } - path.append((next as! any InternalNode, node.index())) + path.append((next.toInternalNode(), node.index())) } } diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index c00385dae..7639881c5 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -24,26 +24,26 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(node: any Node, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool + private mutating func _delete(node: RawNode, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool { var newDepth = depth var _node = node if _node.type == .leaf { - let leaf = _node as! NodeLeaf + let leaf: NodeLeaf = _node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { return false } ref?.pointee = nil - leaf.withKeyValue { _, valuePtr in - valuePtr.deinitialize(count: 1) + leaf.withValue(of: Value.self) { + $0.deinitialize(count: 1) } return true } - var node = _node as! any InternalNode + var node = _node.toInternalNode() if node.partialLength > 0 { let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) assert(matchedBytes <= node.partialLength) diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index 3fe8db58a..ee9aa08da 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -19,13 +19,13 @@ extension ARTree { } if _node.type == .leaf { - let leaf = _node as! NodeLeaf + let leaf: NodeLeaf = _node.toLeafNode() return leaf.keyEquals(with: key) - ? leaf.value + ? leaf.value() : nil } - let node = _node as! any InternalNode + let node = _node.toInternalNode() if node.partialLength > 0 { let prefixLen = node.prefixMismatch(withKey: key, fromIndex: depth) assert(prefixLen <= Const.maxPartialLength, "partial length is always bounded") diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 4e9e32314..c871e3303 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -11,7 +11,7 @@ extension ARTree { public mutating func insert(key: Key, value: Value) -> Bool { - var current: (any Node)? = root + var current: RawNode = root! // Location of child (current) pointer in parent, i.e. memory address where the // address of current node is stored inside the parent node. @@ -20,19 +20,15 @@ extension ARTree { var depth = 0 while depth < key.count { - guard var _node = current else { - assert(false, "current node can never be nil") - } - // Reached leaf already, replace it with a new node, or update the existing value. - if _node.type == .leaf { - let leaf = _node as! NodeLeaf + if current.type == .leaf { + let leaf: NodeLeaf = current.toLeafNode() if leaf.keyEquals(with: key) { //TODO: Replace value. fatalError("replace not supported") } - let newLeaf = NodeLeaf.allocate(key: key, value: value) + let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) var newNode = Node4.allocate() @@ -55,11 +51,11 @@ extension ARTree { newNode = nextNode } - ref?.pointee = newNode // Replace child in parent. + ref?.pointee = RawNode(from: newNode) // Replace child in parent. return true } - var node = _node as! any InternalNode + var node = current.toInternalNode() if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) @@ -80,9 +76,9 @@ extension ARTree { node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) node.partialLength -= prefixDiff + 1 - let newLeaf = NodeLeaf.allocate(key: key, value: value) + let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) - ref?.pointee = newNode + ref?.pointee = newNode.rawNode return true } } @@ -90,7 +86,7 @@ extension ARTree { // Find next child to continue. guard let next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. - let newLeaf = NodeLeaf.allocate(key: key, value: value) + let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) node.addChild(forKey: key[depth], node: newLeaf, ref: ref) return true } diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index b2a74037d..660c074ec 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -27,9 +27,9 @@ // * Values should be whatever. public struct ARTree { - var root: (any Node)? + var root: RawNode? public init() { - self.root = Node4.allocate() + self.root = RawNode(from: Node4.allocate()) } } diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index bf3b057cf..d36f22a8d 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -11,48 +11,56 @@ typealias RawNodeBuffer = ManagedBuffer -final class NodeBuffer: RawNodeBuffer { +final class NodeBuffer: RawNodeBuffer { deinit { - ArtNode.deinitialize(NodeStorage(self)) + Mn.deinitialize(NodeStorage(self)) } } -struct NodeStorage { - typealias Header = ArtNode.Header - var buf: NodeBuffer +struct NodeStorage { + var buf: NodeBuffer } extension NodeStorage { - fileprivate init(_ buf: NodeBuffer) { + fileprivate init(_ buf: NodeBuffer) { self.buf = buf } - init(_ raw: RawNodeBuffer) { - self.buf = unsafeDowncast(raw, to: NodeBuffer.self) + init(raw: RawNodeBuffer) { + self.buf = raw as! NodeBuffer } } extension NodeStorage { static func create(type: NodeType, size: Int) -> RawNodeBuffer { - return NodeBuffer.create(minimumCapacity: size, - makingHeaderWith: {_ in type }) + let buf = NodeBuffer.create(minimumCapacity: size, + makingHeaderWith: {_ in type }) + buf.withUnsafeMutablePointerToElements { + $0.initialize(repeating: 0, count: size) + } + return buf + } + + func withUnsafePointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { + return try buf.withUnsafeMutablePointerToElements { + return try body(UnsafeMutableRawPointer($0)) + } } } -extension NodeStorage where ArtNode: InternalNode { - static func allocate() -> NodeStorage { - let size = ArtNode.size - let buf = NodeStorage.create(type: ArtNode.type, size: size) - let storage = NodeStorage(buf) +extension NodeStorage where Mn: InternalNode { + typealias Header = Mn.Header + + static func allocate() -> NodeStorage { + let size = Mn.size + let buf = NodeStorage.create(type: Mn.type, size: size) + let storage = NodeStorage(raw: buf) buf.withUnsafeMutablePointerToElements { - $0.initialize(repeating: 0, count: size) UnsafeMutableRawPointer($0).bindMemory(to: Header.self, capacity: 1) } return storage } -} -extension NodeStorage { func withHeaderPointer(_ body: (UnsafeMutablePointer
) throws -> R) rethrows -> R { return try buf.withUnsafeMutablePointerToElements { return try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Header.self)) diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index c54788605..e46d89a27 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -33,13 +33,19 @@ func indent(_ width: Int, last: Bool) -> String { extension ARTree: CustomStringConvertible { public var description: String { if let node = root { - return "○ " + node.prettyPrint(depth: 0, value: Value.self) + return "○ " + node.toManagedNode().prettyPrint(depth: 0, value: Value.self) } else { return "<>" } } } +extension RawNode: NodePrettyPrinter { + func prettyPrint(depth: Int, value: Value.Type) -> String { + return toManagedNode().prettyPrint(depth: depth, value: Value.self) + } +} + extension InternalNode { fileprivate var partial: [UInt8] { var arr = [UInt8](repeating: 0, count: partialLength) @@ -52,8 +58,9 @@ extension InternalNode { } extension NodeLeaf: NodePrettyPrinter { - func prettyPrint<_Value>(depth: Int, value: _Value.Type) -> String { - "\(self.keyLength)\(self.key) -> \(self.value)" + func prettyPrint(depth: Int, value: Value.Type) -> String { + let val: Value = self.value() + return "\(self.keyLength)\(self.key) -> \(val)" } } diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/Node+basics.swift index 8244fd713..0978319f9 100644 --- a/Sources/ARTreeModule/Node+basics.swift +++ b/Sources/ARTreeModule/Node+basics.swift @@ -50,21 +50,13 @@ extension InternalNode { } } - func child(forKey k: KeyPart) -> (any Node)? { - var ref = UnsafeMutablePointer<(any Node)?>(nil) + func child(forKey k: KeyPart) -> RawNode? { + var ref = UnsafeMutablePointer(nil) return child(forKey: k, ref: &ref) } - mutating func addChild(forKey k: KeyPart, node: any Node) { - let ref = UnsafeMutablePointer<(any Node)?>(nil) - addChild(forKey: k, node: node, ref: ref) - } - - mutating func addChild( - forKey k: KeyPart, - node: any Node, - ref: ChildSlotPtr? - ) { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) { + let ref = UnsafeMutablePointer(nil) addChild(forKey: k, node: node, ref: ref) } @@ -77,12 +69,12 @@ extension InternalNode { } mutating func deleteChild(forKey k: KeyPart) { - var ptr: (any Node)? = self + var ptr: RawNode? = RawNode(from:self) return deleteChild(forKey: k, ref: &ptr) } mutating func deleteChild(at index: Index) { - var ptr: (any Node)? = self + var ptr: RawNode? = RawNode(from: self) deleteChild(at: index, ref: &ptr) } diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 3be852e61..064ee4604 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -12,21 +12,70 @@ public typealias KeyPart = UInt8 public typealias Key = [KeyPart] -typealias ChildSlotPtr = UnsafeMutablePointer<(any Node)?> +typealias ChildSlotPtr = UnsafeMutablePointer -/// Shared protocol implementation for Node types in an Adaptive Radix Tree -protocol Node: NodePrettyPrinter { - associatedtype Header +struct RawNode { + var storage: RawNodeBuffer +} + +extension RawNode { + init(from: N) { + self.storage = from.storage.buf + } +} + +extension RawNode { + var type: NodeType { + @inline(__always) get { return storage.header } + } + + func toInternalNode() -> any InternalNode { + switch type { + case .node4: + return Node4(buffer: storage) + case .node16: + return Node16(buffer: storage) + case .node48: + return Node48(buffer: storage) + case .node256: + return Node256(buffer: storage) + default: + assert(false, "leaf nodes are not internal nodes") + } + } + + func toLeafNode() -> NodeLeaf { + assert(type == .leaf) + return NodeLeaf(ptr: storage) + } + func toManagedNode() -> any ManagedNode { + switch type { + case .leaf: + return toLeafNode() + default: + return toInternalNode() + } + } +} + +protocol ManagedNode: NodePrettyPrinter { var storage: NodeStorage { get } + static func deinitialize(_ storage: NodeStorage) + + static var type: NodeType { get } var type: NodeType { get } + var rawNode: RawNode { get } +} + +extension ManagedNode { + var rawNode: RawNode { RawNode(from: self) } } -protocol InternalNode: Node { +protocol InternalNode: ManagedNode { typealias Index = Int typealias Header = InternalNodeHeader - static var type: NodeType { get } static var size: Int { get } var count: Int { get set } @@ -37,15 +86,15 @@ protocol InternalNode: Node { func index() -> Index? func next(index: Index) -> Index? - func child(forKey k: KeyPart) -> (any Node)? - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? - func child(at: Index) -> (any Node)? - func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? + func child(forKey k: KeyPart) -> RawNode? + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? + func child(at: Index) -> RawNode? + func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? - mutating func addChild(forKey k: KeyPart, node: any Node) + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) mutating func addChild( forKey k: KeyPart, - node: any Node, + node: any ManagedNode, ref: ChildSlotPtr?) // TODO: Shrinking/expand logic can be moved out. @@ -53,8 +102,8 @@ protocol InternalNode: Node { mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) } -extension Node { - static func deinitialize(_ storage: NodeStorage) { +extension ManagedNode { + static func deinitialize(_ storage: NodeStorage) { // TODO } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index f4890bf79..0a9232c24 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -14,18 +14,18 @@ struct Node16 { var storage: Storage - init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } init(storage: Storage) { - self.storage = storage + self.storage = storage } } extension Node16 { typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + typealias Childs = UnsafeMutableBufferPointer func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in @@ -35,7 +35,7 @@ extension Node16 { ) let childPtr = bodyPtr .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: (any Node)?.self) + .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) return try body(keys, childs) @@ -51,7 +51,7 @@ extension Node16 { UnsafeMutableRawPointer(keys.baseAddress!) .bindMemory(to: UInt8.self, capacity: Self.numKeys) UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return node } @@ -97,17 +97,15 @@ extension Node16 { } } -extension Node16: Node { +extension Node16: InternalNode { static let type: NodeType = .node16 var type: NodeType { .node16 } -} -extension Node16: InternalNode { static let numKeys: Int = 16 static var size: Int { MemoryLayout.stride + Self.numKeys - * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) + * (MemoryLayout.stride + MemoryLayout.stride) } func index(forKey k: KeyPart) -> Index? { @@ -148,7 +146,7 @@ extension Node16: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { guard let index = index(forKey: k) else { return nil } @@ -159,14 +157,14 @@ extension Node16: InternalNode { } } - func child(at: Index) -> (any Node)? { + func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") return withBody { _, childs in return childs[at] } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { assert( index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") @@ -178,7 +176,7 @@ extension Node16: InternalNode { mutating func addChild( forKey k: KeyPart, - node: any Node, + node: any ManagedNode, ref: ChildSlotPtr? ) { if let slot = _insertSlot(forKey: k) { @@ -187,13 +185,13 @@ extension Node16: InternalNode { keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k - childs[slot] = node + childs[slot] = node.rawNode count += 1 } } else { var newNode = Node48.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } @@ -216,7 +214,7 @@ extension Node16: InternalNode { if count == 3 { // Shrink to Node4. let newNode = Node4.allocate(copyFrom: self) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index d5dc7be79..c97e9b70e 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -struct Node256 { +struct Node256: ManagedNode { typealias Storage = NodeStorage var storage: Storage - init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } init(storage: Storage) { @@ -25,13 +25,13 @@ struct Node256 { extension Node256 { typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + typealias Childs = UnsafeMutableBufferPointer func withBody(body: (Childs) throws -> R) rethrows -> R { return try storage.withBodyPointer { return try body( UnsafeMutableBufferPointer( - start: $0.assumingMemoryBound(to: (any Node)?.self), + start: $0.assumingMemoryBound(to: RawNode?.self), count: 256)) } } @@ -43,7 +43,7 @@ extension Node256 { let node = Self(storage: buf) node.withBody { childs in UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return node } @@ -69,16 +69,14 @@ extension Node256 { } -extension Node256: Node { +extension Node256: InternalNode { static let type: NodeType = .node256 var type: NodeType { .node256 } -} -extension Node256: InternalNode { static let numKeys: Int = 256 static var size: Int { - MemoryLayout.stride + 256 * MemoryLayout<(any Node)?>.stride + MemoryLayout.stride + 256 * MemoryLayout.stride } func index(forKey k: KeyPart) -> Index? { @@ -103,21 +101,21 @@ extension Node256: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { return withBody { childs in ref = childs.baseAddress! + Int(k) return childs[Int(k)] } } - func child(at: Int) -> (any Node)? { + func child(at: Int) -> RawNode? { assert(at < 256, "maximum 256 childs allowed") return withBody { childs in return childs[at] } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { assert(index < 256, "maximum 256 childs allowed") return withBody { childs in ref = childs.baseAddress! + index @@ -127,12 +125,12 @@ extension Node256: InternalNode { mutating func addChild( forKey k: KeyPart, - node: any Node, + node: any ManagedNode, ref: ChildSlotPtr? ) { return withBody { childs in assert(childs[Int(k)] == nil, "node for key \(k) already exists") - childs[Int(k)] = node + childs[Int(k)] = node.rawNode count += 1 } } @@ -145,7 +143,7 @@ extension Node256: InternalNode { if count == 40 { let newNode = Node48.allocate(copyFrom: self) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 0f7f11724..278be0be9 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -18,19 +18,17 @@ struct Node4 { } extension Node4 { - init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } -} -extension Node4: Node { static let type: NodeType = .node4 var type: NodeType { .node4 } } extension Node4 { typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + typealias Childs = UnsafeMutableBufferPointer func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in @@ -40,7 +38,7 @@ extension Node4 { ) let childPtr = bodyPtr .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: (any Node)?.self) + .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) return try body(keys, childs) @@ -56,7 +54,7 @@ extension Node4 { UnsafeMutableRawPointer(keys.baseAddress!) .bindMemory(to: UInt8.self, capacity: Self.numKeys) UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return node } @@ -80,7 +78,7 @@ extension Node4 { extension Node4: InternalNode { static var size: Int { MemoryLayout.stride + Self.numKeys - * (MemoryLayout.stride + MemoryLayout<(any Node)?>.stride) + * (MemoryLayout.stride + MemoryLayout.stride) } func index(forKey k: KeyPart) -> Index? { @@ -120,7 +118,7 @@ extension Node4: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { guard let index = index(forKey: k) else { return nil } @@ -131,14 +129,14 @@ extension Node4: InternalNode { } } - func child(at: Index) -> (any Node)? { + func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") return withBody { _, childs in return childs[at] } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { assert( index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") @@ -150,7 +148,7 @@ extension Node4: InternalNode { mutating func addChild( forKey k: KeyPart, - node: any Node, + node: any ManagedNode, ref: ChildSlotPtr? ) { if let slot = _insertSlot(forKey: k) { @@ -159,13 +157,13 @@ extension Node4: InternalNode { keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k - childs[slot] = node + childs[slot] = node.rawNode count += 1 } } else { var newNode = Node16.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index f8c54bfce..baffdba09 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -14,8 +14,8 @@ struct Node48 { var storage: Storage - init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } init(storage: Storage) { @@ -26,7 +26,7 @@ struct Node48 { extension Node48 { typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer<(any Node)?> + typealias Childs = UnsafeMutableBufferPointer func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in @@ -40,7 +40,7 @@ extension Node48 { // TODO: Can we initialize buffer using any stdlib method? let childPtr = bodyPtr .advanced(by: 256 * MemoryLayout.stride) - .assumingMemoryBound(to: (any Node)?.self) + .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) return try body(keys, childs) @@ -57,7 +57,7 @@ extension Node48 { UnsafeMutableRawPointer(keys.baseAddress!) .bindMemory(to: UInt8.self, capacity: Self.numKeys) UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: (any Node)?.self, capacity: Self.numKeys) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) for idx in 0..<256 { keys[idx] = 0xFF @@ -108,17 +108,15 @@ extension Node48 { } -extension Node48: Node { +extension Node48: InternalNode { static let type: NodeType = .node48 var type: NodeType { .node48 } -} -extension Node48: InternalNode { static let numKeys: Int = 48 static var size: Int { MemoryLayout.stride + 256*MemoryLayout.stride + - Self.numKeys*MemoryLayout<(any Node)?>.stride + Self.numKeys*MemoryLayout.stride } func index(forKey k: KeyPart) -> Index? { @@ -144,7 +142,7 @@ extension Node48: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { return withBody { keys, childs in let childIndex = Int(keys[Int(k)]) if childIndex == 0xFF { @@ -156,14 +154,14 @@ extension Node48: InternalNode { } } - func child(at: Int) -> (any Node)? { + func child(at: Int) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") return withBody { _, childs in return childs[at] } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> (any Node)? { + func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed") return withBody { _, childs in ref = childs.baseAddress! + index @@ -173,7 +171,7 @@ extension Node48: InternalNode { mutating func addChild( forKey k: KeyPart, - node: any Node, + node: any ManagedNode, ref: ChildSlotPtr? ) { if count < Self.numKeys { @@ -186,14 +184,14 @@ extension Node48: InternalNode { } keys[Int(k)] = KeyPart(slot) - childs[slot] = node + childs[slot] = node.rawNode } self.count += 1 } else { var newNode = Node256.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } @@ -230,7 +228,7 @@ extension Node48: InternalNode { // 6. Shrink the node to Node16 if needed. if count == 13 { // TODO: Should be made tunable. let newNode = Node16.allocate(copyFrom: self) - ref?.pointee = newNode + ref?.pointee = RawNode(from: newNode) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 14fa30512..3076f5b12 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -struct NodeLeaf { +struct NodeLeaf { typealias Storage = NodeStorage var storage: Storage @@ -17,22 +17,43 @@ struct NodeLeaf { extension NodeLeaf { init(ptr: RawNodeBuffer) { - self.init(storage: Storage(ptr)) + self.init(storage: Storage(raw: ptr)) } } -extension NodeLeaf { - struct Header { - var keyLength: UInt32 +extension NodeLeaf: ManagedNode { + typealias KeyPtr = UnsafeMutableBufferPointer + + static var type: NodeType { .leaf } + var type: NodeType { .leaf } + + func withKey(body: (KeyPtr) throws -> R) rethrows -> R { + return try storage.withUnsafePointer { + let keyPtr = UnsafeMutableBufferPointer( + start: $0 + .advanced(by: MemoryLayout.stride) + .assumingMemoryBound(to: KeyPart.self), + count: Int(keyLength)) + return try body(keyPtr) + } } - typealias KeyPtr = UnsafeMutableBufferPointer - typealias ValuePtr = UnsafeMutablePointer + func withValue(of: Value.Type, + body: (UnsafeMutablePointer) throws -> R) rethrows -> R { + return try storage.withUnsafePointer { + return try body( + $0.advanced(by: MemoryLayout.stride) + .advanced(by: keyLength) + .assumingMemoryBound(to: Value.self)) + } + } - func withKeyValue(body: (KeyPtr, ValuePtr) throws -> R) rethrows -> R { - return try storage.withBodyPointer { + func withKeyValue( + body: (KeyPtr, UnsafeMutablePointer) throws -> R) rethrows -> R { + return try storage.withUnsafePointer { + let base = $0.advanced(by: MemoryLayout.stride) let keyPtr = UnsafeMutableBufferPointer( - start: $0.assumingMemoryBound(to: KeyPart.self), + start: base.assumingMemoryBound(to: KeyPart.self), count: Int(keyLength) ) let valuePtr = UnsafeMutableRawPointer( @@ -43,31 +64,34 @@ extension NodeLeaf { } var key: Key { - withKeyValue { k, _ in Array(k) } + get { withKey { k in Array(k) } } } - var value: Value { - get { withKeyValue { $1.pointee } } - set { withKeyValue { $1.pointee = newValue } } - } - - var keyLength: UInt32 { + var keyLength: Int { get { - storage.withHeaderPointer { $0.pointee.keyLength } + storage.withUnsafePointer { + Int($0.assumingMemoryBound(to: UInt32.self).pointee) + } } set { - storage.withHeaderPointer { $0.pointee.keyLength = newValue } + storage.withUnsafePointer { + $0.assumingMemoryBound(to: UInt32.self).pointee = UInt32(newValue) + } } } + + func value() -> Value { + return withValue(of: Value.self) { $0.pointee } + } } extension NodeLeaf { - static func allocate(key: Key, value: Value) -> Self { - let size = MemoryLayout
.stride + key.count + MemoryLayout.stride + static func allocate(key: Key, value: Value, of: Value.Type) -> Self { + let size = MemoryLayout.stride + key.count + MemoryLayout.stride let buf = NodeStorage.create(type: .leaf, size: size) var leaf = Self(ptr: buf) - leaf.keyLength = UInt32(key.count) + leaf.keyLength = key.count leaf.withKeyValue { keyPtr, valuePtr in key.withUnsafeBytes { UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) @@ -83,7 +107,7 @@ extension NodeLeaf { return false } - return withKeyValue { keyPtr, _ in + return withKey { keyPtr in for ii in depth.. Int { - let maxComp = Int(min(keyLength, other.keyLength) - UInt32(fromIndex)) + let maxComp = Int(min(keyLength, other.keyLength) - fromIndex) - return withKeyValue { keyPtr, _ in - return other.withKeyValue { otherKeyPtr, _ in + return withKey { keyPtr in + return other.withKey { otherKeyPtr in for index in 0.. [2]") } - func testDeleteCompressToLeaf() throws { - var t = ARTree<[UInt8]>() - t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) - t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) - t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) - t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) - t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) - t.delete(key: [1, 2, 3, 4, 5, 6]) - t.delete(key: [1, 2, 3, 4, 8, 9]) - XCTAssertEqual( - t.description, - "○ Node4 {childs=2, partial=[]}\n" + - "├──○ 1: Node4 {childs=2, partial=[2]}\n" + - "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + - "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + - "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") - } + // func testDeleteCompressToLeaf() throws { + // var t = ARTree<[UInt8]>() + // t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + // t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) + // t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) + // t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) + // t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) + // t.delete(key: [1, 2, 3, 4, 5, 6]) + // t.delete(key: [1, 2, 3, 4, 8, 9]) + // XCTAssertEqual( + // t.description, + // "○ Node4 {childs=2, partial=[]}\n" + + // "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + // "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + + // "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + // "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + // } func testDeleteCompressToNode4() throws { var t = ARTree<[UInt8]>() diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 7d94a4ba0..12b03eebb 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -82,7 +82,7 @@ final class ARTreeInsertTests: XCTestCase { } } - let root = t.root! as! (any InternalNode) + let root = t.root!.toInternalNode() XCTAssertEqual(root.count, 40) XCTAssertEqual( t.description, @@ -144,7 +144,7 @@ final class ARTreeInsertTests: XCTestCase { } } - let root = t.root! as! (any InternalNode) + let root = t.root!.toInternalNode() XCTAssertEqual(root.count, 70) XCTAssertEqual( t.description, @@ -271,19 +271,14 @@ final class ARTreeInsertTests: XCTestCase { ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), ([15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ( - [99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243] - ), + ([99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), ([118, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([24, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([45, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([12, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([22, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), ([15, 15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ( - [57, 99, 118, 20, 190, 173, 101, 67, 245], - [161, 154, 111, 179, 216, 198, 248, 206, 164, 243] - ), + ([57, 99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), ([57, 83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), ([16, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), ([17, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), @@ -309,16 +304,10 @@ final class ARTreeInsertTests: XCTestCase { func testInsertPrefixSmallLong() throws { var testCase: [([UInt8], [UInt8])] = [ - ( - [1, 2, 1, 2, 3, 2, 3, 3, 1, 3, 1, 0, 0, 2, 0, 3, 0, 1, 1], - [1, 1, 2, 0, 3, 1, 3, 1, 0, 1, 3, 3, 1, 2, 3, 1, 1, 0, 1] - ), + ([1, 2, 1, 2, 3, 2, 3, 3, 1, 3, 1, 0, 0, 2, 0, 3, 0, 1, 1], [1, 1, 2, 0, 3, 1, 3, 1, 0, 1, 3, 3, 1, 2, 3, 1, 1, 0, 1]), ([1, 3, 2, 2, 1, 0, 0, 2, 3, 2, 0], [0, 0, 2, 0, 3, 3, 0, 3, 3, 0, 2, 3, 3, 1, 2, 3, 2]), ([2, 0, 1, 0, 1, 2, 2, 3], [3, 3, 1, 3, 2, 2, 2, 1, 2, 0, 2, 1, 0, 0, 0, 2, 0, 1, 1, 1]), - ( - [3, 1, 2, 0, 3, 1, 0, 2, 1, 0, 0, 3, 3, 3, 3, 0, 2, 3], - [1, 0, 1, 0, 3, 3, 2, 2, 2, 1, 2, 1, 0, 3, 1, 3, 3, 1, 3, 3] - ), + ([3, 1, 2, 0, 3, 1, 0, 2, 1, 0, 0, 3, 3, 3, 3, 0, 2, 3], [1, 0, 1, 0, 3, 3, 2, 2, 2, 1, 2, 1, 0, 3, 1, 3, 3, 1, 3, 3]), ([2, 0, 2, 2, 0, 2, 2, 0, 3, 2, 2, 3, 1, 0, 1], [1, 1, 3, 2, 2, 1]), ([2, 1, 2, 3, 1, 2, 2, 1, 2, 1, 0, 2, 2, 1], [3, 2, 2, 3, 0, 1, 0, 3, 3, 0, 1, 2]), ] @@ -327,6 +316,13 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } + + var total = 0 + for (k, v)in tree { + total += 1 + print("<--->", k, v) + } + XCTAssertEqual(total, testCase.count) XCTAssertEqual(tree.root?.type, .node4) testCase.shuffle() diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index 7fc0b0505..00f46fc14 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode16Tests: XCTestCase { func test16Basic() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=2, partial=[]}\n" + @@ -27,10 +27,10 @@ final class ARTreeNode16Tests: XCTestCase { func test4AddInMiddle() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) - node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=4, partial=[]}\n" + @@ -42,9 +42,9 @@ final class ARTreeNode16Tests: XCTestCase { func test16DeleteAtIndex() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=3, partial=[]}\n" + @@ -68,9 +68,9 @@ final class ARTreeNode16Tests: XCTestCase { func test16DeleteKey() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=3, partial=[]}\n" + diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index 949c731ba..d7d578f3e 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode256Tests: XCTestCase { func test256Basic() throws { var node = Node256.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=2, partial=[]}\n" + @@ -27,9 +27,9 @@ final class ARTreeNode256Tests: XCTestCase { func test48DeleteAtIndex() throws { var node = Node256.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=3, partial=[]}\n" + diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index 519b3da69..b2b1e0a18 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode48Tests: XCTestCase { func test48Basic() throws { var node = Node48.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=2, partial=[]}\n" + @@ -27,9 +27,9 @@ final class ARTreeNode48Tests: XCTestCase { func test48DeleteAtIndex() throws { var node = Node48.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=3, partial=[]}\n" + diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index b2a377d96..8b661b425 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode4Tests: XCTestCase { func test4Basic() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + @@ -25,12 +25,23 @@ final class ARTreeNode4Tests: XCTestCase { "└──○ 20: 1[20] -> [22]") } + func test4BasicInt() throws { + var node = Node4.allocate() + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: 11, of: Int.self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: 22, of: Int.self)) + XCTAssertEqual( + node.print(value: Int.self), + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> 11\n" + + "└──○ 20: 1[20] -> 22") + } + func test4AddInMiddle() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2])) - node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=4, partial=[]}\n" + @@ -42,9 +53,9 @@ final class ARTreeNode4Tests: XCTestCase { func test4DeleteAtIndex() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=3, partial=[]}\n" + @@ -58,7 +69,7 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - var ptr: (any Node)? = node + var ptr: RawNode? = node.rawNode node.deleteChild(at: 1, ref: &ptr) XCTAssertNotNil(ptr) XCTAssertEqual(ptr?.type, .leaf) @@ -66,11 +77,10 @@ final class ARTreeNode4Tests: XCTestCase { func test4ExapandTo16() throws { var node = Node4.allocate() - - node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1])) - node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2])) - node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3])) - node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4])) + node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1], of: [UInt8].self)) + node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2], of: [UInt8].self)) + node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3], of: [UInt8].self)) + node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=4, partial=[]}\n" + @@ -79,10 +89,12 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - var addr: (any Node)? = node + var addr: RawNode? = node.rawNode withUnsafeMutablePointer(to: &addr) { let ref: ChildSlotPtr? = $0 - node.addChild(forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5]), ref: ref) + node.addChild(forKey: 5, + node: NodeLeaf.allocate(key: [5], value: [5], of: [UInt8].self), + ref: ref) XCTAssertEqual( ref!.pointee!.print(value: [UInt8].self), "○ Node16 {childs=5, partial=[]}\n" + @@ -96,9 +108,9 @@ final class ARTreeNode4Tests: XCTestCase { func test4DeleteKey() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1])) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2])) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3])) + node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=3, partial=[]}\n" + diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index e7e25a2a1..0cbf7da0b 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -18,15 +18,15 @@ final class ARTreeNodeBasicTests: XCTestCase { let header = MemoryLayout.stride XCTAssertEqual(header, 12) - let childSlotSize = MemoryLayout<(any Node)?>.stride + let childSlotSize = MemoryLayout.stride let ptrSize = MemoryLayout.stride - let refSize = MemoryLayout<(any Node)?>.stride + let refSize = MemoryLayout.stride let size4 = Node4.size let size16 = Node16.size let size48 = Node48.size let size256 = Node256.size - print("sizeof((any Node)?) = \(refSize)") + print("sizeof(RawNode?) = \(refSize)") print("sizeOf(Int) = \(ptrSize)") print("sizeOf(childSlot) = \(childSlotSize)") print("sizeOf(Header) = \(header)") diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index a7590a329..d3d050b40 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -15,18 +15,18 @@ import XCTest final class ARTreeNodeLeafTests: XCTestCase { func testLeafBasic() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) XCTAssertEqual(leaf1.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0]") - let leaf2 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + let leaf2 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2], of: [UInt8].self) XCTAssertEqual(leaf2.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") - let leaf3 = NodeLeaf.allocate(key: [], value: []) + let leaf3 = NodeLeaf.allocate(key: [], value: [], of: [UInt8].self) XCTAssertEqual(leaf3.print(value: [UInt8].self), "○ 0[] -> []") } func testLeafKeyEquals() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 50])) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30])) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 40, 50])) @@ -34,46 +34,46 @@ final class ARTreeNodeLeafTests: XCTestCase { } func testCasts() throws { - let leaf = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0]) + let leaf = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) XCTAssertEqual(leaf.key, [10, 20, 30, 40]) - XCTAssertEqual(leaf.value, [0]) + XCTAssertEqual(leaf.value(), [0]) } func testLeafLcp() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2], of: [UInt8].self) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0, 1, 2, 3], value: [0]), + with: NodeLeaf.allocate(key: [0, 1, 2, 3], value: [0], of: [UInt8].self), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0], value: [0]), + with: NodeLeaf.allocate(key: [0], value: [0], of: [UInt8].self), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0, 1], value: [0]), + with: NodeLeaf.allocate(key: [0, 1], value: [0], of: [UInt8].self), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 1], value: [0]), + with: NodeLeaf.allocate(key: [10, 1], value: [0], of: [UInt8].self), fromIndex: 0), 1) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0]), + with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), fromIndex: 0), 2) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0]), + with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), fromIndex: 1), 1) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0]), + with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), fromIndex: 2), 0) From 69ca51373dfdb45f7d38b63c53c23b8a15f9f08c Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 17:34:09 -0700 Subject: [PATCH 12/67] Iterate empty tree --- Sources/ARTreeModule/ARTree+Sequence.swift | 8 +++++--- Tests/ARTreeModuleTests/SequenceTests.swift | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index a2d835b9f..86241486e 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -21,9 +21,11 @@ extension ARTree: Sequence { init(tree: ARTree) { self.tree = tree self.path = [] - if let node = tree.root { - assert(node.type != .leaf) - let n = node.toInternalNode() + guard let node = tree.root else { return } + + assert(node.type != .leaf, "root can't be leaf") + let n = node.toInternalNode() + if node.toInternalNode().count > 0 { self.path = [(n, n.index())] } } diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift index 104edf20a..2e4d49ad0 100644 --- a/Tests/ARTreeModuleTests/SequenceTests.swift +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -14,6 +14,15 @@ import XCTest @testable import ARTreeModule final class ARTreeSequenceTests: XCTestCase { + func testSequenceEmpty() throws { + var t = ARTree<[UInt8]>() + var total = 0 + for (k, v) in t { + total += 1 + } + XCTAssertEqual(total, 0) + } + func testSequenceBasic() throws { let sizes = [3, 12, 30, 70] for size in sizes { From 439c6a114b5849a9c7762634e79410704c258633 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 18:14:31 -0700 Subject: [PATCH 13/67] Remove some TODO's --- Sources/ARTreeModule/ARTree.swift | 8 +------- Sources/ARTreeModule/Node.swift | 4 ++-- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 660c074ec..e8dc5ac70 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -10,12 +10,9 @@ //===----------------------------------------------------------------------===// // TODO: -// * Check deallocate of nodes. -// * Path compression when deleting. // * Range delete. // * Delete node should delete all sub-childs (for range deletes) -// * Confirm to Swift Dictionary/Iterator protocols. -// * Fixed sized array. +// * Confirm to Swift dictionary protocols. // * Generic/any serializable type? // * Binary search Node16. // * SIMD instructions for Node4. @@ -23,9 +20,6 @@ // * Better test cases. // * Fuzz testing. // * Leaf don't need to store entire key. -// * Memory safety in Swift? -// * Values should be whatever. - public struct ARTree { var root: RawNode? diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 064ee4604..84c0a8470 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -60,10 +60,10 @@ extension RawNode { } protocol ManagedNode: NodePrettyPrinter { - var storage: NodeStorage { get } static func deinitialize(_ storage: NodeStorage) - static var type: NodeType { get } + + var storage: NodeStorage { get } var type: NodeType { get } var rawNode: RawNode { get } } From 91e2c7ce39ee753a232deaf37f08a0cbb45a06ed Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 18:22:49 -0700 Subject: [PATCH 14/67] Move around and some cleanup --- Sources/ARTreeModule/ARTree+get.swift | 9 +-- ...e+basics.swift => InternalNode+impl.swift} | 0 Sources/ARTreeModule/Node+Storage.swift | 10 +-- Sources/ARTreeModule/Node.swift | 60 ++---------------- Sources/ARTreeModule/Node16.swift | 55 ++++++++-------- Sources/ARTreeModule/Node256.swift | 46 +++++++------- Sources/ARTreeModule/Node4.swift | 53 ++++++++-------- Sources/ARTreeModule/Node48.swift | 62 +++++++++---------- Sources/ARTreeModule/NodeLeaf.swift | 46 ++++++++------ Sources/ARTreeModule/RawNode.swift | 53 ++++++++++++++++ 10 files changed, 194 insertions(+), 200 deletions(-) rename Sources/ARTreeModule/{Node+basics.swift => InternalNode+impl.swift} (100%) create mode 100644 Sources/ARTreeModule/RawNode.swift diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index ee9aa08da..01ec78b9f 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -11,21 +11,22 @@ extension ARTree { public func getValue(key: Key) -> Value? { + assert(root != nil, "root can't be nil") var current = root var depth = 0 while depth <= key.count { - guard let _node = current else { + guard let _rawNode = current else { return nil } - if _node.type == .leaf { - let leaf: NodeLeaf = _node.toLeafNode() + if _rawNode.type == .leaf { + let leaf: NodeLeaf = _rawNode.toLeafNode() return leaf.keyEquals(with: key) ? leaf.value() : nil } - let node = _node.toInternalNode() + let node = _rawNode.toInternalNode() if node.partialLength > 0 { let prefixLen = node.prefixMismatch(withKey: key, fromIndex: depth) assert(prefixLen <= Const.maxPartialLength, "partial length is always bounded") diff --git a/Sources/ARTreeModule/Node+basics.swift b/Sources/ARTreeModule/InternalNode+impl.swift similarity index 100% rename from Sources/ARTreeModule/Node+basics.swift rename to Sources/ARTreeModule/InternalNode+impl.swift diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index d36f22a8d..76c08b83c 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -13,7 +13,7 @@ typealias RawNodeBuffer = ManagedBuffer final class NodeBuffer: RawNodeBuffer { deinit { - Mn.deinitialize(NodeStorage(self)) + Mn.deinitialize(NodeStorage(buf: self)) } } @@ -22,16 +22,10 @@ struct NodeStorage { } extension NodeStorage { - fileprivate init(_ buf: NodeBuffer) { - self.buf = buf - } - init(raw: RawNodeBuffer) { self.buf = raw as! NodeBuffer } -} -extension NodeStorage { static func create(type: NodeType, size: Int) -> RawNodeBuffer { let buf = NodeBuffer.create(minimumCapacity: size, makingHeaderWith: {_ in type }) @@ -40,7 +34,9 @@ extension NodeStorage { } return buf } +} +extension NodeStorage { func withUnsafePointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { return try buf.withUnsafeMutablePointerToElements { return try body(UnsafeMutableRawPointer($0)) diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 84c0a8470..993180bb0 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -14,64 +14,17 @@ public typealias Key = [KeyPart] typealias ChildSlotPtr = UnsafeMutablePointer -struct RawNode { - var storage: RawNodeBuffer -} - -extension RawNode { - init(from: N) { - self.storage = from.storage.buf - } -} - -extension RawNode { - var type: NodeType { - @inline(__always) get { return storage.header } - } - - func toInternalNode() -> any InternalNode { - switch type { - case .node4: - return Node4(buffer: storage) - case .node16: - return Node16(buffer: storage) - case .node48: - return Node48(buffer: storage) - case .node256: - return Node256(buffer: storage) - default: - assert(false, "leaf nodes are not internal nodes") - } - } - - func toLeafNode() -> NodeLeaf { - assert(type == .leaf) - return NodeLeaf(ptr: storage) - } - - func toManagedNode() -> any ManagedNode { - switch type { - case .leaf: - return toLeafNode() - default: - return toInternalNode() - } - } -} - protocol ManagedNode: NodePrettyPrinter { - static func deinitialize(_ storage: NodeStorage) + typealias Storage = NodeStorage + + static func deinitialize(_ storage: NodeStorage) static var type: NodeType { get } - var storage: NodeStorage { get } + var storage: Storage { get } var type: NodeType { get } var rawNode: RawNode { get } } -extension ManagedNode { - var rawNode: RawNode { RawNode(from: self) } -} - protocol InternalNode: ManagedNode { typealias Index = Int typealias Header = InternalNodeHeader @@ -103,7 +56,6 @@ protocol InternalNode: ManagedNode { } extension ManagedNode { - static func deinitialize(_ storage: NodeStorage) { - // TODO - } + var rawNode: RawNode { RawNode(from: self) } + var type: NodeType { Self.type } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 0a9232c24..7a427317e 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -10,36 +10,15 @@ //===----------------------------------------------------------------------===// struct Node16 { - typealias Storage = NodeStorage - var storage: Storage - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } - - init(storage: Storage) { - self.storage = storage - } } extension Node16 { - typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer - - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) - let childPtr = bodyPtr - .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + static let type: NodeType = .node16 + static let numKeys: Int = 16 - return try body(keys, childs) - } + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } } @@ -97,12 +76,27 @@ extension Node16 { } } -extension Node16: InternalNode { - static let type: NodeType = .node16 - var type: NodeType { .node16 } +extension Node16 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer - static let numKeys: Int = 16 + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + let childPtr = bodyPtr + .advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + + return try body(keys, childs) + } + } +} +extension Node16: InternalNode { static var size: Int { MemoryLayout.stride + Self.numKeys * (MemoryLayout.stride + MemoryLayout.stride) @@ -219,4 +213,7 @@ extension Node16: InternalNode { } } } + + static func deinitialize(_ storage: NodeStorage) { + } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index c97e9b70e..11ed447ae 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -9,31 +9,16 @@ // //===----------------------------------------------------------------------===// -struct Node256: ManagedNode { - typealias Storage = NodeStorage - +struct Node256 { var storage: Storage - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } - - init(storage: Storage) { - self.storage = storage - } } extension Node256 { - typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer + static let type: NodeType = .node256 + static let numKeys: Int = 256 - func withBody(body: (Childs) throws -> R) rethrows -> R { - return try storage.withBodyPointer { - return try body( - UnsafeMutableBufferPointer( - start: $0.assumingMemoryBound(to: RawNode?.self), - count: 256)) - } + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } } @@ -68,13 +53,21 @@ extension Node256 { } } +extension Node256 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer -extension Node256: InternalNode { - static let type: NodeType = .node256 - var type: NodeType { .node256 } - - static let numKeys: Int = 256 + func withBody(body: (Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { + return try body( + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: RawNode?.self), + count: 256)) + } + } +} +extension Node256: InternalNode { static var size: Int { MemoryLayout.stride + 256 * MemoryLayout.stride } @@ -148,4 +141,7 @@ extension Node256: InternalNode { } } } + + static func deinitialize(_ storage: NodeStorage) { + } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 278be0be9..095c17caf 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -10,39 +10,15 @@ //===----------------------------------------------------------------------===// struct Node4 { - static let numKeys: Int = 4 - - typealias Storage = NodeStorage - var storage: Storage } extension Node4 { - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } - static let type: NodeType = .node4 - var type: NodeType { .node4 } -} - -extension Node4 { - typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer - - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) - let childPtr = bodyPtr - .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + static let numKeys: Int = 4 - return try body(keys, childs) - } + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } } @@ -75,6 +51,26 @@ extension Node4 { } } +extension Node4 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer + + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + let childPtr = bodyPtr + .advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + + return try body(keys, childs) + } + } +} + extension Node4: InternalNode { static var size: Int { MemoryLayout.stride + Self.numKeys @@ -189,4 +185,7 @@ extension Node4: InternalNode { } } } + + static func deinitialize(_ storage: NodeStorage) { + } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index baffdba09..a17bf5309 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -10,41 +10,15 @@ //===----------------------------------------------------------------------===// struct Node48 { - typealias Storage = NodeStorage - var storage: Storage - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } - - init(storage: Storage) { - self.storage = storage - } } - extension Node48 { - typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer - - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: 256 - ) - - // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will - // refer to the first child. - // TODO: Can we initialize buffer using any stdlib method? - let childPtr = bodyPtr - .advanced(by: 256 * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + static let type: NodeType = .node48 + static let numKeys: Int = 48 - return try body(keys, childs) - } + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } } @@ -107,13 +81,31 @@ extension Node48 { } } +extension Node48 { + typealias Keys = UnsafeMutableBufferPointer + typealias Childs = UnsafeMutableBufferPointer -extension Node48: InternalNode { - static let type: NodeType = .node48 - var type: NodeType { .node48 } + func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + return try storage.withBodyPointer { bodyPtr in + let keys = UnsafeMutableBufferPointer( + start: bodyPtr.assumingMemoryBound(to: KeyPart.self), + count: 256 + ) - static let numKeys: Int = 48 + // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will + // refer to the first child. + // TODO: Can we initialize buffer using any stdlib method? + let childPtr = bodyPtr + .advanced(by: 256 * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + return try body(keys, childs) + } + } +} + +extension Node48: InternalNode { static var size: Int { MemoryLayout.stride + 256*MemoryLayout.stride + Self.numKeys*MemoryLayout.stride @@ -246,4 +238,6 @@ extension Node48: InternalNode { } } + static func deinitialize(_ storage: NodeStorage) { + } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 3076f5b12..6d789da86 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -16,16 +16,33 @@ struct NodeLeaf { } extension NodeLeaf { + static var type: NodeType { .leaf } + init(ptr: RawNodeBuffer) { self.init(storage: Storage(raw: ptr)) } } -extension NodeLeaf: ManagedNode { - typealias KeyPtr = UnsafeMutableBufferPointer +extension NodeLeaf { + static func allocate(key: Key, value: Value, of: Value.Type) -> Self { + let size = MemoryLayout.stride + key.count + MemoryLayout.stride + let buf = NodeStorage.create(type: .leaf, size: size) + var leaf = Self(ptr: buf) - static var type: NodeType { .leaf } - var type: NodeType { .leaf } + leaf.keyLength = key.count + leaf.withKeyValue { keyPtr, valuePtr in + key.withUnsafeBytes { + UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) + } + valuePtr.pointee = value + } + + return leaf + } +} + +extension NodeLeaf { + typealias KeyPtr = UnsafeMutableBufferPointer func withKey(body: (KeyPtr) throws -> R) rethrows -> R { return try storage.withUnsafePointer { @@ -86,22 +103,6 @@ extension NodeLeaf: ManagedNode { } extension NodeLeaf { - static func allocate(key: Key, value: Value, of: Value.Type) -> Self { - let size = MemoryLayout.stride + key.count + MemoryLayout.stride - let buf = NodeStorage.create(type: .leaf, size: size) - var leaf = Self(ptr: buf) - - leaf.keyLength = key.count - leaf.withKeyValue { keyPtr, valuePtr in - key.withUnsafeBytes { - UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) - } - valuePtr.pointee = value - } - - return leaf - } - func keyEquals(with key: Key, depth: Int = 0) -> Bool { if key.count != keyLength { return false @@ -133,3 +134,8 @@ extension NodeLeaf { } } } + +extension NodeLeaf: ManagedNode { + static func deinitialize(_ storage: NodeStorage) { + } +} diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift new file mode 100644 index 000000000..1119e93d6 --- /dev/null +++ b/Sources/ARTreeModule/RawNode.swift @@ -0,0 +1,53 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +struct RawNode { + var storage: RawNodeBuffer + + init(from: N) { + self.storage = from.storage.buf + } +} + +extension RawNode { + var type: NodeType { + @inline(__always) get { return storage.header } + } + + func toInternalNode() -> any InternalNode { + switch type { + case .node4: + return Node4(buffer: storage) + case .node16: + return Node16(buffer: storage) + case .node48: + return Node48(buffer: storage) + case .node256: + return Node256(buffer: storage) + default: + assert(false, "leaf nodes are not internal nodes") + } + } + + func toLeafNode() -> NodeLeaf { + assert(type == .leaf) + return NodeLeaf(ptr: storage) + } + + func toManagedNode() -> any ManagedNode { + switch type { + case .leaf: + return toLeafNode() + default: + return toInternalNode() + } + } +} From f9a5452a4326e1db0fd1b10f1982f657dd55acf0 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 19:08:23 -0700 Subject: [PATCH 15/67] Fix deletion when node4/16 is full --- Sources/ARTreeModule/Node16.swift | 7 ++---- Sources/ARTreeModule/Node256.swift | 1 - Sources/ARTreeModule/Node4.swift | 7 ++---- Tests/ARTreeModuleTests/Node4Tests.swift | 29 ++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 11 deletions(-) diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 7a427317e..6878b764c 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -194,12 +194,9 @@ extension Node16: InternalNode { assert(index < Self.numKeys, "index can't >= 16 in Node16") assert(index < count, "not enough childs in node") - let childBuf = child(at: index) - // childBuf?.deallocate() - withBody { keys, childs in - keys[self.count] = 0 - childs[self.count] = nil + keys[index] = 0 + childs[index] = nil count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 11ed447ae..1ff9ac90d 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -130,7 +130,6 @@ extension Node256: InternalNode { public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { return withBody { childs in - // childs[index]?.deallocate() childs[index] = nil count -= 1 diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 095c17caf..06d6e6e9f 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -168,12 +168,9 @@ extension Node4: InternalNode { assert(index < 4, "index can't >= 4 in Node4") assert(index < count, "not enough childs in node") - let childBuf = child(at: index) - // childBuf?.deallocate() - withBody { keys, childs in - keys[self.count] = 0 - childs[self.count] = nil + keys[index] = 0 + childs[index] = nil count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 8b661b425..bbf3851a7 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -75,6 +75,35 @@ final class ARTreeNode4Tests: XCTestCase { XCTAssertEqual(ptr?.type, .leaf) } + func test4DeleteFromFull() throws { + var node = Node4.allocate() + node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: 1, of: Int.self)) + node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: 2, of: Int.self)) + node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: 3, of: Int.self)) + node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: 4, of: Int.self)) + XCTAssertEqual(node.type, .node4) + XCTAssertEqual( + node.print(value: Int.self), + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 1: 1[1] -> 1\n" + + "├──○ 2: 1[2] -> 2\n" + + "├──○ 3: 1[3] -> 3\n" + + "└──○ 4: 1[4] -> 4") + node.deleteChild(at: 1) + XCTAssertEqual( + node.print(value: Int.self), + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 1: 1[1] -> 1\n" + + "├──○ 3: 1[3] -> 3\n" + + "└──○ 4: 1[4] -> 4") + + var ptr: RawNode? = node.rawNode + node.deleteChild(at: 1, ref: &ptr) + node.deleteChild(at: 1, ref: &ptr) + XCTAssertNotNil(ptr) + XCTAssertEqual(ptr?.type, .leaf) + } + func test4ExapandTo16() throws { var node = Node4.allocate() node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1], of: [UInt8].self)) From 4174087d02405ae1b18f3672a4ed726e69155ecf Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 19:45:17 -0700 Subject: [PATCH 16/67] Cleanup some NodeLeaf generic and some warnings --- Sources/ARTreeModule/ARTree+delete.swift | 13 ++++++------- Sources/ARTreeModule/ARTree+insert.swift | 6 +++--- Sources/ARTreeModule/ARTree+utils.swift | 16 ++++++++++++++++ Sources/ARTreeModule/NodeLeaf.swift | 2 -- 4 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 Sources/ARTreeModule/ARTree+utils.swift diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 7639881c5..910a84aab 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -26,24 +26,23 @@ extension ARTree { private mutating func _delete(node: RawNode, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool { - var newDepth = depth - var _node = node - - if _node.type == .leaf { - let leaf: NodeLeaf = _node.toLeafNode() + if node.type == .leaf { + let leaf: NodeLeaf = node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { return false } ref?.pointee = nil - leaf.withValue(of: Value.self) { + _ = leaf.withValue(of: Value.self) { $0.deinitialize(count: 1) } return true } - var node = _node.toInternalNode() + var newDepth = depth + var node = node.toInternalNode() + if node.partialLength > 0 { let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) assert(matchedBytes <= node.partialLength) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index c871e3303..f69cff06c 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -28,7 +28,7 @@ extension ARTree { fatalError("replace not supported") } - let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) + let newLeaf = Self.allocateLeaf(key: key, value: value) var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) var newNode = Node4.allocate() @@ -76,7 +76,7 @@ extension ARTree { node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) node.partialLength -= prefixDiff + 1 - let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) + let newLeaf = Self.allocateLeaf(key: key, value: value) newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) ref?.pointee = newNode.rawNode return true @@ -86,7 +86,7 @@ extension ARTree { // Find next child to continue. guard let next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. - let newLeaf = NodeLeaf.allocate(key: key, value: value, of: Value.self) + let newLeaf = Self.allocateLeaf(key: key, value: value) node.addChild(forKey: key[depth], node: newLeaf, ref: ref) return true } diff --git a/Sources/ARTreeModule/ARTree+utils.swift b/Sources/ARTreeModule/ARTree+utils.swift new file mode 100644 index 000000000..85a518745 --- /dev/null +++ b/Sources/ARTreeModule/ARTree+utils.swift @@ -0,0 +1,16 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 ARTree { + static func allocateLeaf(key: Key, value: Value) -> NodeLeaf { + return NodeLeaf.allocate(key: key, value: value, of: Value.self) + } +} diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 6d789da86..970fb4eed 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -10,8 +10,6 @@ //===----------------------------------------------------------------------===// struct NodeLeaf { - typealias Storage = NodeStorage - var storage: Storage } From accbd63379b0970bf81ce5beb30638eb636ada09 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 18 Aug 2023 23:50:00 -0700 Subject: [PATCH 17/67] Replace unsafe parent updating in delete functions Instead of using UnsafeMutablePointer to locate where parent stores the child pointer everywhere, localize the unsafe behaviour using the `updateChild` method. --- Sources/ARTreeModule/ARTree+delete.swift | 47 +++++++------------ Sources/ARTreeModule/InternalNode+impl.swift | 14 ++---- Sources/ARTreeModule/Node.swift | 38 ++++++++++++++- Sources/ARTreeModule/Node16.swift | 9 ++-- Sources/ARTreeModule/Node256.swift | 6 ++- Sources/ARTreeModule/Node4.swift | 8 ++-- Sources/ARTreeModule/Node48.swift | 10 ++-- .../HashNode/_UnmanagedHashNode.swift | 1 - Tests/ARTreeModuleTests/Node4Tests.swift | 25 ++++++---- 9 files changed, 91 insertions(+), 67 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 910a84aab..9a62a0183 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -10,13 +10,14 @@ //===----------------------------------------------------------------------===// extension ARTree { - public mutating func delete(key: Key) -> Bool { - var ref: ChildSlotPtr? = ChildSlotPtr(&root) - if let node = root { - return _delete(node: node, ref: &ref, key: key, depth: 0) + public mutating func delete(key: Key) { + guard let node = root else { return } + switch _delete(node: node, key: key, depth: 0) { + case .noop: + return + case .replaceWith(let newValue): + root = newValue } - - return false } public mutating func deleteRange(start: Key, end: Key) { @@ -24,20 +25,16 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(node: RawNode, ref: inout ChildSlotPtr?, key: Key, depth: Int) -> Bool - { + private mutating func _delete(node: RawNode, + key: Key, + depth: Int) -> UpdateResult { if node.type == .leaf { - let leaf: NodeLeaf = node.toLeafNode() - + let leaf = node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { - return false + return .noop } - ref?.pointee = nil - _ = leaf.withValue(of: Value.self) { - $0.deinitialize(count: 1) - } - return true + return .replaceWith(nil) } var newDepth = depth @@ -49,21 +46,9 @@ extension ARTree { newDepth += matchedBytes } - guard let childPosition = node.index(forKey: key[newDepth]) else { - // Key not found, nothing to do. - return false + return node.updateChild(forKey: key[newDepth]) { + guard let child = $0 else { return .noop } + return _delete(node: child, key: key, depth: newDepth + 1) } - - var childRef: ChildSlotPtr? - let child = node.child(at: childPosition, ref: &childRef)! - if !_delete(node: child, ref: &childRef, key: key, depth: newDepth + 1) { - return false - } - - let shouldDeleteNode = node.count == 1 - node.deleteChild(at: childPosition, ref: ref) - - // NOTE: node can be invalid because of node shrinking. Hence, we get count before. - return shouldDeleteNode } } diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index 0978319f9..f3fa7c682 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -60,22 +60,14 @@ extension InternalNode { addChild(forKey: k, node: node, ref: ref) } - mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) { + mutating func deleteChild(forKey k: KeyPart) -> UpdateResult { let index = index(forKey: k) assert(index != nil, "trying to delete key that doesn't exist") if index != nil { - deleteChild(at: index!, ref: ref) + return deleteChild(at: index!) } - } - - mutating func deleteChild(forKey k: KeyPart) { - var ptr: RawNode? = RawNode(from:self) - return deleteChild(forKey: k, ref: &ptr) - } - mutating func deleteChild(at index: Index) { - var ptr: RawNode? = RawNode(from: self) - deleteChild(at: index, ref: &ptr) + return .noop } mutating func copyHeader(from: any InternalNode) { diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 993180bb0..165cc3dc4 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -25,6 +25,11 @@ protocol ManagedNode: NodePrettyPrinter { var rawNode: RawNode { get } } +enum UpdateResult { + case noop + case replaceWith(T) +} + protocol InternalNode: ManagedNode { typealias Index = Int typealias Header = InternalNodeHeader @@ -50,12 +55,41 @@ protocol InternalNode: ManagedNode { node: any ManagedNode, ref: ChildSlotPtr?) + mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) + -> UpdateResult + // TODO: Shrinking/expand logic can be moved out. - mutating func deleteChild(forKey k: KeyPart, ref: ChildSlotPtr?) - mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) + mutating func deleteChild(forKey k: KeyPart) -> UpdateResult + mutating func deleteChild(at index: Index) -> UpdateResult } extension ManagedNode { var rawNode: RawNode { RawNode(from: self) } var type: NodeType { Self.type } } + +extension InternalNode { + mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) + -> UpdateResult { + + var ref: ChildSlotPtr? + let child = child(forKey: k, ref: &ref) + switch body(child) { + case .noop: + return .noop + case .replaceWith(nil): + let shouldDeleteMyself = count == 1 + switch deleteChild(forKey: k) { + case .noop: + return .noop + case .replaceWith(nil) where shouldDeleteMyself: + return .replaceWith(nil) + case .replaceWith(let newValue): + return .replaceWith(newValue) + } + case .replaceWith(let newValue): + ref?.pointee = newValue + return .noop + } + } +} diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 6878b764c..b0c743feb 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -190,11 +190,11 @@ extension Node16: InternalNode { } } - mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + mutating func deleteChild(at index: Index) -> UpdateResult { assert(index < Self.numKeys, "index can't >= 16 in Node16") assert(index < count, "not enough childs in node") - withBody { keys, childs in + return withBody { keys, childs in keys[index] = 0 childs[index] = nil @@ -205,9 +205,10 @@ extension Node16: InternalNode { if count == 3 { // Shrink to Node4. let newNode = Node4.allocate(copyFrom: self) - ref?.pointee = RawNode(from: newNode) - // pointer.deallocate() + return .replaceWith(RawNode(from: newNode)) } + + return .noop } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 1ff9ac90d..711b5a01a 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -128,16 +128,18 @@ extension Node256: InternalNode { } } - public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + public mutating func deleteChild(at index: Index) -> UpdateResult { return withBody { childs in childs[index] = nil count -= 1 if count == 40 { let newNode = Node48.allocate(copyFrom: self) - ref?.pointee = RawNode(from: newNode) + return .replaceWith(RawNode(from: newNode)) // pointer.deallocate() } + + return .noop } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 06d6e6e9f..6ac52362a 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -164,11 +164,11 @@ extension Node4: InternalNode { } } - mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { + mutating func deleteChild(at index: Index) -> UpdateResult { assert(index < 4, "index can't >= 4 in Node4") assert(index < count, "not enough childs in node") - withBody { keys, childs in + return withBody { keys, childs in keys[index] = 0 childs[index] = nil @@ -178,8 +178,10 @@ extension Node4: InternalNode { if count == 1 { // Shrink to leaf node. - ref?.pointee = childs[0] + return .replaceWith(childs[0]) } + + return .noop } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index a17bf5309..73c1cb6e6 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -188,8 +188,8 @@ extension Node48: InternalNode { } } - public mutating func deleteChild(at index: Index, ref: ChildSlotPtr?) { - withBody { keys, childs in + public mutating func deleteChild(at index: Index) -> UpdateResult { + return withBody { keys, childs in let targetSlot = Int(keys[index]) assert(targetSlot != 0xFF, "slot is empty already") let targetChildBuf = childs[targetSlot] @@ -218,11 +218,13 @@ extension Node48: InternalNode { count -= 1 // 6. Shrink the node to Node16 if needed. - if count == 13 { // TODO: Should be made tunable. + if count == 13 { let newNode = Node16.allocate(copyFrom: self) - ref?.pointee = RawNode(from: newNode) + return .replaceWith(RawNode(from: newNode)) // pointer.deallocate() } + + return .noop } } diff --git a/Sources/HashTreeCollections/HashNode/_UnmanagedHashNode.swift b/Sources/HashTreeCollections/HashNode/_UnmanagedHashNode.swift index 31d223460..8dacdb887 100644 --- a/Sources/HashTreeCollections/HashNode/_UnmanagedHashNode.swift +++ b/Sources/HashTreeCollections/HashNode/_UnmanagedHashNode.swift @@ -106,4 +106,3 @@ extension _UnmanagedHashNode { } } } - diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index bbf3851a7..9eff68fff 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -13,6 +13,17 @@ import XCTest @testable import ARTreeModule +extension InternalNode { + mutating func deleteChildReturn(at idx: Index) -> (any ManagedNode)? { + switch deleteChild(at: idx) { + case .noop: + return self + case .replaceWith(let newValue): + return newValue?.toManagedNode() + } + } +} + final class ARTreeNode4Tests: XCTestCase { func test4Basic() throws { var node = Node4.allocate() @@ -69,10 +80,8 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - var ptr: RawNode? = node.rawNode - node.deleteChild(at: 1, ref: &ptr) - XCTAssertNotNil(ptr) - XCTAssertEqual(ptr?.type, .leaf) + let newNode = node.deleteChildReturn(at: 1) + XCTAssertEqual(newNode?.type, .leaf) } func test4DeleteFromFull() throws { @@ -97,11 +106,9 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") - var ptr: RawNode? = node.rawNode - node.deleteChild(at: 1, ref: &ptr) - node.deleteChild(at: 1, ref: &ptr) - XCTAssertNotNil(ptr) - XCTAssertEqual(ptr?.type, .leaf) + node.deleteChild(at: 1) + let newNode = node.deleteChildReturn(at: 1) + XCTAssertEqual(newNode?.type, .leaf) } func test4ExapandTo16() throws { From 8ade1e4f712c3f8fff2f2a38a9b799d4ada0c11b Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 00:33:13 -0700 Subject: [PATCH 18/67] Replace unsafe update of parent in insertion --- Sources/ARTreeModule/ARTree+insert.swift | 6 ++-- Sources/ARTreeModule/InternalNode+impl.swift | 5 --- Sources/ARTreeModule/Node.swift | 21 ++++-------- Sources/ARTreeModule/Node16.swift | 9 ++---- Sources/ARTreeModule/Node256.swift | 7 ++-- Sources/ARTreeModule/Node4.swift | 9 ++---- Sources/ARTreeModule/Node48.swift | 9 ++---- Tests/ARTreeModuleTests/Node4Tests.swift | 34 +++++++++++--------- 8 files changed, 39 insertions(+), 61 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index f69cff06c..04e630708 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -72,12 +72,12 @@ extension ARTree { assert( node.partialLength <= Const.maxPartialLength, "partial length is always bounded") - newNode.addChild(forKey: node.partialBytes[prefixDiff], node: node) + _ = newNode.addChild(forKey: node.partialBytes[prefixDiff], node: node) node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) node.partialLength -= prefixDiff + 1 let newLeaf = Self.allocateLeaf(key: key, value: value) - newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) + _ = newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) ref?.pointee = newNode.rawNode return true } @@ -87,7 +87,7 @@ extension ARTree { guard let next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. let newLeaf = Self.allocateLeaf(key: key, value: value) - node.addChild(forKey: key[depth], node: newLeaf, ref: ref) + node.addChild(forKey: key[depth], node: newLeaf) return true } diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index f3fa7c682..78039e8ad 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -55,11 +55,6 @@ extension InternalNode { return child(forKey: k, ref: &ref) } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) { - let ref = UnsafeMutablePointer(nil) - addChild(forKey: k, node: node, ref: ref) - } - mutating func deleteChild(forKey k: KeyPart) -> UpdateResult { let index = index(forKey: k) assert(index != nil, "trying to delete key that doesn't exist") diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 165cc3dc4..e5e0fd227 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -25,11 +25,6 @@ protocol ManagedNode: NodePrettyPrinter { var rawNode: RawNode { get } } -enum UpdateResult { - case noop - case replaceWith(T) -} - protocol InternalNode: ManagedNode { typealias Index = Int typealias Header = InternalNodeHeader @@ -49,16 +44,7 @@ protocol InternalNode: ManagedNode { func child(at: Index) -> RawNode? func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) - mutating func addChild( - forKey k: KeyPart, - node: any ManagedNode, - ref: ChildSlotPtr?) - - mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) - -> UpdateResult - - // TODO: Shrinking/expand logic can be moved out. + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult mutating func deleteChild(forKey k: KeyPart) -> UpdateResult mutating func deleteChild(at index: Index) -> UpdateResult } @@ -68,6 +54,11 @@ extension ManagedNode { var type: NodeType { Self.type } } +enum UpdateResult { + case noop + case replaceWith(T) +} + extension InternalNode { mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) -> UpdateResult { diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index b0c743feb..5c6d59719 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -168,11 +168,7 @@ extension Node16: InternalNode { } } - mutating func addChild( - forKey k: KeyPart, - node: any ManagedNode, - ref: ChildSlotPtr? - ) { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody {keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") @@ -182,10 +178,11 @@ extension Node16: InternalNode { childs[slot] = node.rawNode count += 1 } + return .noop } else { var newNode = Node48.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = RawNode(from: newNode) + return .replaceWith(RawNode(from: newNode)) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 711b5a01a..af75a8f80 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -116,15 +116,12 @@ extension Node256: InternalNode { } } - mutating func addChild( - forKey k: KeyPart, - node: any ManagedNode, - ref: ChildSlotPtr? - ) { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { return withBody { childs in assert(childs[Int(k)] == nil, "node for key \(k) already exists") childs[Int(k)] = node.rawNode count += 1 + return .noop } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 6ac52362a..4d0b869f0 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -142,11 +142,7 @@ extension Node4: InternalNode { } } - mutating func addChild( - forKey k: KeyPart, - node: any ManagedNode, - ref: ChildSlotPtr? - ) { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody { keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") @@ -156,10 +152,11 @@ extension Node4: InternalNode { childs[slot] = node.rawNode count += 1 } + return .noop } else { var newNode = Node16.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = RawNode(from: newNode) + return .replaceWith(RawNode(from: newNode)) // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 73c1cb6e6..c7a0fa291 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -161,11 +161,7 @@ extension Node48: InternalNode { } } - mutating func addChild( - forKey k: KeyPart, - node: any ManagedNode, - ref: ChildSlotPtr? - ) { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if count < Self.numKeys { withBody { keys, childs in assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") @@ -180,10 +176,11 @@ extension Node48: InternalNode { } self.count += 1 + return .noop } else { var newNode = Node256.allocate(copyFrom: self) newNode.addChild(forKey: k, node: node) - ref?.pointee = RawNode(from: newNode) + return .replaceWith(RawNode(from: newNode)) // pointer.deallocate() } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 9eff68fff..e5f44c36d 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -14,6 +14,15 @@ import XCTest @testable import ARTreeModule extension InternalNode { + mutating func addChildReturn(forKey k: KeyPart, node: any ManagedNode) -> (any ManagedNode)? { + switch addChild(forKey: k, node: node) { + case .noop: + return self + case .replaceWith(let newValue): + return newValue?.toManagedNode() + } + } + mutating func deleteChildReturn(at idx: Index) -> (any ManagedNode)? { switch deleteChild(at: idx) { case .noop: @@ -125,21 +134,16 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - var addr: RawNode? = node.rawNode - withUnsafeMutablePointer(to: &addr) { - let ref: ChildSlotPtr? = $0 - node.addChild(forKey: 5, - node: NodeLeaf.allocate(key: [5], value: [5], of: [UInt8].self), - ref: ref) - XCTAssertEqual( - ref!.pointee!.print(value: [UInt8].self), - "○ Node16 {childs=5, partial=[]}\n" + - "├──○ 1: 1[1] -> [1]\n" + - "├──○ 2: 1[2] -> [2]\n" + - "├──○ 3: 1[3] -> [3]\n" + - "├──○ 4: 1[4] -> [4]\n" + - "└──○ 5: 1[5] -> [5]") - } + let newNode = node.addChildReturn( + forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5], of: [UInt8].self)) + XCTAssertEqual( + newNode!.print(value: [UInt8].self), + "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: 1[1] -> [1]\n" + + "├──○ 2: 1[2] -> [2]\n" + + "├──○ 3: 1[3] -> [3]\n" + + "├──○ 4: 1[4] -> [4]\n" + + "└──○ 5: 1[5] -> [5]") } func test4DeleteKey() throws { From 8634916b5a15e82660ee3ef2b6fd48d1b68a3083 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 01:04:34 -0700 Subject: [PATCH 19/67] Fix warnings --- Sources/ARTreeModule/ARTree+insert.swift | 7 +-- Sources/ARTreeModule/Node+Storage.swift | 2 +- Sources/ARTreeModule/Node16.swift | 3 +- Sources/ARTreeModule/Node256.swift | 3 +- Sources/ARTreeModule/Node4.swift | 3 +- Sources/ARTreeModule/Node48.swift | 7 +-- Tests/ARTreeModuleTests/DeleteTests.swift | 34 ++++++------- Tests/ARTreeModuleTests/Node16Tests.swift | 36 ++++++------- Tests/ARTreeModuleTests/Node256Tests.swift | 16 +++--- Tests/ARTreeModuleTests/Node48Tests.swift | 16 +++--- Tests/ARTreeModuleTests/Node4Tests.swift | 56 ++++++++++----------- Tests/ARTreeModuleTests/SequenceTests.swift | 4 +- 12 files changed, 90 insertions(+), 97 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 04e630708..77e82eab3 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// extension ARTree { + @discardableResult public mutating func insert(key: Key, value: Value) -> Bool { var current: RawNode = root! @@ -32,8 +33,8 @@ extension ARTree { var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) var newNode = Node4.allocate() - newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) - newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) + _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) + _ = newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) while longestPrefix > 0 { let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) @@ -47,7 +48,7 @@ extension ARTree { } var nextNode = Node4.allocate() - nextNode.addChild(forKey: key[start - 1], node: newNode) + _ = nextNode.addChild(forKey: key[start - 1], node: newNode) newNode = nextNode } diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index 76c08b83c..846ac8220 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -51,7 +51,7 @@ extension NodeStorage where Mn: InternalNode { let size = Mn.size let buf = NodeStorage.create(type: Mn.type, size: size) let storage = NodeStorage(raw: buf) - buf.withUnsafeMutablePointerToElements { + _ = buf.withUnsafeMutablePointerToElements { UnsafeMutableRawPointer($0).bindMemory(to: Header.self, capacity: 1) } return storage diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 5c6d59719..e8a3f2a1e 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -181,9 +181,8 @@ extension Node16: InternalNode { return .noop } else { var newNode = Node48.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) + _ = newNode.addChild(forKey: k, node: node) return .replaceWith(RawNode(from: newNode)) - // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index af75a8f80..1ab6aa56d 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -26,7 +26,7 @@ extension Node256 { static func allocate() -> Node256 { let buf: NodeStorage = NodeStorage.allocate() let node = Self(storage: buf) - node.withBody { childs in + _ = node.withBody { childs in UnsafeMutableRawPointer(childs.baseAddress!) .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } @@ -133,7 +133,6 @@ extension Node256: InternalNode { if count == 40 { let newNode = Node48.allocate(copyFrom: self) return .replaceWith(RawNode(from: newNode)) - // pointer.deallocate() } return .noop diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 4d0b869f0..f5a56a92d 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -155,9 +155,8 @@ extension Node4: InternalNode { return .noop } else { var newNode = Node16.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) + _ = newNode.addChild(forKey: k, node: node) return .replaceWith(RawNode(from: newNode)) - // pointer.deallocate() } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index c7a0fa291..21080e9d6 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -179,9 +179,8 @@ extension Node48: InternalNode { return .noop } else { var newNode = Node256.allocate(copyFrom: self) - newNode.addChild(forKey: k, node: node) + _ = newNode.addChild(forKey: k, node: node) return .replaceWith(RawNode(from: newNode)) - // pointer.deallocate() } } @@ -189,9 +188,6 @@ extension Node48: InternalNode { return withBody { keys, childs in let targetSlot = Int(keys[index]) assert(targetSlot != 0xFF, "slot is empty already") - let targetChildBuf = childs[targetSlot] - // targetChildBuf?.deallocate() - // 1. Find out who has the last slot. var lastSlotKey = 0 for k in 0..<256 { @@ -218,7 +214,6 @@ extension Node48: InternalNode { if count == 13 { let newNode = Node16.allocate(copyFrom: self) return .replaceWith(RawNode(from: newNode)) - // pointer.deallocate() } return .noop diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 56e6322cf..d98ecbc20 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -86,23 +86,23 @@ final class ARTreeDeleteTests: XCTestCase { "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") } - // func testDeleteCompressToLeaf() throws { - // var t = ARTree<[UInt8]>() - // t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) - // t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) - // t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) - // t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) - // t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) - // t.delete(key: [1, 2, 3, 4, 5, 6]) - // t.delete(key: [1, 2, 3, 4, 8, 9]) - // XCTAssertEqual( - // t.description, - // "○ Node4 {childs=2, partial=[]}\n" + - // "├──○ 1: Node4 {childs=2, partial=[2]}\n" + - // "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + - // "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + - // "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") - // } + func testDeleteCompressToLeaf() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) + t.insert(key: [1, 2, 4, 5, 6, 7], value: [3]) + t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) + t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) + t.delete(key: [1, 2, 3, 4, 5, 6]) + t.delete(key: [1, 2, 3, 4, 8, 9]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + + "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + } func testDeleteCompressToNode4() throws { var t = ARTree<[UInt8]>() diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index 00f46fc14..fd134b1fa 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode16Tests: XCTestCase { func test16Basic() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=2, partial=[]}\n" + @@ -27,10 +27,10 @@ final class ARTreeNode16Tests: XCTestCase { func test4AddInMiddle() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) - node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=4, partial=[]}\n" + @@ -42,53 +42,53 @@ final class ARTreeNode16Tests: XCTestCase { func test16DeleteAtIndex() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 0) + _ = node.deleteChild(at: 0) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 1) + _ = node.deleteChild(at: 1) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") - node.deleteChild(at: 0) + _ = node.deleteChild(at: 0) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") } func test16DeleteKey() throws { var node = Node16.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 10) + _ = node.deleteChild(forKey: 10) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 15) + _ = node.deleteChild(forKey: 15) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 20) + _ = node.deleteChild(forKey: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index d7d578f3e..66961a4e8 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode256Tests: XCTestCase { func test256Basic() throws { var node = Node256.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=2, partial=[]}\n" + @@ -27,27 +27,27 @@ final class ARTreeNode256Tests: XCTestCase { func test48DeleteAtIndex() throws { var node = Node256.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 10) + _ = node.deleteChild(at: 10) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 15) + _ = node.deleteChild(at: 15) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 20) + _ = node.deleteChild(at: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node256 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index b2b1e0a18..a4c5bccbe 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -16,8 +16,8 @@ import XCTest final class ARTreeNode48Tests: XCTestCase { func test48Basic() throws { var node = Node48.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=2, partial=[]}\n" + @@ -27,27 +27,27 @@ final class ARTreeNode48Tests: XCTestCase { func test48DeleteAtIndex() throws { var node = Node48.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 10) + _ = node.deleteChild(at: 10) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 15) + _ = node.deleteChild(at: 15) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 20) + _ = node.deleteChild(at: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node48 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index e5f44c36d..4a7a5e724 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -36,8 +36,8 @@ extension InternalNode { final class ARTreeNode4Tests: XCTestCase { func test4Basic() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + @@ -47,8 +47,8 @@ final class ARTreeNode4Tests: XCTestCase { func test4BasicInt() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: 11, of: Int.self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: 22, of: Int.self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: 11, of: Int.self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: 22, of: Int.self)) XCTAssertEqual( node.print(value: Int.self), "○ Node4 {childs=2, partial=[]}\n" + @@ -58,10 +58,10 @@ final class ARTreeNode4Tests: XCTestCase { func test4AddInMiddle() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) - node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=4, partial=[]}\n" + @@ -73,16 +73,16 @@ final class ARTreeNode4Tests: XCTestCase { func test4DeleteAtIndex() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(at: 0) + _ = node.deleteChild(at: 0) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + @@ -95,10 +95,10 @@ final class ARTreeNode4Tests: XCTestCase { func test4DeleteFromFull() throws { var node = Node4.allocate() - node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: 1, of: Int.self)) - node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: 2, of: Int.self)) - node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: 3, of: Int.self)) - node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: 4, of: Int.self)) + _ = node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: 1, of: Int.self)) + _ = node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: 2, of: Int.self)) + _ = node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: 3, of: Int.self)) + _ = node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: 4, of: Int.self)) XCTAssertEqual(node.type, .node4) XCTAssertEqual( node.print(value: Int.self), @@ -107,7 +107,7 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 2: 1[2] -> 2\n" + "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") - node.deleteChild(at: 1) + _ = node.deleteChild(at: 1) XCTAssertEqual( node.print(value: Int.self), "○ Node4 {childs=3, partial=[]}\n" + @@ -115,17 +115,17 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") - node.deleteChild(at: 1) + _ = node.deleteChild(at: 1) let newNode = node.deleteChildReturn(at: 1) XCTAssertEqual(newNode?.type, .leaf) } func test4ExapandTo16() throws { var node = Node4.allocate() - node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1], of: [UInt8].self)) - node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2], of: [UInt8].self)) - node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3], of: [UInt8].self)) - node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4], of: [UInt8].self)) + _ = node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=4, partial=[]}\n" + @@ -148,27 +148,27 @@ final class ARTreeNode4Tests: XCTestCase { func test4DeleteKey() throws { var node = Node4.allocate() - node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) + _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) + _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 10) + _ = node.deleteChild(forKey: 10) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 15) + _ = node.deleteChild(forKey: 15) XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - node.deleteChild(forKey: 20) + _ = node.deleteChild(forKey: 20) XCTAssertEqual(node.print(value: [UInt8].self), "○ Node4 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift index 2e4d49ad0..61904e89f 100644 --- a/Tests/ARTreeModuleTests/SequenceTests.swift +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -15,9 +15,9 @@ import XCTest final class ARTreeSequenceTests: XCTestCase { func testSequenceEmpty() throws { - var t = ARTree<[UInt8]>() + let t = ARTree<[UInt8]>() var total = 0 - for (k, v) in t { + for (_, _) in t { total += 1 } XCTAssertEqual(total, 0) From 79e1966f969b450fffa1250b09092287a34d18eb Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 01:04:41 -0700 Subject: [PATCH 20/67] Make insert work, but still using unsafe ChildSlotPtr --- Sources/ARTreeModule/ARTree+insert.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 77e82eab3..311171d68 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -88,7 +88,9 @@ extension ARTree { guard let next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. let newLeaf = Self.allocateLeaf(key: key, value: value) - node.addChild(forKey: key[depth], node: newLeaf) + if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { + ref?.pointee = newNode + } return true } From 9d313d1f360ec7dae42ae5c38943efcd878c5f7c Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 01:23:52 -0700 Subject: [PATCH 21/67] Removed unused `child(at: .., ref: ...)` methods --- Sources/ARTreeModule/Node.swift | 1 - Sources/ARTreeModule/Node16.swift | 10 ---------- Sources/ARTreeModule/Node256.swift | 8 -------- Sources/ARTreeModule/Node4.swift | 10 ---------- Sources/ARTreeModule/Node48.swift | 8 -------- 5 files changed, 37 deletions(-) diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index e5e0fd227..817996150 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -42,7 +42,6 @@ protocol InternalNode: ManagedNode { func child(forKey k: KeyPart) -> RawNode? func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? func child(at: Index) -> RawNode? - func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult mutating func deleteChild(forKey k: KeyPart) -> UpdateResult diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index e8a3f2a1e..0d91d3ee2 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -158,16 +158,6 @@ extension Node16: InternalNode { } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { - assert( - index < Self.numKeys, - "maximum \(Self.numKeys) childs allowed, given index = \(index)") - return withBody { _, childs in - ref = childs.baseAddress! + index - return childs[index] - } - } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody {keys, childs in diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 1ab6aa56d..188faedd5 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -108,14 +108,6 @@ extension Node256: InternalNode { } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { - assert(index < 256, "maximum 256 childs allowed") - return withBody { childs in - ref = childs.baseAddress! + index - return childs[index] - } - } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { return withBody { childs in assert(childs[Int(k)] == nil, "node for key \(k) already exists") diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index f5a56a92d..2ab5544b4 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -132,16 +132,6 @@ extension Node4: InternalNode { } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { - assert( - index < Self.numKeys, - "maximum \(Self.numKeys) childs allowed, given index = \(index)") - return withBody { _, childs in - ref = childs.baseAddress! + index - return childs[index] - } - } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody { keys, childs in diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 21080e9d6..d5518c1b8 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -153,14 +153,6 @@ extension Node48: InternalNode { } } - func child(at index: Index, ref: inout ChildSlotPtr?) -> RawNode? { - assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return withBody { _, childs in - ref = childs.baseAddress! + index - return childs[index] - } - } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if count < Self.numKeys { withBody { keys, childs in From 5d76b253876f95c87772484394c976373d668e08 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 15:16:57 -0700 Subject: [PATCH 22/67] Don't use ChildSlotPtr out as InternalNode method params directly --- Sources/ARTreeModule/ARTree+insert.swift | 8 ++--- Sources/ARTreeModule/InternalNode+impl.swift | 13 +------- .../ARTreeModule/Node+StringConverter.swift | 4 +-- Sources/ARTreeModule/Node.swift | 32 ++++++++++++++----- Sources/ARTreeModule/Node16.swift | 19 +++++------ Sources/ARTreeModule/Node256.swift | 15 +++++---- Sources/ARTreeModule/Node4.swift | 19 +++++------ Sources/ARTreeModule/Node48.swift | 20 +++++------- Tests/ARTreeModuleTests/Node16Tests.swift | 6 ++-- Tests/ARTreeModuleTests/Node4Tests.swift | 6 ++-- 10 files changed, 69 insertions(+), 73 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 311171d68..45b3e61e9 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -17,7 +17,7 @@ extension ARTree { // Location of child (current) pointer in parent, i.e. memory address where the // address of current node is stored inside the parent node. // TODO: Fix warning here? - var ref: ChildSlotPtr? = ChildSlotPtr(&root) + var ref = UnsafeMutablePointer(&root) var depth = 0 while depth < key.count { @@ -52,7 +52,7 @@ extension ARTree { newNode = nextNode } - ref?.pointee = RawNode(from: newNode) // Replace child in parent. + ref.pointee = RawNode(from: newNode) // Replace child in parent. return true } @@ -79,7 +79,7 @@ extension ARTree { let newLeaf = Self.allocateLeaf(key: key, value: value) _ = newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) - ref?.pointee = newNode.rawNode + ref.pointee = newNode.rawNode return true } } @@ -89,7 +89,7 @@ extension ARTree { // No child, insert leaf within us. let newLeaf = Self.allocateLeaf(key: key, value: value) if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { - ref?.pointee = newNode + ref.pointee = newNode } return true } diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index 78039e8ad..ddf62b42c 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -51,18 +51,7 @@ extension InternalNode { } func child(forKey k: KeyPart) -> RawNode? { - var ref = UnsafeMutablePointer(nil) - return child(forKey: k, ref: &ref) - } - - mutating func deleteChild(forKey k: KeyPart) -> UpdateResult { - let index = index(forKey: k) - assert(index != nil, "trying to delete key that doesn't exist") - if index != nil { - return deleteChild(at: index!) - } - - return .noop + return index(forKey: k).flatMap { child(at: $0) } } mutating func copyHeader(from: any InternalNode) { diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index e46d89a27..892d666c4 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -73,7 +73,7 @@ extension Node4: NodePrettyPrinter { let last = idx == count - 1 output += indent(depth, last: last) output += String(key) + ": " - output += child(forKey: key)!.prettyPrint( + output += child(at: idx)!.prettyPrint( depth: depth + 1, value: value) if !last { @@ -95,7 +95,7 @@ extension Node16: NodePrettyPrinter { let last = idx == count - 1 output += indent(depth, last: last) output += String(key) + ": " - output += child(forKey: key)!.prettyPrint( + output += child(at: idx)!.prettyPrint( depth: depth + 1, value: value) if !last { diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 817996150..a4260b990 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -12,10 +12,9 @@ public typealias KeyPart = UInt8 public typealias Key = [KeyPart] -typealias ChildSlotPtr = UnsafeMutablePointer - protocol ManagedNode: NodePrettyPrinter { typealias Storage = NodeStorage + typealias ChildSlotPtr = UnsafeMutablePointer static func deinitialize(_ storage: NodeStorage) static var type: NodeType { get } @@ -40,12 +39,12 @@ protocol InternalNode: ManagedNode { func next(index: Index) -> Index? func child(forKey k: KeyPart) -> RawNode? - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? func child(at: Index) -> RawNode? mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult - mutating func deleteChild(forKey k: KeyPart) -> UpdateResult mutating func deleteChild(at index: Index) -> UpdateResult + + mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R } extension ManagedNode { @@ -59,17 +58,34 @@ enum UpdateResult { } extension InternalNode { + mutating func child(forKey k: KeyPart, ref: inout ChildSlotPtr) -> RawNode? { + if count == 0 { + return nil + } + + return index(forKey: k).flatMap { index in + return withChildRef(at: index) { _ref in + ref = _ref + return ref.pointee + } + } + } + mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) -> UpdateResult { - var ref: ChildSlotPtr? - let child = child(forKey: k, ref: &ref) + guard let childPosition = index(forKey: k) else { + return .noop + } + + let ref = withChildRef(at: childPosition) { $0 } + let child = child(at: childPosition) switch body(child) { case .noop: return .noop case .replaceWith(nil): let shouldDeleteMyself = count == 1 - switch deleteChild(forKey: k) { + switch deleteChild(at: childPosition) { case .noop: return .noop case .replaceWith(nil) where shouldDeleteMyself: @@ -78,7 +94,7 @@ extension InternalNode { return .replaceWith(newValue) } case .replaceWith(let newValue): - ref?.pointee = newValue + ref.pointee = newValue return .noop } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 0d91d3ee2..1f7d5c547 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -140,17 +140,6 @@ extension Node16: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { - guard let index = index(forKey: k) else { - return nil - } - - return withBody {_, childs in - ref = childs.baseAddress! + index - return child(at: index) - } - } - func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") return withBody { _, childs in @@ -198,6 +187,14 @@ extension Node16: InternalNode { } } + mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + assert(index < count, "not enough childs in node") + return withBody {_, childs in + let ref = childs.baseAddress! + index + return body(ref) + } + } + static func deinitialize(_ storage: NodeStorage) { } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 188faedd5..8776450f7 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -94,13 +94,6 @@ extension Node256: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { - return withBody { childs in - ref = childs.baseAddress! + Int(k) - return childs[Int(k)] - } - } - func child(at: Int) -> RawNode? { assert(at < 256, "maximum 256 childs allowed") return withBody { childs in @@ -131,6 +124,14 @@ extension Node256: InternalNode { } } + mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + assert(index < count, "not enough childs in node") + return withBody { childs in + let ref = childs.baseAddress! + index + return body(ref) + } + } + static func deinitialize(_ storage: NodeStorage) { } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 2ab5544b4..0bd33d314 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -114,17 +114,6 @@ extension Node4: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { - guard let index = index(forKey: k) else { - return nil - } - - return withBody {_, childs in - ref = childs.baseAddress! + index - return child(at: index) - } - } - func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") return withBody { _, childs in @@ -171,6 +160,14 @@ extension Node4: InternalNode { } } + mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + assert(index < count, "index=\(index) less than count=\(count)") + return withBody {_, childs in + let ref = childs.baseAddress! + index + return body(ref) + } + } + static func deinitialize(_ storage: NodeStorage) { } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index d5518c1b8..a5fa693f3 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -134,18 +134,6 @@ extension Node48: InternalNode { } } - func child(forKey k: KeyPart, ref: inout ChildSlotPtr?) -> RawNode? { - return withBody { keys, childs in - let childIndex = Int(keys[Int(k)]) - if childIndex == 0xFF { - return nil - } - - ref = childs.baseAddress! + childIndex - return child(at: childIndex) - } - } - func child(at: Int) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") return withBody { _, childs in @@ -224,6 +212,14 @@ extension Node48: InternalNode { } } + mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + assert(index < count, "not enough childs in node") + return withBody {_, childs in + let ref = childs.baseAddress! + index + return body(ref) + } + } + static func deinitialize(_ storage: NodeStorage) { } } diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index fd134b1fa..1b70e1866 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -77,18 +77,18 @@ final class ARTreeNode16Tests: XCTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 10) + _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 15) + _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( node.print(value: [UInt8].self), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 20) + _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 4a7a5e724..1cf384214 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -157,18 +157,18 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 10) + _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 15) + _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( node.print(value: [UInt8].self), "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(forKey: 20) + _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } XCTAssertEqual(node.print(value: [UInt8].self), "○ Node4 {childs=0, partial=[]}\n") } } From 1ce7b8fabbcb9305d8c0a307ec42fbed98cd7adf Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 23:25:31 -0700 Subject: [PATCH 23/67] Break ARTree.insert into two parts --- Sources/ARTreeModule/ARTree+insert.swift | 129 +++++++++++++--------- Tests/ARTreeModuleTests/InsertTests.swift | 1 - 2 files changed, 75 insertions(+), 55 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 45b3e61e9..3af40fb89 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -10,50 +10,90 @@ //===----------------------------------------------------------------------===// extension ARTree { + fileprivate enum InsertAction { + case replace(NodeLeaf) + case splitLeaf(NodeLeaf, depth: Int) + case splitNode(any InternalNode, depth: Int, prefixDiff: Int) + case insertInto(any InternalNode, depth: Int) + } + @discardableResult public mutating func insert(key: Key, value: Value) -> Bool { - var current: RawNode = root! + guard let (action, ref) = _findInsertNode(startNode: root!, key: key) else { return false } - // Location of child (current) pointer in parent, i.e. memory address where the - // address of current node is stored inside the parent node. - // TODO: Fix warning here? - var ref = UnsafeMutablePointer(&root) + switch action { + case .replace(var _): + fatalError("replace not supported") - var depth = 0 - while depth < key.count { - // Reached leaf already, replace it with a new node, or update the existing value. - if current.type == .leaf { - let leaf: NodeLeaf = current.toLeafNode() - if leaf.keyEquals(with: key) { - //TODO: Replace value. - fatalError("replace not supported") + case .splitLeaf(let leaf, let depth): + let newLeaf = Self.allocateLeaf(key: key, value: value) + var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) + + var newNode = Node4.allocate() + _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) + _ = newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) + + while longestPrefix > 0 { + let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) + let start = depth + longestPrefix - nBytes + newNode.partialLength = nBytes + newNode.partialBytes.copy(src: key[...], start: start, count: nBytes) + longestPrefix -= nBytes + 1 + + if longestPrefix <= 0 { + break } - let newLeaf = Self.allocateLeaf(key: key, value: value) - var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) + var nextNode = Node4.allocate() + _ = nextNode.addChild(forKey: key[start - 1], node: newNode) + newNode = nextNode + } + + ref.pointee = RawNode(from: newNode) // Replace child in parent. - var newNode = Node4.allocate() - _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) - _ = newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) + case .splitNode(var node, let depth, let prefixDiff): + var newNode = Node4.allocate() + newNode.partialLength = prefixDiff + // TODO: Just copy min(maxPartialLength, prefixDiff) bytes + newNode.partialBytes = node.partialBytes - while longestPrefix > 0 { - let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) - let start = depth + longestPrefix - nBytes - newNode.partialLength = nBytes - newNode.partialBytes.copy(src: key[...], start: start, count: nBytes) - longestPrefix -= nBytes + 1 + assert( + node.partialLength <= Const.maxPartialLength, + "partial length is always bounded") + _ = newNode.addChild(forKey: node.partialBytes[prefixDiff], node: node) + node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) + node.partialLength -= prefixDiff + 1 - if longestPrefix <= 0 { - break - } + let newLeaf = Self.allocateLeaf(key: key, value: value) + _ = newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) + ref.pointee = newNode.rawNode - var nextNode = Node4.allocate() - _ = nextNode.addChild(forKey: key[start - 1], node: newNode) - newNode = nextNode + case .insertInto(var node, let depth): + let newLeaf = Self.allocateLeaf(key: key, value: value) + if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { + ref.pointee = newNode + } + } + + return true + } + + fileprivate mutating func _findInsertNode(startNode: RawNode, key: Key) + -> (InsertAction, InternalNode.ChildSlotPtr)? { + + var current: RawNode = startNode + var depth = 0 + var ref = UnsafeMutablePointer(&root) + + while depth < key.count { + // Reached leaf already, replace it with a new node, or update the existing value. + if current.type == .leaf { + let leaf: NodeLeaf = current.toLeafNode() + if leaf.keyEquals(with: key) { + return (.replace(leaf), ref) } - ref.pointee = RawNode(from: newNode) // Replace child in parent. - return true + return (.splitLeaf(leaf, depth: depth), ref) } var node = current.toInternalNode() @@ -65,39 +105,20 @@ extension ARTree { depth += partialLength } else { // Incomplete match with partial bytes, hence needs splitting. - var newNode = Node4.allocate() - newNode.partialLength = prefixDiff - // TODO: Just copy min(maxPartialLength, prefixDiff) bytes - newNode.partialBytes = node.partialBytes - - assert( - node.partialLength <= Const.maxPartialLength, - "partial length is always bounded") - _ = newNode.addChild(forKey: node.partialBytes[prefixDiff], node: node) - node.partialBytes.shiftLeft(toIndex: prefixDiff + 1) - node.partialLength -= prefixDiff + 1 - - let newLeaf = Self.allocateLeaf(key: key, value: value) - _ = newNode.addChild(forKey: key[depth + prefixDiff], node: newLeaf) - ref.pointee = newNode.rawNode - return true + return (.splitNode(node, depth: depth, prefixDiff: prefixDiff), ref) } } // Find next child to continue. guard let next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. - let newLeaf = Self.allocateLeaf(key: key, value: value) - if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { - ref.pointee = newNode - } - return true + return (.insertInto(node, depth: depth), ref) } depth += 1 current = next } - return false + return nil } } diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 12b03eebb..38bc3b6d5 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -320,7 +320,6 @@ final class ARTreeInsertTests: XCTestCase { var total = 0 for (k, v)in tree { total += 1 - print("<--->", k, v) } XCTAssertEqual(total, testCase.count) XCTAssertEqual(tree.root?.type, .node4) From 4d5a9c90c90dcc81d66dec7944b16830550864a4 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sat, 19 Aug 2023 23:39:07 -0700 Subject: [PATCH 24/67] Use Node.rawNode instead of constructor --- Sources/ARTreeModule/ARTree+insert.swift | 2 +- Sources/ARTreeModule/ARTree.swift | 2 +- Sources/ARTreeModule/Node16.swift | 4 ++-- Sources/ARTreeModule/Node256.swift | 2 +- Sources/ARTreeModule/Node4.swift | 2 +- Sources/ARTreeModule/Node48.swift | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 3af40fb89..c9eb20d54 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -49,7 +49,7 @@ extension ARTree { newNode = nextNode } - ref.pointee = RawNode(from: newNode) // Replace child in parent. + ref.pointee = newNode.rawNode // Replace child in parent. case .splitNode(var node, let depth, let prefixDiff): var newNode = Node4.allocate() diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index e8dc5ac70..35cdc54ee 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -24,6 +24,6 @@ public struct ARTree { var root: RawNode? public init() { - self.root = RawNode(from: Node4.allocate()) + self.root = Node4.allocate().rawNode } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 1f7d5c547..fcd955174 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -161,7 +161,7 @@ extension Node16: InternalNode { } else { var newNode = Node48.allocate(copyFrom: self) _ = newNode.addChild(forKey: k, node: node) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } } @@ -180,7 +180,7 @@ extension Node16: InternalNode { if count == 3 { // Shrink to Node4. let newNode = Node4.allocate(copyFrom: self) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } return .noop diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 8776450f7..7a604d392 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -117,7 +117,7 @@ extension Node256: InternalNode { if count == 40 { let newNode = Node48.allocate(copyFrom: self) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } return .noop diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 0bd33d314..a83ea791c 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -135,7 +135,7 @@ extension Node4: InternalNode { } else { var newNode = Node16.allocate(copyFrom: self) _ = newNode.addChild(forKey: k, node: node) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } } diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index a5fa693f3..2cfed919f 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -160,7 +160,7 @@ extension Node48: InternalNode { } else { var newNode = Node256.allocate(copyFrom: self) _ = newNode.addChild(forKey: k, node: node) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } } @@ -193,7 +193,7 @@ extension Node48: InternalNode { // 6. Shrink the node to Node16 if needed. if count == 13 { let newNode = Node16.allocate(copyFrom: self) - return .replaceWith(RawNode(from: newNode)) + return .replaceWith(newNode.rawNode) } return .noop From dd52ccb116fae9f82083cbc19d73862b34451366 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sun, 20 Aug 2023 11:42:53 -0700 Subject: [PATCH 25/67] Add NodeReference type to avoid exposing pointer directly --- Sources/ARTreeModule/ARTree+insert.swift | 6 +++--- Sources/ARTreeModule/Node.swift | 25 ++++++++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index c9eb20d54..63e922fc9 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -19,7 +19,7 @@ extension ARTree { @discardableResult public mutating func insert(key: Key, value: Value) -> Bool { - guard let (action, ref) = _findInsertNode(startNode: root!, key: key) else { return false } + guard var (action, ref) = _findInsertNode(startNode: root!, key: key) else { return false } switch action { case .replace(var _): @@ -79,11 +79,11 @@ extension ARTree { } fileprivate mutating func _findInsertNode(startNode: RawNode, key: Key) - -> (InsertAction, InternalNode.ChildSlotPtr)? { + -> (InsertAction, NodeReference)? { var current: RawNode = startNode var depth = 0 - var ref = UnsafeMutablePointer(&root) + var ref = NodeReference(&root) while depth < key.count { // Reached leaf already, replace it with a new node, or update the existing value. diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index a4260b990..2fde43a9a 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -52,21 +52,38 @@ extension ManagedNode { var type: NodeType { Self.type } } +struct NodeReference { + var _ptr: InternalNode.ChildSlotPtr + + init(_ ptr: InternalNode.ChildSlotPtr) { + self._ptr = ptr + } +} + +extension NodeReference { + var pointee: RawNode? { + @inline(__always) get { _ptr.pointee } + @inline(__always) set { + _ptr.pointee = newValue + } + } +} + enum UpdateResult { case noop case replaceWith(T) } extension InternalNode { - mutating func child(forKey k: KeyPart, ref: inout ChildSlotPtr) -> RawNode? { + mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { if count == 0 { return nil } return index(forKey: k).flatMap { index in - return withChildRef(at: index) { _ref in - ref = _ref - return ref.pointee + self.withChildRef(at: index) { ptr in + ref = NodeReference(ptr) + return ptr.pointee } } } From 784b788608ff97949b2099b581381d698012bfa4 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sun, 20 Aug 2023 22:52:59 -0700 Subject: [PATCH 26/67] Generalize Tree a bit more --- Sources/ARTreeModule/ARTree+Sequence.swift | 22 ++-- Sources/ARTreeModule/ARTree+delete.swift | 5 +- Sources/ARTreeModule/ARTree+get.swift | 7 +- Sources/ARTreeModule/ARTree+insert.swift | 26 ++-- Sources/ARTreeModule/ARTree+utils.swift | 4 +- Sources/ARTreeModule/ARTree.swift | 12 +- Sources/ARTreeModule/Node+Storage.swift | 4 +- .../ARTreeModule/Node+StringConverter.swift | 76 +++++++----- Sources/ARTreeModule/Node.swift | 20 ++-- Sources/ARTreeModule/Node16.swift | 12 +- Sources/ARTreeModule/Node256.swift | 10 +- Sources/ARTreeModule/Node4.swift | 10 +- Sources/ARTreeModule/Node48.swift | 12 +- Sources/ARTreeModule/NodeLeaf.swift | 20 ++-- Sources/ARTreeModule/RawNode.swift | 21 ++-- Tests/ARTreeModuleTests/DeleteTests.swift | 1 + Tests/ARTreeModuleTests/GetValueTests.swift | 1 + Tests/ARTreeModuleTests/InsertTests.swift | 11 +- Tests/ARTreeModuleTests/Node16Tests.swift | 57 +++++---- Tests/ARTreeModuleTests/Node256Tests.swift | 28 +++-- Tests/ARTreeModuleTests/Node48Tests.swift | 28 +++-- Tests/ARTreeModuleTests/Node4Tests.swift | 111 ++++++++++-------- Tests/ARTreeModuleTests/NodeBasicTests.swift | 9 +- Tests/ARTreeModuleTests/NodeLeafTests.swift | 42 ++++--- Tests/ARTreeModuleTests/SequenceTests.swift | 1 + .../ARTreeModuleTests/TreeRefCountTests.swift | 1 + 26 files changed, 322 insertions(+), 229 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 86241486e..d55528464 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -9,14 +9,15 @@ // //===----------------------------------------------------------------------===// +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree: Sequence { - public typealias Element = (Key, Value) + public typealias Iterator = _Iterator - public struct Iterator { - typealias _ChildIndex = InternalNode.Index + public struct _Iterator { + typealias _ChildIndex = InternalNode.Index private let tree: ARTree - private var path: [(any InternalNode, _ChildIndex?)] + private var path: [(any InternalNode, _ChildIndex?)] init(tree: ARTree) { self.tree = tree @@ -24,8 +25,8 @@ extension ARTree: Sequence { guard let node = tree.root else { return } assert(node.type != .leaf, "root can't be leaf") - let n = node.toInternalNode() - if node.toInternalNode().count > 0 { + let n: any InternalNode = node.toInternalNode() + if n.count > 0 { self.path = [(n, n.index())] } } @@ -37,8 +38,9 @@ extension ARTree: Sequence { } // TODO: Instead of index, use node iterators, to advance to next child. -extension ARTree.Iterator: IteratorProtocol { - public typealias Element = (Key, Value) +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTree._Iterator: IteratorProtocol { + public typealias Element = (Key, Spec.Value) // TODO: Why just Value fails? // Exhausted childs on the tip of path. Forward to sibling. mutating private func advanceToSibling() { @@ -64,8 +66,8 @@ extension ARTree.Iterator: IteratorProtocol { let next = node.child(at: index)! if next.type == .leaf { - let leaf: NodeLeaf = next.toLeafNode() - let result: (Key, Value) = (leaf.key, leaf.value()) + let leaf: NodeLeaf = next.toLeafNode() + let result = (leaf.key, leaf.value) advanceToNextChild() return result } diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 9a62a0183..1407da1db 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { public mutating func delete(key: Key) { guard let node = root else { return } @@ -29,7 +30,7 @@ extension ARTree { key: Key, depth: Int) -> UpdateResult { if node.type == .leaf { - let leaf = node.toLeafNode() + let leaf: NodeLeaf = node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { return .noop } @@ -38,7 +39,7 @@ extension ARTree { } var newDepth = depth - var node = node.toInternalNode() + var node: any InternalNode = node.toInternalNode() if node.partialLength > 0 { let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index 01ec78b9f..f680f9d7b 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { public func getValue(key: Key) -> Value? { assert(root != nil, "root can't be nil") @@ -20,13 +21,13 @@ extension ARTree { } if _rawNode.type == .leaf { - let leaf: NodeLeaf = _rawNode.toLeafNode() + let leaf: NodeLeaf = _rawNode.toLeafNode() return leaf.keyEquals(with: key) - ? leaf.value() + ? leaf.value : nil } - let node = _rawNode.toInternalNode() + let node: any InternalNode = _rawNode.toInternalNode() if node.partialLength > 0 { let prefixLen = node.prefixMismatch(withKey: key, fromIndex: depth) assert(prefixLen <= Const.maxPartialLength, "partial length is always bounded") diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 63e922fc9..8b74f836c 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -9,27 +9,29 @@ // //===----------------------------------------------------------------------===// +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { fileprivate enum InsertAction { - case replace(NodeLeaf) - case splitLeaf(NodeLeaf, depth: Int) - case splitNode(any InternalNode, depth: Int, prefixDiff: Int) - case insertInto(any InternalNode, depth: Int) + case replace(NodeLeaf) + case splitLeaf(NodeLeaf, depth: Int) + case splitNode(any InternalNode, depth: Int, prefixDiff: Int) + case insertInto(any InternalNode, depth: Int) } @discardableResult + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) public mutating func insert(key: Key, value: Value) -> Bool { guard var (action, ref) = _findInsertNode(startNode: root!, key: key) else { return false } switch action { - case .replace(var _): + case .replace(_): fatalError("replace not supported") case .splitLeaf(let leaf, let depth): let newLeaf = Self.allocateLeaf(key: key, value: value) var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) - var newNode = Node4.allocate() + var newNode = Node4.allocate() _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) _ = newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) @@ -44,7 +46,7 @@ extension ARTree { break } - var nextNode = Node4.allocate() + var nextNode = Node4.allocate() _ = nextNode.addChild(forKey: key[start - 1], node: newNode) newNode = nextNode } @@ -52,7 +54,7 @@ extension ARTree { ref.pointee = newNode.rawNode // Replace child in parent. case .splitNode(var node, let depth, let prefixDiff): - var newNode = Node4.allocate() + var newNode = Node4.allocate() newNode.partialLength = prefixDiff // TODO: Just copy min(maxPartialLength, prefixDiff) bytes newNode.partialBytes = node.partialBytes @@ -79,16 +81,16 @@ extension ARTree { } fileprivate mutating func _findInsertNode(startNode: RawNode, key: Key) - -> (InsertAction, NodeReference)? { + -> (InsertAction, NodeReference)? { var current: RawNode = startNode var depth = 0 - var ref = NodeReference(&root) + var ref = NodeReference(&root) while depth < key.count { // Reached leaf already, replace it with a new node, or update the existing value. if current.type == .leaf { - let leaf: NodeLeaf = current.toLeafNode() + let leaf: NodeLeaf = current.toLeafNode() if leaf.keyEquals(with: key) { return (.replace(leaf), ref) } @@ -96,7 +98,7 @@ extension ARTree { return (.splitLeaf(leaf, depth: depth), ref) } - var node = current.toInternalNode() + var node: any InternalNode = current.toInternalNode() if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) diff --git a/Sources/ARTreeModule/ARTree+utils.swift b/Sources/ARTreeModule/ARTree+utils.swift index 85a518745..db647c6ad 100644 --- a/Sources/ARTreeModule/ARTree+utils.swift +++ b/Sources/ARTreeModule/ARTree+utils.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension ARTree { - static func allocateLeaf(key: Key, value: Value) -> NodeLeaf { - return NodeLeaf.allocate(key: key, value: value, of: Value.self) + static func allocateLeaf(key: Key, value: Value) -> NodeLeaf { + return NodeLeaf.allocate(key: key, value: value) } } diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 35cdc54ee..aacbf425a 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -20,10 +20,20 @@ // * Better test cases. // * Fuzz testing. // * Leaf don't need to store entire key. + +public protocol ARTreeSpec { + associatedtype Value +} + +public struct DefaultSpec<_Value>: ARTreeSpec { + public typealias Value = _Value +} + public struct ARTree { + public typealias Spec = DefaultSpec var root: RawNode? public init() { - self.root = Node4.allocate().rawNode + self.root = Node4.allocate().rawNode } } diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index 846ac8220..98a751422 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -12,8 +12,10 @@ typealias RawNodeBuffer = ManagedBuffer final class NodeBuffer: RawNodeBuffer { + typealias Value = Mn.Value + deinit { - Mn.deinitialize(NodeStorage(buf: self)) + Mn.deinitialize(NodeStorage(buf: self)) } } diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index 892d666c4..d27aceaa2 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -10,13 +10,13 @@ //===----------------------------------------------------------------------===// protocol NodePrettyPrinter { - func print(value: Value.Type) -> String - func prettyPrint(depth: Int, value: Value.Type) -> String + func print() -> String + func prettyPrint(depth: Int) -> String } extension NodePrettyPrinter { - func print(value: Value.Type) -> String { - return "○ " + prettyPrint(depth: 0, value: value) + func print() -> String { + return "○ " + prettyPrint(depth: 0) } } @@ -30,22 +30,48 @@ func indent(_ width: Int, last: Bool) -> String { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree: CustomStringConvertible { public var description: String { if let node = root { - return "○ " + node.toManagedNode().prettyPrint(depth: 0, value: Value.self) + return "○ " + node.prettyPrint(depth: 0, with: Spec.self) } else { return "<>" } } } -extension RawNode: NodePrettyPrinter { - func prettyPrint(depth: Int, value: Value.Type) -> String { - return toManagedNode().prettyPrint(depth: depth, value: Value.self) +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ManagedNode { + public var description: String { + return "○ " + prettyPrint(depth: 0) + } + + func prettyPrint(depth: Int) -> String { + switch self.type { + case .leaf: + return (self as! NodeLeaf).prettyPrint(depth: depth) + case .node4: + return (self as! Node4).prettyPrint(depth: depth) + case .node16: + return (self as! Node16).prettyPrint(depth: depth) + case .node48: + return (self as! Node48).prettyPrint(depth: depth) + case .node256: + return (self as! Node256).prettyPrint(depth: depth) + } + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RawNode { + func prettyPrint(depth: Int, with: Spec.Type) -> String { + let n: any ManagedNode = toManagedNode() + return n.prettyPrint(depth: depth) } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { fileprivate var partial: [UInt8] { var arr = [UInt8](repeating: 0, count: partialLength) @@ -57,15 +83,16 @@ extension InternalNode { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension NodeLeaf: NodePrettyPrinter { - func prettyPrint(depth: Int, value: Value.Type) -> String { - let val: Value = self.value() - return "\(self.keyLength)\(self.key) -> \(val)" + func prettyPrint(depth: Int) -> String { + return "\(self.keyLength)\(self.key) -> \(self.value)" } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension Node4: NodePrettyPrinter { - func prettyPrint(depth: Int, value: Value.Type) -> String { + func prettyPrint(depth: Int) -> String { var output = "Node4 {childs=\(count), partial=\(partial)}\n" withBody { keys, childs in for idx in 0..(depth: Int, value: Value.Type) -> String { + func prettyPrint(depth: Int) -> String { var output = "Node16 {childs=\(count), partial=\(partial)}\n" withBody { keys, childs in for idx in 0..(depth: Int, value: Value.Type) -> String { + func prettyPrint(depth: Int) -> String { var output = "Node48 {childs=\(count), partial=\(partial)}\n" var total = 0 withBody { keys, childs in @@ -122,9 +147,7 @@ extension Node48: NodePrettyPrinter { let last = total == count output += indent(depth, last: last) output += String(key) + ": " - output += child(at: Int(slot))!.prettyPrint( - depth: depth + 1, - value: value) + output += child(at: Int(slot))!.prettyPrint(depth: depth + 1, with: Spec.self) if !last { output += "\n" } @@ -135,8 +158,9 @@ extension Node48: NodePrettyPrinter { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension Node256: NodePrettyPrinter { - func prettyPrint(depth: Int, value: Value.Type) -> String { + func prettyPrint(depth: Int) -> String { var output = "Node256 {childs=\(count), partial=\(partial)}\n" var total = 0 withBody { childs in @@ -149,9 +173,7 @@ extension Node256: NodePrettyPrinter { let last = total == count output += indent(depth, last: last) output += String(key) + ": " - output += child!.prettyPrint( - depth: depth + 1, - value: value) + output += child!.prettyPrint(depth: depth + 1, with: Spec.self) if !last { output += "\n" } diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 2fde43a9a..8716af00c 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -12,11 +12,14 @@ public typealias KeyPart = UInt8 public typealias Key = [KeyPart] -protocol ManagedNode: NodePrettyPrinter { +protocol ManagedNode { + associatedtype Spec: ARTreeSpec + + typealias Value = Spec.Value typealias Storage = NodeStorage typealias ChildSlotPtr = UnsafeMutablePointer - static func deinitialize(_ storage: NodeStorage) + static func deinitialize(_ storage: Storage) static var type: NodeType { get } var storage: Storage { get } @@ -24,7 +27,8 @@ protocol ManagedNode: NodePrettyPrinter { var rawNode: RawNode { get } } -protocol InternalNode: ManagedNode { +protocol InternalNode: ManagedNode { + typealias Value = Spec.Value typealias Index = Int typealias Header = InternalNodeHeader @@ -41,7 +45,7 @@ protocol InternalNode: ManagedNode { func child(forKey k: KeyPart) -> RawNode? func child(at: Index) -> RawNode? - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult mutating func deleteChild(at index: Index) -> UpdateResult mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R @@ -52,10 +56,10 @@ extension ManagedNode { var type: NodeType { Self.type } } -struct NodeReference { - var _ptr: InternalNode.ChildSlotPtr +struct NodeReference { + var _ptr: (any InternalNode).ChildSlotPtr - init(_ ptr: InternalNode.ChildSlotPtr) { + init(_ ptr: (any InternalNode).ChildSlotPtr) { self._ptr = ptr } } @@ -75,7 +79,7 @@ enum UpdateResult { } extension InternalNode { - mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { + mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { if count == 0 { return nil } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index fcd955174..ad6c52049 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -struct Node16 { +struct Node16 { var storage: Storage } extension Node16 { - static let type: NodeType = .node16 - static let numKeys: Int = 16 + static var type: NodeType { .node16 } + static var numKeys: Int { 16 } init(buffer: RawNodeBuffer) { self.init(storage: Storage(raw: buffer)) @@ -35,7 +35,7 @@ extension Node16 { return node } - static func allocate(copyFrom: Node4) -> Self { + static func allocate(copyFrom: Node4) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) @@ -50,7 +50,7 @@ extension Node16 { return node } - static func allocate(copyFrom: Node48) -> Self { + static func allocate(copyFrom: Node48) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) @@ -147,7 +147,7 @@ extension Node16: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody {keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 7a604d392..f4d601726 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -struct Node256 { +struct Node256 { var storage: Storage } extension Node256 { - static let type: NodeType = .node256 - static let numKeys: Int = 256 + static var type: NodeType { .node256 } + static var numKeys: Int { 256 } init(buffer: RawNodeBuffer) { self.init(storage: Storage(raw: buffer)) @@ -33,7 +33,7 @@ extension Node256 { return node } - static func allocate(copyFrom: Node48) -> Self { + static func allocate(copyFrom: Node48) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) @@ -101,7 +101,7 @@ extension Node256: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { return withBody { childs in assert(childs[Int(k)] == nil, "node for key \(k) already exists") childs[Int(k)] = node.rawNode diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index a83ea791c..85e0f8325 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -struct Node4 { +struct Node4 { var storage: Storage } extension Node4 { - static let type: NodeType = .node4 - static let numKeys: Int = 4 + static var type: NodeType { .node4 } + static var numKeys: Int { 4 } init(buffer: RawNodeBuffer) { self.init(storage: Storage(raw: buffer)) @@ -35,7 +35,7 @@ extension Node4 { return node } - static func allocate(copyFrom: Node16) -> Self { + static func allocate(copyFrom: Node16) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) node.withBody { newKeys, newChilds in @@ -121,7 +121,7 @@ extension Node4: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody { keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 2cfed919f..ba6eae7f0 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -9,13 +9,13 @@ // //===----------------------------------------------------------------------===// -struct Node48 { +struct Node48 { var storage: Storage } extension Node48 { - static let type: NodeType = .node48 - static let numKeys: Int = 48 + static var type: NodeType { .node48 } + static var numKeys: Int { 48 } init(buffer: RawNodeBuffer) { self.init(storage: Storage(raw: buffer)) @@ -41,7 +41,7 @@ extension Node48 { return node } - static func allocate(copyFrom: Node16) -> Self { + static func allocate(copyFrom: Node16) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) @@ -58,7 +58,7 @@ extension Node48 { return node } - static func allocate(copyFrom: Node256) -> Self { + static func allocate(copyFrom: Node256) -> Self { var node = Self.allocate() node.copyHeader(from: copyFrom) @@ -141,7 +141,7 @@ extension Node48: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { if count < Self.numKeys { withBody { keys, childs in assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 970fb4eed..6bcc6d239 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -9,7 +9,8 @@ // //===----------------------------------------------------------------------===// -struct NodeLeaf { +struct NodeLeaf { + typealias Value = Spec.Value var storage: Storage } @@ -22,7 +23,7 @@ extension NodeLeaf { } extension NodeLeaf { - static func allocate(key: Key, value: Value, of: Value.Type) -> Self { + static func allocate(key: Key, value: Value) -> Self { let size = MemoryLayout.stride + key.count + MemoryLayout.stride let buf = NodeStorage.create(type: .leaf, size: size) var leaf = Self(ptr: buf) @@ -53,8 +54,7 @@ extension NodeLeaf { } } - func withValue(of: Value.Type, - body: (UnsafeMutablePointer) throws -> R) rethrows -> R { + func withValue(body: (UnsafeMutablePointer) throws -> R) rethrows -> R { return try storage.withUnsafePointer { return try body( $0.advanced(by: MemoryLayout.stride) @@ -63,8 +63,7 @@ extension NodeLeaf { } } - func withKeyValue( - body: (KeyPtr, UnsafeMutablePointer) throws -> R) rethrows -> R { + func withKeyValue(body: (KeyPtr, UnsafeMutablePointer) throws -> R) rethrows -> R { return try storage.withUnsafePointer { let base = $0.advanced(by: MemoryLayout.stride) let keyPtr = UnsafeMutableBufferPointer( @@ -95,8 +94,8 @@ extension NodeLeaf { } } - func value() -> Value { - return withValue(of: Value.self) { $0.pointee } + var value: Value { + get { withValue() { $0.pointee } } } } @@ -134,6 +133,9 @@ extension NodeLeaf { } extension NodeLeaf: ManagedNode { - static func deinitialize(_ storage: NodeStorage) { + static func deinitialize(_ storage: Storage) { + Self(storage: storage).withValue { + $0.deinitialize(count: 1) + } } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 1119e93d6..178d6009a 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -17,37 +17,40 @@ struct RawNode { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { var type: NodeType { @inline(__always) get { return storage.header } } - func toInternalNode() -> any InternalNode { + func toInternalNode() -> any InternalNode { switch type { case .node4: - return Node4(buffer: storage) + return Node4(buffer: storage) case .node16: - return Node16(buffer: storage) + return Node16(buffer: storage) case .node48: - return Node48(buffer: storage) + return Node48(buffer: storage) case .node256: - return Node256(buffer: storage) + return Node256(buffer: storage) default: assert(false, "leaf nodes are not internal nodes") } } - func toLeafNode() -> NodeLeaf { + func toLeafNode() -> NodeLeaf { assert(type == .leaf) return NodeLeaf(ptr: storage) } - func toManagedNode() -> any ManagedNode { + func toManagedNode() -> any ManagedNode { switch type { case .leaf: - return toLeafNode() + let r: NodeLeaf = toLeafNode() + return r default: - return toInternalNode() + let r: any ManagedNode = toInternalNode() + return r } } } diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index d98ecbc20..a4f7fce9b 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -13,6 +13,7 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeDeleteTests: XCTestCase { func testDeleteBasic() throws { var t = ARTree<[UInt8]>() diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index 9db979018..1bd11cb91 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -20,6 +20,7 @@ func randomByteArray(minSize: Int, maxSize: Int, minByte: UInt8, maxByte: UInt8) return result } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeGetValueTests: XCTestCase { func testGetValueBasic() throws { var t = ARTree<[UInt8]>() diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 38bc3b6d5..67ede7842 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -13,6 +13,7 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeInsertTests: XCTestCase { func testInsertBasic() throws { var t = ARTree<[UInt8]>() @@ -68,7 +69,8 @@ final class ARTreeInsertTests: XCTestCase { } func testInsertExpandTo48() throws { - var t = ARTree<[UInt8]>() + typealias T = ARTree<[UInt8]> + var t = T() for ii: UInt8 in 0..<40 { t.insert(key: [ii + 1], value: [ii @@ -82,7 +84,7 @@ final class ARTreeInsertTests: XCTestCase { } } - let root = t.root!.toInternalNode() + let root: any InternalNode = t.root!.toInternalNode() XCTAssertEqual(root.count, 40) XCTAssertEqual( t.description, @@ -130,7 +132,8 @@ final class ARTreeInsertTests: XCTestCase { } func testInsertExpandTo256() throws { - var t = ARTree<[UInt8]>() + typealias T = ARTree<[UInt8]> + var t = T() for ii: UInt8 in 0..<70 { t.insert(key: [ii + 1], value: [ii @@ -144,7 +147,7 @@ final class ARTreeInsertTests: XCTestCase { } } - let root = t.root!.toInternalNode() + let root: any InternalNode = t.root!.toInternalNode() XCTAssertEqual(root.count, 70) XCTAssertEqual( t.description, diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index 1b70e1866..67df6a3e8 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -13,26 +13,29 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeNode16Tests: XCTestCase { func test16Basic() throws { - var node = Node16.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N16.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [0])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") } func test4AddInMiddle() throws { - var node = Node16.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N16.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [2])) + _ = node.addChild(forKey: 30, node: T.Leaf.allocate(key: [30], value: [3])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [4])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" + @@ -41,54 +44,56 @@ final class ARTreeNode16Tests: XCTestCase { } func test16DeleteAtIndex() throws { - var node = Node16.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N16.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 0) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 1) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") _ = node.deleteChild(at: 0) - XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") + XCTAssertEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } func test16DeleteKey() throws { - var node = Node16.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N16.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual(node.print(value: [UInt8].self), "○ Node16 {childs=0, partial=[]}\n") + XCTAssertEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index 66961a4e8..17f49cd0f 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -13,41 +13,45 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeNode256Tests: XCTestCase { + typealias Leaf = NodeLeaf> + typealias N256 = Node256> + func test256Basic() throws { - var node = Node256.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [0], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + var node = N256.allocate() + _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [0])) + _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + "└──○ 20: 1[20] -> [3]") } func test48DeleteAtIndex() throws { - var node = Node256.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + var node = N256.allocate() + _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node256 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 10) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 15) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 20) - XCTAssertEqual(node.print(value: [UInt8].self), "○ Node256 {childs=0, partial=[]}\n") + XCTAssertEqual(node.print(), "○ Node256 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index a4c5bccbe..ddce99541 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -13,41 +13,45 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeNode48Tests: XCTestCase { + typealias Leaf = NodeLeaf> + typealias N48 = Node48> + func test48Basic() throws { - var node = Node48.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) + var node = N48.allocate() + _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [2])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "└──○ 20: 1[20] -> [2]") } func test48DeleteAtIndex() throws { - var node = Node48.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + var node = N48.allocate() + _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node48 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 10) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 15) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 20) - XCTAssertEqual(node.print(value: [UInt8].self), "○ Node48 {childs=0, partial=[]}\n") + XCTAssertEqual(node.print(), "○ Node48 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 1cf384214..4269e13f0 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -13,8 +13,20 @@ import XCTest @testable import ARTreeModule +struct Tree { + typealias Spec = DefaultSpec + typealias Leaf = NodeLeaf + typealias N4 = Node4 + typealias N16 = Node16 + typealias N48 = Node16 + typealias N256 = Node256 +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { - mutating func addChildReturn(forKey k: KeyPart, node: any ManagedNode) -> (any ManagedNode)? { + mutating func addChildReturn(forKey k: KeyPart, node: any ManagedNode) + -> (any ManagedNode)? { + switch addChild(forKey: k, node: node) { case .noop: return self @@ -23,7 +35,7 @@ extension InternalNode { } } - mutating func deleteChildReturn(at idx: Index) -> (any ManagedNode)? { + mutating func deleteChildReturn(at idx: Index) -> (any ManagedNode)? { switch deleteChild(at: idx) { case .noop: return self @@ -33,37 +45,41 @@ extension InternalNode { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeNode4Tests: XCTestCase { func test4Basic() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [11], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [22], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N4.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [11])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [22])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [11]\n" + "└──○ 20: 1[20] -> [22]") } func test4BasicInt() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: 11, of: Int.self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: 22, of: Int.self)) + typealias T = Tree + var node = T.N4.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: 11)) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: 22)) XCTAssertEqual( - node.print(value: Int.self), + node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> 11\n" + "└──○ 20: 1[20] -> 22") } func test4AddInMiddle() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 30, node: NodeLeaf.allocate(key: [30], value: [3], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [4], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N4.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [2])) + _ = node.addChild(forKey: 30, node: T.Leaf.allocate(key: [30], value: [3])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [4])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [4]\n" + @@ -72,19 +88,20 @@ final class ARTreeNode4Tests: XCTestCase { } func test4DeleteAtIndex() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N4.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 0) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") @@ -94,14 +111,15 @@ final class ARTreeNode4Tests: XCTestCase { } func test4DeleteFromFull() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: 1, of: Int.self)) - _ = node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: 2, of: Int.self)) - _ = node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: 3, of: Int.self)) - _ = node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: 4, of: Int.self)) + typealias T = Tree + var node = T.N4.allocate() + _ = node.addChild(forKey: 1, node: T.Leaf.allocate(key: [1], value: 1)) + _ = node.addChild(forKey: 2, node: T.Leaf.allocate(key: [2], value: 2)) + _ = node.addChild(forKey: 3, node: T.Leaf.allocate(key: [3], value: 3)) + _ = node.addChild(forKey: 4, node: T.Leaf.allocate(key: [4], value: 4)) XCTAssertEqual(node.type, .node4) XCTAssertEqual( - node.print(value: Int.self), + node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> 1\n" + "├──○ 2: 1[2] -> 2\n" + @@ -109,7 +127,7 @@ final class ARTreeNode4Tests: XCTestCase { "└──○ 4: 1[4] -> 4") _ = node.deleteChild(at: 1) XCTAssertEqual( - node.print(value: Int.self), + node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 1: 1[1] -> 1\n" + "├──○ 3: 1[3] -> 3\n" + @@ -121,23 +139,23 @@ final class ARTreeNode4Tests: XCTestCase { } func test4ExapandTo16() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 1, node: NodeLeaf.allocate(key: [1], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 2, node: NodeLeaf.allocate(key: [2], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 3, node: NodeLeaf.allocate(key: [3], value: [3], of: [UInt8].self)) - _ = node.addChild(forKey: 4, node: NodeLeaf.allocate(key: [4], value: [4], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N4.allocate() + _ = node.addChild(forKey: 1, node: T.Leaf.allocate(key: [1], value: [1])) + _ = node.addChild(forKey: 2, node: T.Leaf.allocate(key: [2], value: [2])) + _ = node.addChild(forKey: 3, node: T.Leaf.allocate(key: [3], value: [3])) + _ = node.addChild(forKey: 4, node: T.Leaf.allocate(key: [4], value: [4])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - let newNode = node.addChildReturn( - forKey: 5, node: NodeLeaf.allocate(key: [5], value: [5], of: [UInt8].self)) + let newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) XCTAssertEqual( - newNode!.print(value: [UInt8].self), + newNode!.description, "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + @@ -147,28 +165,29 @@ final class ARTreeNode4Tests: XCTestCase { } func test4DeleteKey() throws { - var node = Node4.allocate() - _ = node.addChild(forKey: 10, node: NodeLeaf.allocate(key: [10], value: [1], of: [UInt8].self)) - _ = node.addChild(forKey: 15, node: NodeLeaf.allocate(key: [15], value: [2], of: [UInt8].self)) - _ = node.addChild(forKey: 20, node: NodeLeaf.allocate(key: [20], value: [3], of: [UInt8].self)) + typealias T = Tree<[UInt8]> + var node = T.N4.allocate() + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } XCTAssertEqual( - node.print(value: [UInt8].self), + node.print(), "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual(node.print(value: [UInt8].self), "○ Node4 {childs=0, partial=[]}\n") + XCTAssertEqual(node.print(), "○ Node4 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index 0cbf7da0b..de8ac2059 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -18,13 +18,14 @@ final class ARTreeNodeBasicTests: XCTestCase { let header = MemoryLayout.stride XCTAssertEqual(header, 12) + typealias Spec = DefaultSpec let childSlotSize = MemoryLayout.stride let ptrSize = MemoryLayout.stride let refSize = MemoryLayout.stride - let size4 = Node4.size - let size16 = Node16.size - let size48 = Node48.size - let size256 = Node256.size + let size4 = Node4.size + let size16 = Node16.size + let size48 = Node48.size + let size256 = Node256.size print("sizeof(RawNode?) = \(refSize)") print("sizeOf(Int) = \(ptrSize)") diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index d3d050b40..3eb176e56 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -15,18 +15,20 @@ import XCTest final class ARTreeNodeLeafTests: XCTestCase { func testLeafBasic() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) - XCTAssertEqual(leaf1.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0]") + typealias L = NodeLeaf> + let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) + XCTAssertEqual(leaf1.print(), "○ 4[10, 20, 30, 40] -> [0]") - let leaf2 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2], of: [UInt8].self) - XCTAssertEqual(leaf2.print(value: [UInt8].self), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") + let leaf2 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + XCTAssertEqual(leaf2.print(), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") - let leaf3 = NodeLeaf.allocate(key: [], value: [], of: [UInt8].self) - XCTAssertEqual(leaf3.print(value: [UInt8].self), "○ 0[] -> []") + let leaf3 = L.allocate(key: [], value: []) + XCTAssertEqual(leaf3.print(), "○ 0[] -> []") } func testLeafKeyEquals() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) + typealias L = NodeLeaf> + let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 50])) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30])) XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 40, 50])) @@ -34,56 +36,58 @@ final class ARTreeNodeLeafTests: XCTestCase { } func testCasts() throws { - let leaf = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0], of: [UInt8].self) + typealias L = NodeLeaf> + let leaf = L.allocate(key: [10, 20, 30, 40], value: [0]) XCTAssertEqual(leaf.key, [10, 20, 30, 40]) - XCTAssertEqual(leaf.value(), [0]) + XCTAssertEqual(leaf.value, [0]) } func testLeafLcp() throws { - let leaf1 = NodeLeaf.allocate(key: [10, 20, 30, 40], value: [0, 1, 2], of: [UInt8].self) + typealias L = NodeLeaf> + let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0, 1, 2, 3], value: [0], of: [UInt8].self), + with: L.allocate(key: [0, 1, 2, 3], value: [0]), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0], value: [0], of: [UInt8].self), + with: L.allocate(key: [0], value: [0]), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [0, 1], value: [0], of: [UInt8].self), + with: L.allocate(key: [0, 1], value: [0]), fromIndex: 0), 0) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 1], value: [0], of: [UInt8].self), + with: L.allocate(key: [10, 1], value: [0]), fromIndex: 0), 1) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), + with: L.allocate(key: [10, 20], value: [0]), fromIndex: 0), 2) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), + with: L.allocate(key: [10, 20], value: [0]), fromIndex: 1), 1) XCTAssertEqual( leaf1.longestCommonPrefix( - with: NodeLeaf.allocate(key: [10, 20], value: [0], of: [UInt8].self), + with: L.allocate(key: [10, 20], value: [0]), fromIndex: 2), 0) // Breaks the contract, so its OK that these fail. // XCTAssertEqual( - // leaf1.longestCommonPrefix(with: NodeLeaf.allocate(key: [], value: [0]), + // leaf1.longestCommonPrefix(with: L.allocate(key: [], value: [0]), // fromIndex: 0), // 0) // XCTAssertEqual( - // leaf1.longestCommonPrefix(with: NodeLeaf.allocate(key: [10], value: [0]), + // leaf1.longestCommonPrefix(with: L.allocate(key: [10], value: [0]), // fromIndex: 2), // 0) } diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift index 61904e89f..ca19103b7 100644 --- a/Tests/ARTreeModuleTests/SequenceTests.swift +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -13,6 +13,7 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeSequenceTests: XCTestCase { func testSequenceEmpty() throws { let t = ARTree<[UInt8]>() diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index e7ba28391..4215b1396 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -21,6 +21,7 @@ private class TestBox { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeRefCountTest: XCTestCase { func testRefCountBasic() throws { // TODO: Why is it 2? From e29d1dac349fdb37fc12fe669ee865d687a7682a Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 08:03:23 -0700 Subject: [PATCH 27/67] Refactor ARTree into ARTreeImpl --- Sources/ARTreeModule/ARTree+Sequence.swift | 12 ++++++------ Sources/ARTreeModule/ARTree+get.swift | 2 +- Sources/ARTreeModule/ARTree.swift | 12 ++++-------- .../{Const.swift => Consts and Specs.swift} | 10 ++++++++-- Sources/ARTreeModule/NodeLeaf.swift | 2 +- Sources/ARTreeModule/RawNode.swift | 6 ++---- 6 files changed, 22 insertions(+), 22 deletions(-) rename Sources/ARTreeModule/{Const.swift => Consts and Specs.swift} (77%) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index d55528464..6f1365698 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -10,16 +10,16 @@ //===----------------------------------------------------------------------===// @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree: Sequence { - public typealias Iterator = _Iterator +extension ARTreeImpl: Sequence { + public typealias Iterator = _Iterator - public struct _Iterator { + public struct _Iterator { typealias _ChildIndex = InternalNode.Index - private let tree: ARTree + private let tree: ARTreeImpl private var path: [(any InternalNode, _ChildIndex?)] - init(tree: ARTree) { + init(tree: ARTreeImpl) { self.tree = tree self.path = [] guard let node = tree.root else { return } @@ -39,7 +39,7 @@ extension ARTree: Sequence { // TODO: Instead of index, use node iterators, to advance to next child. @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree._Iterator: IteratorProtocol { +extension ARTreeImpl._Iterator: IteratorProtocol { public typealias Element = (Key, Spec.Value) // TODO: Why just Value fails? // Exhausted childs on the tip of path. Forward to sibling. diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index f680f9d7b..d6dbff5b2 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree { +extension ARTreeImpl { public func getValue(key: Key) -> Value? { assert(root != nil, "root can't be nil") var current = root diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index aacbf425a..5e231364b 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -21,16 +21,12 @@ // * Fuzz testing. // * Leaf don't need to store entire key. -public protocol ARTreeSpec { - associatedtype Value -} - -public struct DefaultSpec<_Value>: ARTreeSpec { - public typealias Value = _Value -} +public typealias ARTree = ARTreeImpl> -public struct ARTree { +public struct ARTreeImpl { public typealias Spec = DefaultSpec + public typealias Value = Spec.Value + var root: RawNode? public init() { diff --git a/Sources/ARTreeModule/Const.swift b/Sources/ARTreeModule/Consts and Specs.swift similarity index 77% rename from Sources/ARTreeModule/Const.swift rename to Sources/ARTreeModule/Consts and Specs.swift index bfdfd5402..1631e7e5b 100644 --- a/Sources/ARTreeModule/Const.swift +++ b/Sources/ARTreeModule/Consts and Specs.swift @@ -10,7 +10,13 @@ //===----------------------------------------------------------------------===// struct Const { - static let indentWidth = 4 - static let defaultAlignment = 4 static let maxPartialLength = 8 } + +public protocol ARTreeSpec { + associatedtype Value +} + +public struct DefaultSpec<_Value>: ARTreeSpec { + public typealias Value = _Value +} diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 6bcc6d239..f024e9c36 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -134,7 +134,7 @@ extension NodeLeaf { extension NodeLeaf: ManagedNode { static func deinitialize(_ storage: Storage) { - Self(storage: storage).withValue { + _ = Self(storage: storage).withValue { $0.deinitialize(count: 1) } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 178d6009a..273b38208 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -46,11 +46,9 @@ extension RawNode { func toManagedNode() -> any ManagedNode { switch type { case .leaf: - let r: NodeLeaf = toLeafNode() - return r + return toLeafNode() default: - let r: any ManagedNode = toInternalNode() - return r + return toInternalNode() } } } From 2de02bd264caeccf07ebc61e0a3e633f160d200b Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 08:14:19 -0700 Subject: [PATCH 28/67] Rename ChildSlotRef to RawNode.SlotRef --- Sources/ARTreeModule/ARTree+insert.swift | 4 ++-- Sources/ARTreeModule/Node.swift | 11 +++++------ Sources/ARTreeModule/Node16.swift | 2 +- Sources/ARTreeModule/Node256.swift | 2 +- Sources/ARTreeModule/Node4.swift | 2 +- Sources/ARTreeModule/Node48.swift | 2 +- Sources/ARTreeModule/RawNode.swift | 6 +++++- 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 8b74f836c..4eba57458 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -81,11 +81,11 @@ extension ARTree { } fileprivate mutating func _findInsertNode(startNode: RawNode, key: Key) - -> (InsertAction, NodeReference)? { + -> (InsertAction, NodeReference)? { var current: RawNode = startNode var depth = 0 - var ref = NodeReference(&root) + var ref = NodeReference(&root) while depth < key.count { // Reached leaf already, replace it with a new node, or update the existing value. diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 8716af00c..d2f0ee277 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -17,7 +17,6 @@ protocol ManagedNode { typealias Value = Spec.Value typealias Storage = NodeStorage - typealias ChildSlotPtr = UnsafeMutablePointer static func deinitialize(_ storage: Storage) static var type: NodeType { get } @@ -48,7 +47,7 @@ protocol InternalNode: ManagedNode { mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult mutating func deleteChild(at index: Index) -> UpdateResult - mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R } extension ManagedNode { @@ -56,10 +55,10 @@ extension ManagedNode { var type: NodeType { Self.type } } -struct NodeReference { - var _ptr: (any InternalNode).ChildSlotPtr +struct NodeReference { + var _ptr: RawNode.SlotRef - init(_ ptr: (any InternalNode).ChildSlotPtr) { + init(_ ptr: RawNode.SlotRef) { self._ptr = ptr } } @@ -79,7 +78,7 @@ enum UpdateResult { } extension InternalNode { - mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { + mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { if count == 0 { return nil } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index ad6c52049..cb9ad3b80 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -187,7 +187,7 @@ extension Node16: InternalNode { } } - mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") return withBody {_, childs in let ref = childs.baseAddress! + index diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index f4d601726..f20b96acb 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -124,7 +124,7 @@ extension Node256: InternalNode { } } - mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") return withBody { childs in let ref = childs.baseAddress! + index diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 85e0f8325..8b4bd3d51 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -160,7 +160,7 @@ extension Node4: InternalNode { } } - mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "index=\(index) less than count=\(count)") return withBody {_, childs in let ref = childs.baseAddress! + index diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index ba6eae7f0..d57a52c4c 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -212,7 +212,7 @@ extension Node48: InternalNode { } } - mutating func withChildRef(at index: Index, _ body: (ChildSlotPtr) -> R) -> R { + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") return withBody {_, childs in let ref = childs.baseAddress! + index diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 273b38208..cd937662e 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -17,12 +17,16 @@ struct RawNode { } } -@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { + typealias SlotRef = UnsafeMutablePointer + var type: NodeType { @inline(__always) get { return storage.header } } +} +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RawNode { func toInternalNode() -> any InternalNode { switch type { case .node4: From cdecdd1bf6e95293d8941b90d893630b1117ba55 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 12:37:58 -0700 Subject: [PATCH 29/67] Retain refcounts when expanding/shrinking to a new node --- Sources/ARTreeModule/InternalNode+impl.swift | 22 +++++++++ Sources/ARTreeModule/Node+Storage.swift | 13 ++--- Sources/ARTreeModule/Node.swift | 18 ++----- Sources/ARTreeModule/Node16.swift | 20 ++++++-- Sources/ARTreeModule/Node256.swift | 22 ++++++++- Sources/ARTreeModule/Node4.swift | 19 ++++++-- Sources/ARTreeModule/Node48.swift | 23 ++++++++- Sources/ARTreeModule/NodeLeaf.swift | 14 +++--- Sources/ARTreeModule/RawNode.swift | 2 +- .../ARTreeModuleTests/TreeRefCountTests.swift | 47 ++++++++++++++++--- 10 files changed, 152 insertions(+), 48 deletions(-) diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index ddf62b42c..fde998241 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -54,6 +54,19 @@ extension InternalNode { return index(forKey: k).flatMap { child(at: $0) } } + mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { + if count == 0 { + return nil + } + + return index(forKey: k).flatMap { index in + self.withChildRef(at: index) { ptr in + ref = NodeReference(ptr) + return ptr.pointee + } + } + } + mutating func copyHeader(from: any InternalNode) { self.storage.withHeaderPointer { header in header.pointee.count = UInt16(from.count) @@ -75,4 +88,13 @@ extension InternalNode { return maxComp } + + // TODO: Look everywhere its used, and try to avoid unnecessary RC traffic. + static func retainChildren(_ children: Children, count: Int) { + for idx in 0.. - -final class NodeBuffer: RawNodeBuffer { - typealias Value = Mn.Value - - deinit { - Mn.deinitialize(NodeStorage(buf: self)) - } -} +typealias NodeBuffer = Mn.Buffer struct NodeStorage { var buf: NodeBuffer @@ -29,8 +22,8 @@ extension NodeStorage { } static func create(type: NodeType, size: Int) -> RawNodeBuffer { - let buf = NodeBuffer.create(minimumCapacity: size, - makingHeaderWith: {_ in type }) + let buf = Mn.Buffer.create(minimumCapacity: size, + makingHeaderWith: {_ in type }) buf.withUnsafeMutablePointerToElements { $0.initialize(repeating: 0, count: size) } diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index d2f0ee277..f44ea18db 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -14,11 +14,11 @@ public typealias Key = [KeyPart] protocol ManagedNode { associatedtype Spec: ARTreeSpec - + associatedtype Buffer: RawNodeBuffer + typealias Value = Spec.Value typealias Storage = NodeStorage - static func deinitialize(_ storage: Storage) static var type: NodeType { get } var storage: Storage { get } @@ -30,6 +30,7 @@ protocol InternalNode: ManagedNode { typealias Value = Spec.Value typealias Index = Int typealias Header = InternalNodeHeader + typealias Children = UnsafeMutableBufferPointer static var size: Int { get } @@ -78,19 +79,6 @@ enum UpdateResult { } extension InternalNode { - mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { - if count == 0 { - return nil - } - - return index(forKey: k).flatMap { index in - self.withChildRef(at: index) { ptr in - ref = NodeReference(ptr) - return ptr.pointee - } - } - } - mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) -> UpdateResult { diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index cb9ad3b80..9f1ca79c9 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -44,6 +44,7 @@ extension Node16 { UnsafeMutableRawBufferPointer(newKeys).copyBytes(from: fromKeys) UnsafeMutableRawBufferPointer(newChilds).copyBytes( from: UnsafeMutableRawBufferPointer(fromChilds)) + Self.retainChildren(newChilds, count: node.count) } } @@ -69,6 +70,7 @@ extension Node16 { } assert(slot == node.count) + Self.retainChildren(newChilds, count: node.count) } } @@ -78,9 +80,9 @@ extension Node16 { extension Node16 { typealias Keys = UnsafeMutableBufferPointer - typealias Childs = UnsafeMutableBufferPointer + typealias Children = UnsafeMutableBufferPointer - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + func withBody(body: (Keys, Children) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in let keys = UnsafeMutableBufferPointer( start: bodyPtr.assumingMemoryBound(to: KeyPart.self), @@ -194,7 +196,19 @@ extension Node16: InternalNode { return body(ref) } } +} - static func deinitialize(_ storage: NodeStorage) { +extension Node16: ManagedNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node16(buffer: self) + let count = node.count + _ = node.withBody { _, childs in + for idx in 0.. - typealias Childs = UnsafeMutableBufferPointer + typealias Children = UnsafeMutableBufferPointer - func withBody(body: (Childs) throws -> R) rethrows -> R { + func withBody(body: (Children) throws -> R) rethrows -> R { return try storage.withBodyPointer { return try body( UnsafeMutableBufferPointer( @@ -135,3 +137,19 @@ extension Node256: InternalNode { static func deinitialize(_ storage: NodeStorage) { } } + + +extension Node256: ManagedNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node256(buffer: self) + let count = node.count + _ = node.withBody { childs in + for idx in 0.. - typealias Childs = UnsafeMutableBufferPointer + typealias Children = UnsafeMutableBufferPointer - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + func withBody(body: (Keys, Children) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in let keys = UnsafeMutableBufferPointer( start: bodyPtr.assumingMemoryBound(to: KeyPart.self), @@ -167,7 +168,19 @@ extension Node4: InternalNode { return body(ref) } } +} - static func deinitialize(_ storage: NodeStorage) { +extension Node4: ManagedNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node4(buffer: self) + let count = node.count + _ = node.withBody { _, childs in + for idx in 0.. - typealias Childs = UnsafeMutableBufferPointer + typealias Children = UnsafeMutableBufferPointer - func withBody(body: (Keys, Childs) throws -> R) rethrows -> R { + func withBody(body: (Keys, Children) throws -> R) rethrows -> R { return try storage.withBodyPointer { bodyPtr in let keys = UnsafeMutableBufferPointer( start: bodyPtr.assumingMemoryBound(to: KeyPart.self), @@ -223,3 +227,18 @@ extension Node48: InternalNode { static func deinitialize(_ storage: NodeStorage) { } } + +extension Node48: ManagedNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node48(buffer: self) + let count = node.count + _ = node.withBody { _, childs in + for idx in 0.. { extension NodeLeaf { static var type: NodeType { .leaf } - init(ptr: RawNodeBuffer) { - self.init(storage: Storage(raw: ptr)) + init(buffer: RawNodeBuffer) { + self.init(storage: Storage(raw: buffer)) } } @@ -26,7 +26,7 @@ extension NodeLeaf { static func allocate(key: Key, value: Value) -> Self { let size = MemoryLayout.stride + key.count + MemoryLayout.stride let buf = NodeStorage.create(type: .leaf, size: size) - var leaf = Self(ptr: buf) + var leaf = Self(buffer: buf) leaf.keyLength = key.count leaf.withKeyValue { keyPtr, valuePtr in @@ -133,9 +133,11 @@ extension NodeLeaf { } extension NodeLeaf: ManagedNode { - static func deinitialize(_ storage: Storage) { - _ = Self(storage: storage).withValue { - $0.deinitialize(count: 1) + final class Buffer: RawNodeBuffer { + deinit { + _ = NodeLeaf(buffer: self).withValue { + $0.deinitialize(count: 1) + } } } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index cd937662e..b7d1da95b 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -44,7 +44,7 @@ extension RawNode { func toLeafNode() -> NodeLeaf { assert(type == .leaf) - return NodeLeaf(ptr: storage) + return NodeLeaf(buffer: storage) } func toManagedNode() -> any ManagedNode { diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 4215b1396..17bc12248 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -21,21 +21,56 @@ private class TestBox { } } +@inline(__always) func getRc(_ x: AnyObject) -> UInt { + return _getRetainCount(x) +} + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeRefCountTest: XCTestCase { func testRefCountBasic() throws { // TODO: Why is it 2? var x = TestBox("foo") - XCTAssertEqual(CFGetRetainCount(x), 2) + XCTAssertEqual(getRc(x), 2) var t = ARTree() - XCTAssertEqual(CFGetRetainCount(x), 2) + XCTAssertEqual(getRc(x), 2) t.insert(key: [10, 20, 30], value: x) - XCTAssertEqual(CFGetRetainCount(x), 3) + XCTAssertEqual(getRc(x), 3) x = TestBox("bar") - XCTAssertEqual(CFGetRetainCount(x), 2) + XCTAssertEqual(getRc(x), 2) x = t.getValue(key: [10, 20, 30])! - XCTAssertEqual(CFGetRetainCount(x), 3) + XCTAssertEqual(getRc(x), 3) t.delete(key: [10, 20, 30]) - XCTAssertEqual(CFGetRetainCount(x), 2) + XCTAssertEqual(getRc(x), 2) + } + + func testRefCountNode4() throws { + typealias Tree = ARTree + var t: _? = Tree() + t!.insert(key: [1, 2, 3], value: 10) + t!.insert(key: [2, 4, 4], value: 20) + + XCTAssertEqual(getRc(t!.root!.storage), 2) + var n4 = t!.root + XCTAssertEqual(getRc(n4!.storage), 3) + t = nil + XCTAssertEqual(getRc(n4!.storage), 2) + n4 = nil + } + + func testRefCountNode16() throws { + typealias Tree = ARTree + var t: _? = Tree() + t!.insert(key: [1, 2, 3], value: 10) + t!.insert(key: [2, 4, 4], value: 20) + t!.insert(key: [3, 2, 3], value: 30) + t!.insert(key: [4, 4, 4], value: 40) + t!.insert(key: [5, 4, 4], value: 50) + + XCTAssertEqual(getRc(t!.root!.storage), 2) + var n4 = t!.root + XCTAssertEqual(getRc(n4!.storage), 3) + t = nil + XCTAssertEqual(getRc(n4!.storage), 2) + n4 = nil } } From 485f91aa68b9cd43f3456a2eb77281c10a80f22d Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 13:33:32 -0700 Subject: [PATCH 30/67] Add clone() method to ManagedNodes --- .../ARTreeModule/Node+StringConverter.swift | 2 +- Sources/ARTreeModule/Node.swift | 2 ++ Sources/ARTreeModule/Node16.swift | 16 ++++++++++++++++ Sources/ARTreeModule/Node256.swift | 14 ++++++++++++++ Sources/ARTreeModule/Node4.swift | 16 ++++++++++++++++ Sources/ARTreeModule/Node48.swift | 19 +++++++++++++++++++ Sources/ARTreeModule/NodeLeaf.swift | 4 ++++ 7 files changed, 72 insertions(+), 1 deletion(-) diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index d27aceaa2..2f31de417 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -31,7 +31,7 @@ func indent(_ width: Int, last: Bool) -> String { } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree: CustomStringConvertible { +extension ARTreeImpl: CustomStringConvertible { public var description: String { if let node = root { return "○ " + node.prettyPrint(depth: 0, with: Spec.self) diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index f44ea18db..faa0201af 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -24,6 +24,8 @@ protocol ManagedNode { var storage: Storage { get } var type: NodeType { get } var rawNode: RawNode { get } + + func clone() -> Self } protocol InternalNode: ManagedNode { diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 9f1ca79c9..7795cc196 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -211,4 +211,20 @@ extension Node16: ManagedNode { node.count = 0 } } + + func clone() -> Self { + var node = Self.allocate() + node.copyHeader(from: self) + + self.withBody { fromKeys, fromChildren in + node.withBody { newKeys, newChildren in + for idx in 0.. Self { + let node = Self.allocate() + + self.withBody { fromChildren in + node.withBody { newChildren in + for idx in 0..<256 { + newChildren[idx] = fromChildren[idx] + } + } + } + + return node + } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 0d962d063..49b76df23 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -183,4 +183,20 @@ extension Node4: ManagedNode { node.count = 0 } } + + func clone() -> Self { + var node = Self.allocate() + node.copyHeader(from: self) + + self.withBody { fromKeys, fromChildren in + node.withBody { newKeys, newChildren in + for idx in 0.. Self { + var node = Self.allocate() + node.copyHeader(from: self) + + self.withBody { fromKeys, fromChildren in + node.withBody { newKeys, newChildren in + for idx in 0..<256 { + let slot = fromKeys[idx] + newKeys[idx] = slot + if slot != 0xFF { + newChildren[Int(slot)] = fromChildren[Int(slot)] + } + } + } + } + + return node + } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index f835b55da..057278329 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -140,4 +140,8 @@ extension NodeLeaf: ManagedNode { } } } + + func clone() -> Self { + return Self.allocate(key: key, value: value) + } } From e95ea5ff8314d9971908572be42ff0a96deba427 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 14:37:10 -0700 Subject: [PATCH 31/67] Copy-on-write for inserts and delete --- Sources/ARTreeModule/ARTree+delete.swift | 17 +++-- Sources/ARTreeModule/ARTree+insert.swift | 23 ++++-- .../ARTreeModule/Node+StringConverter.swift | 4 ++ Sources/ARTreeModule/Node.swift | 63 +++++++++++++---- Sources/ARTreeModule/Node256.swift | 3 +- Sources/ARTreeModule/RawNode.swift | 8 +++ .../ARTreeModuleTests/CopyOnWriteTests.swift | 70 +++++++++++++++++++ 7 files changed, 159 insertions(+), 29 deletions(-) create mode 100644 Tests/ARTreeModuleTests/CopyOnWriteTests.swift diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 1407da1db..58d367148 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -12,8 +12,9 @@ @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { public mutating func delete(key: Key) { - guard let node = root else { return } - switch _delete(node: node, key: key, depth: 0) { + guard var node = root else { return } + let isUnique = true + switch _delete(node: &node, key: key, depth: 0, isUniquePath: isUnique) { case .noop: return case .replaceWith(let newValue): @@ -26,9 +27,10 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(node: RawNode, + private mutating func _delete(node: inout RawNode, key: Key, - depth: Int) -> UpdateResult { + depth: Int, + isUniquePath: Bool) -> UpdateResult { if node.type == .leaf { let leaf: NodeLeaf = node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { @@ -38,6 +40,7 @@ extension ARTree { return .replaceWith(nil) } + let isUnique = isUniquePath && node.isUnique() var newDepth = depth var node: any InternalNode = node.toInternalNode() @@ -47,9 +50,9 @@ extension ARTree { newDepth += matchedBytes } - return node.updateChild(forKey: key[newDepth]) { - guard let child = $0 else { return .noop } - return _delete(node: child, key: key, depth: newDepth + 1) + return node.updateChild(forKey: key[newDepth], isUnique: isUnique) { + guard var child = $0 else { return .noop } + return _delete(node: &child, key: key, depth: newDepth + 1, isUniquePath: isUnique) } } } diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 4eba57458..94f934e62 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -21,7 +21,7 @@ extension ARTree { @discardableResult @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) public mutating func insert(key: Key, value: Value) -> Bool { - guard var (action, ref) = _findInsertNode(startNode: root!, key: key) else { return false } + guard var (action, ref) = _findInsertNode(key: key) else { return false } switch action { case .replace(_): @@ -80,11 +80,10 @@ extension ARTree { return true } - fileprivate mutating func _findInsertNode(startNode: RawNode, key: Key) - -> (InsertAction, NodeReference)? { - - var current: RawNode = startNode + fileprivate mutating func _findInsertNode(key: Key) -> (InsertAction, NodeReference)? { var depth = 0 + var isUnique = root!.isUnique() + var current = root! var ref = NodeReference(&root) while depth < key.count { @@ -95,10 +94,19 @@ extension ARTree { return (.replace(leaf), ref) } - return (.splitLeaf(leaf, depth: depth), ref) + if isUnique { + return (.splitLeaf(leaf, depth: depth), ref) + } else { + return (.splitLeaf(leaf.clone(), depth: depth), ref) + } } var node: any InternalNode = current.toInternalNode() + if !isUnique { + node = node.clone() + ref.pointee = node.rawNode + } + if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) @@ -112,11 +120,12 @@ extension ARTree { } // Find next child to continue. - guard let next = node.child(forKey: key[depth], ref: &ref) else { + guard var next = node.child(forKey: key[depth], ref: &ref) else { // No child, insert leaf within us. return (.insertInto(node, depth: depth), ref) } + isUnique = next.isUnique() depth += 1 current = next } diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index 2f31de417..021d262bf 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -65,6 +65,10 @@ extension ManagedNode { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { + func print(with: Spec.Type) -> String { + return prettyPrint(depth: 0, with: Spec.self) + } + func prettyPrint(depth: Int, with: Spec.Type) -> String { let n: any ManagedNode = toManagedNode() return n.prettyPrint(depth: depth) diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index faa0201af..b24d95b6b 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -81,31 +81,66 @@ enum UpdateResult { } extension InternalNode { - mutating func updateChild(forKey k: KeyPart, body: (RawNode?) -> UpdateResult) - -> UpdateResult { + mutating func updateChild(forKey k: KeyPart, isUnique: Bool, + body: (RawNode?) -> UpdateResult) -> UpdateResult { guard let childPosition = index(forKey: k) else { return .noop } - let ref = withChildRef(at: childPosition) { $0 } let child = child(at: childPosition) - switch body(child) { - case .noop: - return .noop - case .replaceWith(nil): - let shouldDeleteMyself = count == 1 - switch deleteChild(at: childPosition) { + + // TODO: This is ugly. Rewrite. + let action = body(child) + if isUnique { + switch action { case .noop: return .noop - case .replaceWith(nil) where shouldDeleteMyself: + case .replaceWith(nil): + let shouldDeleteMyself = count == 1 + switch deleteChild(at: childPosition) { + case .noop: + return .noop + case .replaceWith(nil) where shouldDeleteMyself: + return .replaceWith(nil) + case .replaceWith(let newValue): + return .replaceWith(newValue) + } + case .replaceWith(let newValue): + _ = withChildRef(at: childPosition) { + $0.pointee = newValue + } + + return .noop + } + } else { + switch action { + case .noop: + // Key wasn't in the tree. + return .noop + case .replaceWith(nil) where count == 1: + // Subtree was deleted, and that was our last child. Delete myself. return .replaceWith(nil) + case .replaceWith(nil): + // Clone myself and remove the child from the clone. + var myClone = clone() + switch myClone.deleteChild(at: childPosition) { + case .noop: + return .replaceWith(myClone.rawNode) + case .replaceWith(nil): + fatalError("unexpected state: should be handled in branch where count == 1") + case .replaceWith(let newValue): + // Our clone got shrunk down after delete. + return .replaceWith(newValue) + } case .replaceWith(let newValue): - return .replaceWith(newValue) + // Clone myself and update the subtree. + var myClone = clone() + _ = myClone.withChildRef(at: childPosition) { + $0.pointee = newValue + } + return .replaceWith(myClone.rawNode) } - case .replaceWith(let newValue): - ref.pointee = newValue - return .noop } } } diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 6bb6f5fc3..c489d3e7d 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -154,7 +154,8 @@ extension Node256: ManagedNode { } func clone() -> Self { - let node = Self.allocate() + var node = Self.allocate() + node.copyHeader(from: self) self.withBody { fromChildren in node.withBody { newChildren in diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index b7d1da95b..c0532bcd1 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -15,6 +15,10 @@ struct RawNode { init(from: N) { self.storage = from.storage.buf } + + init(storage: RawNodeBuffer){ + self.storage = storage + } } extension RawNode { @@ -23,6 +27,10 @@ extension RawNode { var type: NodeType { @inline(__always) get { return storage.header } } + + mutating func isUnique() -> Bool { + return isKnownUniquelyReferenced(&storage) + } } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) diff --git a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift new file mode 100644 index 000000000..aa20b3f18 --- /dev/null +++ b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 + +@testable import ARTreeModule + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +final class ARTreeCopyOnWriteTests: XCTestCase { + func testCopyOnWriteBasicInsert() throws { + var t1 = ARTree() + _ = t1.insert(key: [10, 20], value: 10) + _ = t1.insert(key: [20, 30], value: 20) + var t2 = t1 + _ = t2.insert(key: [30, 40], value: 30) + _ = t2.insert(key: [40, 50], value: 40) + + XCTAssertEqual( + t1.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "└──○ 20: 2[20, 30] -> 20") + XCTAssertEqual( + t2.description, + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "├──○ 20: 2[20, 30] -> 20\n" + + "├──○ 30: 2[30, 40] -> 30\n" + + "└──○ 40: 2[40, 50] -> 40") + } + + func testCopyOnWriteBasicDelete() throws { + var t1 = ARTree() + _ = t1.insert(key: [10, 20], value: 10) + _ = t1.insert(key: [20, 30], value: 20) + var t2 = t1 + _ = t2.insert(key: [30, 40], value: 30) + _ = t2.insert(key: [40, 50], value: 40) + var t3 = t2 + t3.delete(key: [30, 40]) + t3.delete(key: [10, 20]) + t3.delete(key: [20]) + + XCTAssertEqual( + t1.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "└──○ 20: 2[20, 30] -> 20") + XCTAssertEqual( + t2.description, + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "├──○ 20: 2[20, 30] -> 20\n" + + "├──○ 30: 2[30, 40] -> 30\n" + + "└──○ 40: 2[40, 50] -> 40") + XCTAssertEqual( + t3.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 20: 2[20, 30] -> 20\n" + + "└──○ 40: 2[40, 50] -> 40") + } +} From 06255c050c724a1e775cec3d9c1af953c1f8d8f4 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 16:57:42 -0700 Subject: [PATCH 32/67] Remove some warnings --- Sources/ARTreeModule/Node.swift | 4 ++-- Sources/ARTreeModule/Node16.swift | 2 +- Sources/ARTreeModule/Node256.swift | 2 +- Sources/ARTreeModule/Node4.swift | 2 +- Sources/ARTreeModule/Node48.swift | 2 +- Tests/ARTreeModuleTests/GetValueTests.swift | 2 +- Tests/ARTreeModuleTests/InsertTests.swift | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index b24d95b6b..2e2411466 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -107,7 +107,7 @@ extension InternalNode { return .replaceWith(newValue) } case .replaceWith(let newValue): - _ = withChildRef(at: childPosition) { + withChildRef(at: childPosition) { $0.pointee = newValue } @@ -136,7 +136,7 @@ extension InternalNode { case .replaceWith(let newValue): // Clone myself and update the subtree. var myClone = clone() - _ = myClone.withChildRef(at: childPosition) { + myClone.withChildRef(at: childPosition) { $0.pointee = newValue } return .replaceWith(myClone.rawNode) diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 7795cc196..8b202db07 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -203,7 +203,7 @@ extension Node16: ManagedNode { deinit { var node = Node16(buffer: self) let count = node.count - _ = node.withBody { _, childs in + node.withBody { _, childs in for idx in 0.. \(value)") // for (k, v) in testSetArray[0...idx] { diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 67ede7842..2aff6e0d4 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -321,7 +321,7 @@ final class ARTreeInsertTests: XCTestCase { } var total = 0 - for (k, v)in tree { + for (_, _)in tree { total += 1 } XCTAssertEqual(total, testCase.count) From 32db022d3643c4c4aedcfd20387d10afd264c45e Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 20:04:36 -0700 Subject: [PATCH 33/67] Remove some redundant constructors --- Sources/ARTreeModule/Node+Storage.swift | 5 ++--- Sources/ARTreeModule/Node.swift | 10 +++++++++- Sources/ARTreeModule/Node16.swift | 4 ---- Sources/ARTreeModule/Node256.swift | 4 ---- Sources/ARTreeModule/Node4.swift | 4 ---- Sources/ARTreeModule/Node48.swift | 4 ---- Sources/ARTreeModule/RawNode.swift | 4 ---- 7 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index 46fc67a7a..410efbd88 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -10,15 +10,14 @@ //===----------------------------------------------------------------------===// typealias RawNodeBuffer = ManagedBuffer -typealias NodeBuffer = Mn.Buffer struct NodeStorage { - var buf: NodeBuffer + var buf: Mn.Buffer } extension NodeStorage { init(raw: RawNodeBuffer) { - self.buf = raw as! NodeBuffer + self.buf = unsafeDowncast(raw, to: Mn.Buffer.self) } static func create(type: NodeType, size: Int) -> RawNodeBuffer { diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 2e2411466..2047a38a5 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -26,6 +26,14 @@ protocol ManagedNode { var rawNode: RawNode { get } func clone() -> Self + + init(storage: Storage) +} + +extension ManagedNode { + init(buffer: RawNodeBuffer) { + self.init(storage: Self.Storage(raw: buffer)) + } } protocol InternalNode: ManagedNode { @@ -54,7 +62,7 @@ protocol InternalNode: ManagedNode { } extension ManagedNode { - var rawNode: RawNode { RawNode(from: self) } + var rawNode: RawNode { RawNode(storage: self.storage.buf) } var type: NodeType { Self.type } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 8b202db07..801df7cfd 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -16,10 +16,6 @@ struct Node16 { extension Node16 { static var type: NodeType { .node16 } static var numKeys: Int { 16 } - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } } extension Node16 { diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 49d7e88e8..243c22662 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -16,10 +16,6 @@ struct Node256 { extension Node256 { static var type: NodeType { .node256 } static var numKeys: Int { 256 } - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } } extension Node256 { diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 97da9240e..d6311269e 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -16,10 +16,6 @@ struct Node4 { extension Node4 { static var type: NodeType { .node4 } static var numKeys: Int { 4 } - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } } extension Node4 { diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index 4c95416d6..231cf3e2f 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -16,10 +16,6 @@ struct Node48 { extension Node48 { static var type: NodeType { .node48 } static var numKeys: Int { 48 } - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } } extension Node48 { diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index c0532bcd1..0f677a31a 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -12,10 +12,6 @@ struct RawNode { var storage: RawNodeBuffer - init(from: N) { - self.storage = from.storage.buf - } - init(storage: RawNodeBuffer){ self.storage = storage } From 6cc2899b91eb69b0d8183d41bf6a47464a14ef73 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 21 Aug 2023 23:52:16 -0700 Subject: [PATCH 34/67] Use UnmanagedStorage for Node* functions --- Sources/ARTreeModule/ARTree+insert.swift | 35 +++-- Sources/ARTreeModule/ARTree+utils.swift | 2 +- Sources/ARTreeModule/ARTree.swift | 2 +- Sources/ARTreeModule/InternalNode+impl.swift | 6 +- Sources/ARTreeModule/Node+Storage.swift | 99 ++++++++++--- .../ARTreeModule/Node+StringConverter.swift | 14 +- .../ARTreeModule/Node+UnmanagedStorage.swift | 55 ++++++++ Sources/ARTreeModule/Node.swift | 46 +++--- Sources/ARTreeModule/Node16.swift | 118 ++++++++-------- Sources/ARTreeModule/Node256.swift | 78 ++++++----- Sources/ARTreeModule/Node4.swift | 77 +++++----- Sources/ARTreeModule/Node48.swift | 131 +++++++++--------- Sources/ARTreeModule/NodeLeaf.swift | 27 ++-- Sources/ARTreeModule/RawNode.swift | 37 +++-- Tests/ARTreeModuleTests/Node4Tests.swift | 18 +-- Tests/ARTreeModuleTests/NodeLeafTests.swift | 102 +++++++------- .../ARTreeModuleTests/TreeRefCountTests.swift | 30 +++- 17 files changed, 533 insertions(+), 344 deletions(-) create mode 100644 Sources/ARTreeModule/Node+UnmanagedStorage.swift diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 94f934e62..c7139189d 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -19,7 +19,6 @@ extension ARTree { } @discardableResult - @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) public mutating func insert(key: Key, value: Value) -> Bool { guard var (action, ref) = _findInsertNode(key: key) else { return false } @@ -29,11 +28,13 @@ extension ARTree { case .splitLeaf(let leaf, let depth): let newLeaf = Self.allocateLeaf(key: key, value: value) - var longestPrefix = leaf.longestCommonPrefix(with: newLeaf, fromIndex: depth) + var longestPrefix = newLeaf.read { + leaf.longestCommonPrefix(with: $0, fromIndex: depth) + } var newNode = Node4.allocate() _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) - _ = newNode.addChild(forKey: newLeaf.key[depth + longestPrefix], node: newLeaf) + _ = newNode.addChild(forKey: key[depth + longestPrefix], node: newLeaf) while longestPrefix > 0 { let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) @@ -46,9 +47,9 @@ extension ARTree { break } - var nextNode = Node4.allocate() - _ = nextNode.addChild(forKey: key[start - 1], node: newNode) - newNode = nextNode + var next = Node4.allocate() + _ = next.addChild(forKey: key[start - 1], node: newNode) + newNode = next } ref.pointee = newNode.rawNode // Replace child in parent. @@ -56,8 +57,7 @@ extension ARTree { case .splitNode(var node, let depth, let prefixDiff): var newNode = Node4.allocate() newNode.partialLength = prefixDiff - // TODO: Just copy min(maxPartialLength, prefixDiff) bytes - newNode.partialBytes = node.partialBytes + newNode.partialBytes = node.partialBytes // TODO: Just copy min(maxPartialLength, prefixDiff) assert( node.partialLength <= Const.maxPartialLength, @@ -71,15 +71,17 @@ extension ARTree { ref.pointee = newNode.rawNode case .insertInto(var node, let depth): - let newLeaf = Self.allocateLeaf(key: key, value: value) - if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { - ref.pointee = newNode + Self.allocateLeaf(key: key, value: value).read { newLeaf in + if case .replaceWith(let newNode) = node.addChild(forKey: key[depth], node: newLeaf) { + ref.pointee = newNode + } } } return true } + // TODO: Make sure that the node returned have fileprivate mutating func _findInsertNode(key: Key) -> (InsertAction, NodeReference)? { var depth = 0 var isUnique = root!.isUnique() @@ -97,16 +99,19 @@ extension ARTree { if isUnique { return (.splitLeaf(leaf, depth: depth), ref) } else { - return (.splitLeaf(leaf.clone(), depth: depth), ref) + let clone = leaf.clone() + ref.pointee = clone.node.rawNode + return (.splitLeaf(clone.node, depth: depth), ref) } } - var node: any InternalNode = current.toInternalNode() + if !isUnique { - node = node.clone() - ref.pointee = node.rawNode + current = current.clone(spec: Spec.self) + ref.pointee = current } + var node: any InternalNode = current.toInternalNode() if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) diff --git a/Sources/ARTreeModule/ARTree+utils.swift b/Sources/ARTreeModule/ARTree+utils.swift index db647c6ad..8ab2b6de3 100644 --- a/Sources/ARTreeModule/ARTree+utils.swift +++ b/Sources/ARTreeModule/ARTree+utils.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension ARTree { - static func allocateLeaf(key: Key, value: Value) -> NodeLeaf { + static func allocateLeaf(key: Key, value: Value) -> NodeStorage> { return NodeLeaf.allocate(key: key, value: value) } } diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 5e231364b..dd3e0d4e4 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -30,6 +30,6 @@ public struct ARTreeImpl { var root: RawNode? public init() { - self.root = Node4.allocate().rawNode + self.root = Node4.allocate().read { $0.rawNode } } } diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index fde998241..4fa9d594f 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -67,6 +67,10 @@ extension InternalNode { } } + mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult { + return addChild(forKey: k, node: node.rawNode) + } + mutating func copyHeader(from: any InternalNode) { self.storage.withHeaderPointer { header in header.pointee.count = UInt16(from.count) @@ -93,7 +97,7 @@ extension InternalNode { static func retainChildren(_ children: Children, count: Int) { for idx in 0.. -struct NodeStorage { - var buf: Mn.Buffer -} +struct NodeStorage { + var ref: Mn.Buffer + + var node: Mn { Mn(buffer: ref) } + var rawNode: RawNode { RawNode(buf: ref) } -extension NodeStorage { init(raw: RawNodeBuffer) { - self.buf = unsafeDowncast(raw, to: Mn.Buffer.self) + self.ref = unsafeDowncast(raw, to: Mn.Buffer.self) } +} - static func create(type: NodeType, size: Int) -> RawNodeBuffer { +extension NodeStorage { + static func create(type: NodeType, size: Int) -> NodeStorage { let buf = Mn.Buffer.create(minimumCapacity: size, makingHeaderWith: {_ in type }) buf.withUnsafeMutablePointerToElements { $0.initialize(repeating: 0, count: size) } - return buf + return NodeStorage(raw: buf) } -} -extension NodeStorage { - func withUnsafePointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { - return try buf.withUnsafeMutablePointerToElements { - return try body(UnsafeMutableRawPointer($0)) - } + func clone() -> Self { + read { $0.clone() } } } extension NodeStorage where Mn: InternalNode { typealias Header = Mn.Header + typealias Index = Mn.Index static func allocate() -> NodeStorage { let size = Mn.size - let buf = NodeStorage.create(type: Mn.type, size: size) - let storage = NodeStorage(raw: buf) - _ = buf.withUnsafeMutablePointerToElements { + let buf = Self.create(type: Mn.type, size: size) + _ = buf.ref.withUnsafeMutablePointerToElements { UnsafeMutableRawPointer($0).bindMemory(to: Header.self, capacity: 1) } - return storage + return buf } - func withHeaderPointer(_ body: (UnsafeMutablePointer
) throws -> R) rethrows -> R { - return try buf.withUnsafeMutablePointerToElements { - return try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Header.self)) + var type: NodeType { + read { $0.type } + } + var partialBytes: PartialBytes { + get { + read { $0.partialBytes } + } + set { + update { $0.partialBytes = newValue } } } - func withBodyPointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { - return try buf.withUnsafeMutablePointerToElements { - return try body(UnsafeMutableRawPointer($0).advanced(by: MemoryLayout
.stride)) + var partialLength: Int { + get { + read { $0.partialLength } } + set { + update { $0.partialLength = newValue } + } + } + + mutating func index(forKey k: KeyPart) -> Index? { + read { + $0.index(forKey: k) + } + } + + // TODO: Remove this so that we don't use it by mistake where `node` is invalid. + mutating func addChild(forKey k: KeyPart, node: some ArtNode) + -> UpdateResult { + + update { + $0.addChild(forKey: k, node: node.rawNode) + } + } + + mutating func addChild(forKey k: KeyPart, node: NodeStorage) + -> UpdateResult where N.Spec == Mn.Spec { + update { + $0.addChild(forKey: k, node: node.rawNode) + } + } + + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { + update { + $0.addChild(forKey: k, node: node) + } + } + + mutating func deleteChild(at index: Index) -> UpdateResult { + update { + $0.deleteChild(at: index) + } + } +} + +extension NodeStorage { + func read(_ body: (Mn) throws -> R) rethrows -> R{ + try body(self.node) + } + + func update(_ body: (inout Mn) throws -> R) rethrows -> R { + var n = self.node + return try body(&n) } } diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index 021d262bf..846e7a22f 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -42,7 +42,7 @@ extension ARTreeImpl: CustomStringConvertible { } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ManagedNode { +extension ArtNode { public var description: String { return "○ " + prettyPrint(depth: 0) } @@ -66,15 +66,23 @@ extension ManagedNode { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { func print(with: Spec.Type) -> String { - return prettyPrint(depth: 0, with: Spec.self) + return "○ " + prettyPrint(depth: 0, with: Spec.self) } func prettyPrint(depth: Int, with: Spec.Type) -> String { - let n: any ManagedNode = toManagedNode() + let n: any ArtNode = toArtNode() return n.prettyPrint(depth: depth) } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension NodeStorage { + func print() -> String { + return node.description + } +} + + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { fileprivate var partial: [UInt8] { diff --git a/Sources/ARTreeModule/Node+UnmanagedStorage.swift b/Sources/ARTreeModule/Node+UnmanagedStorage.swift new file mode 100644 index 000000000..f10bb3160 --- /dev/null +++ b/Sources/ARTreeModule/Node+UnmanagedStorage.swift @@ -0,0 +1,55 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +struct UnmanagedNodeStorage { + var ref: Unmanaged +} + +extension UnmanagedNodeStorage { + init(raw: RawNodeBuffer) { + self.ref = .passUnretained(unsafeDowncast(raw, to: Mn.Buffer.self)) + } +} + +extension UnmanagedNodeStorage { + @inlinable @inline(__always) + internal func withRaw(_ body: (Mn.Buffer) throws -> R) rethrows -> R { + try ref._withUnsafeGuaranteedRef(body) + } + + func withUnsafePointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { + try withRaw { buf in + try buf.withUnsafeMutablePointerToElements { + try body(UnsafeMutableRawPointer($0)) + } + } + } +} + +extension UnmanagedNodeStorage where Mn: InternalNode { + typealias Header = Mn.Header + + func withHeaderPointer(_ body: (UnsafeMutablePointer
) throws -> R) rethrows -> R { + try withRaw { buf in + try buf.withUnsafeMutablePointerToElements { + try body(UnsafeMutableRawPointer($0).assumingMemoryBound(to: Header.self)) + } + } + } + + func withBodyPointer(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R { + try withRaw { buf in + try buf.withUnsafeMutablePointerToElements { + try body(UnsafeMutableRawPointer($0).advanced(by: MemoryLayout
.stride)) + } + } + } +} diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index 2047a38a5..a6a473afd 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -12,12 +12,12 @@ public typealias KeyPart = UInt8 public typealias Key = [KeyPart] -protocol ManagedNode { +protocol ArtNode { associatedtype Spec: ARTreeSpec associatedtype Buffer: RawNodeBuffer typealias Value = Spec.Value - typealias Storage = NodeStorage + typealias Storage = UnmanagedNodeStorage static var type: NodeType { get } @@ -25,18 +25,18 @@ protocol ManagedNode { var type: NodeType { get } var rawNode: RawNode { get } - func clone() -> Self + func clone() -> NodeStorage init(storage: Storage) } -extension ManagedNode { +extension ArtNode { init(buffer: RawNodeBuffer) { self.init(storage: Self.Storage(raw: buffer)) } } -protocol InternalNode: ManagedNode { +protocol InternalNode: ArtNode { typealias Value = Spec.Value typealias Index = Int typealias Header = InternalNodeHeader @@ -55,14 +55,16 @@ protocol InternalNode: ManagedNode { func child(forKey k: KeyPart) -> RawNode? func child(at: Index) -> RawNode? - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult + mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult + mutating func deleteChild(at index: Index) -> UpdateResult mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R } -extension ManagedNode { - var rawNode: RawNode { RawNode(storage: self.storage.buf) } +extension ArtNode { + var rawNode: RawNode { RawNode(buf: self.storage.ref.takeUnretainedValue()) } var type: NodeType { Self.type } } @@ -131,23 +133,25 @@ extension InternalNode { return .replaceWith(nil) case .replaceWith(nil): // Clone myself and remove the child from the clone. - var myClone = clone() - switch myClone.deleteChild(at: childPosition) { - case .noop: - return .replaceWith(myClone.rawNode) - case .replaceWith(nil): - fatalError("unexpected state: should be handled in branch where count == 1") - case .replaceWith(let newValue): - // Our clone got shrunk down after delete. - return .replaceWith(newValue) + return self.clone().update { myClone in + switch myClone.deleteChild(at: childPosition) { + case .noop: + return .replaceWith(myClone.rawNode) + case .replaceWith(nil): + fatalError("unexpected state: should be handled in branch where count == 1") + case .replaceWith(let newValue): + // Our clone got shrunk down after delete. + return .replaceWith(newValue) + } } case .replaceWith(let newValue): // Clone myself and update the subtree. - var myClone = clone() - myClone.withChildRef(at: childPosition) { - $0.pointee = newValue + return clone().update { clone in + clone.withChildRef(at: childPosition) { + $0.pointee = newValue + } + return .replaceWith(clone.rawNode) } - return .replaceWith(myClone.rawNode) } } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 801df7cfd..71ac62407 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -19,58 +19,65 @@ extension Node16 { } extension Node16 { - static func allocate() -> Self { - let buf: NodeStorage = NodeStorage.allocate() - let node = Self(storage: buf) - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + static func allocate() -> NodeStorage { + let storage = NodeStorage.allocate() + + storage.update { node in + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } } - return node - } - static func allocate(copyFrom: Node4) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) + return storage + } - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - UnsafeMutableRawBufferPointer(newKeys).copyBytes(from: fromKeys) - UnsafeMutableRawBufferPointer(newChilds).copyBytes( - from: UnsafeMutableRawBufferPointer(fromChilds)) - Self.retainChildren(newChilds, count: node.count) + static func allocate(copyFrom: Node4) -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + UnsafeMutableRawBufferPointer(newKeys).copyBytes(from: fromKeys) + UnsafeMutableRawBufferPointer(newChilds).copyBytes( + from: UnsafeMutableRawBufferPointer(fromChilds)) + Self.retainChildren(newChilds, count: node.count) + } } } - return node + return storage } - static func allocate(copyFrom: Node48) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) - - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - var slot = 0 - for key: UInt8 in 0...255 { - let childPosition = Int(fromKeys[Int(key)]) - if childPosition == 0xFF { - continue + static func allocate(copyFrom: Node48) -> NodeStorage { + let storage = NodeStorage.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + var slot = 0 + for key: UInt8 in 0...255 { + let childPosition = Int(fromKeys[Int(key)]) + if childPosition == 0xFF { + continue + } + + newKeys[slot] = key + newChilds[slot] = fromChilds[childPosition] + slot += 1 } - newKeys[slot] = key - newChilds[slot] = fromChilds[childPosition] - slot += 1 + assert(slot == node.count) + Self.retainChildren(newChilds, count: node.count) } - - assert(slot == node.count) - Self.retainChildren(newChilds, count: node.count) } } - return node + return storage } } @@ -145,21 +152,22 @@ extension Node16: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody {keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k - childs[slot] = node.rawNode + childs[slot] = node count += 1 } return .noop } else { - var newNode = Node48.allocate(copyFrom: self) - _ = newNode.addChild(forKey: k, node: node) - return .replaceWith(newNode.rawNode) + return Node48.allocate(copyFrom: self).update { newNode in + _ = newNode.addChild(forKey: k, node: node) + return .replaceWith(newNode.rawNode) + } } } @@ -178,7 +186,7 @@ extension Node16: InternalNode { if count == 3 { // Shrink to Node4. let newNode = Node4.allocate(copyFrom: self) - return .replaceWith(newNode.rawNode) + return .replaceWith(newNode.node.rawNode) } return .noop @@ -194,7 +202,7 @@ extension Node16: InternalNode { } } -extension Node16: ManagedNode { +extension Node16: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node16(buffer: self) @@ -208,19 +216,21 @@ extension Node16: ManagedNode { } } - func clone() -> Self { - var node = Self.allocate() - node.copyHeader(from: self) + func clone() -> NodeStorage { + let storage = Self.allocate() - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0.. Node256 { - let buf: NodeStorage = NodeStorage.allocate() - let node = Self(storage: buf) - _ = node.withBody { childs in - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + static func allocate() -> NodeStorage { + let storage = NodeStorage.allocate() + + storage.update { node in + _ = node.withBody { childs in + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } } - return node - } - static func allocate(copyFrom: Node48) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) + return storage + } - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newChilds in - for key in 0..<256 { - let slot = Int(fromKeys[key]) - if slot < 0xFF { - newChilds[key] = fromChilds[slot] + static func allocate(copyFrom: Node48) -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newChilds in + for key in 0..<256 { + let slot = Int(fromKeys[key]) + if slot < 0xFF { + newChilds[key] = fromChilds[slot] + } } - } - Self.retainChildren(newChilds, count: Self.numKeys) + Self.retainChildren(newChilds, count: Self.numKeys) + } } + assert(node.count == 48, "should have exactly 48 childs") } - - assert(node.count == 48, "should have exactly 48 childs") - return node + + return storage } } @@ -99,10 +104,10 @@ extension Node256: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { return withBody { childs in assert(childs[Int(k)] == nil, "node for key \(k) already exists") - childs[Int(k)] = node.rawNode + childs[Int(k)] = node count += 1 return .noop } @@ -115,7 +120,7 @@ extension Node256: InternalNode { if count == 40 { let newNode = Node48.allocate(copyFrom: self) - return .replaceWith(newNode.rawNode) + return .replaceWith(newNode.node.rawNode) } return .noop @@ -129,13 +134,10 @@ extension Node256: InternalNode { return body(ref) } } - - static func deinitialize(_ storage: NodeStorage) { - } } -extension Node256: ManagedNode { +extension Node256: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node256(buffer: self) @@ -149,18 +151,20 @@ extension Node256: ManagedNode { } } - func clone() -> Self { - var node = Self.allocate() - node.copyHeader(from: self) + func clone() -> NodeStorage { + let storage = Self.allocate() - self.withBody { fromChildren in - node.withBody { newChildren in - for idx in 0..<256 { - newChildren[idx] = fromChildren[idx] + storage.update { node in + node.copyHeader(from: self) + self.withBody { fromChildren in + node.withBody { newChildren in + for idx in 0..<256 { + newChildren[idx] = fromChildren[idx] + } } } } - return node + return storage } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index d6311269e..fb84047e6 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -19,32 +19,39 @@ extension Node4 { } extension Node4 { - static func allocate() -> Self { - let buf: NodeStorage = NodeStorage.allocate() - let node = Self(storage: buf) - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + static func allocate() -> NodeStorage { + let storage = NodeStorage.allocate() + + storage.update { node in + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } } - return node + + return storage } - static func allocate(copyFrom: Node16) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) - node.withBody { newKeys, newChilds in - copyFrom.withBody { fromKeys, fromChilds in - UnsafeMutableRawBufferPointer(newKeys).copyBytes( - from: UnsafeBufferPointer(rebasing: fromKeys[0..) -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + node.withBody { newKeys, newChilds in + copyFrom.withBody { fromKeys, fromChilds in + UnsafeMutableRawBufferPointer(newKeys).copyBytes( + from: UnsafeBufferPointer(rebasing: fromKeys[0..) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { withBody { keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) keys[slot] = k - childs[slot] = node.rawNode + childs[slot] = node count += 1 } return .noop @@ -166,7 +173,7 @@ extension Node4: InternalNode { } } -extension Node4: ManagedNode { +extension Node4: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node4(buffer: self) @@ -180,19 +187,21 @@ extension Node4: ManagedNode { } } - func clone() -> Self { - var node = Self.allocate() - node.copyHeader(from: self) - - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0.. NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: self) + self.withBody { fromKeys, fromChildren in + node.withBody { newKeys, newChildren in + for idx in 0.. Self { - let buf: NodeStorage = NodeStorage.allocate() - let node = Self(storage: buf) - - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) - - for idx in 0..<256 { - keys[idx] = 0xFF + static func allocate() -> NodeStorage { + let storage = NodeStorage.allocate() + + storage.update { node in + node.withBody { keys, childs in + UnsafeMutableRawPointer(keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + + for idx in 0..<256 { + keys[idx] = 0xFF + } } } - return node + return storage } - static func allocate(copyFrom: Node16) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) + static func allocate(copyFrom: Node16) -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + copyFrom.withBody { fromKeys, fromChilds in + node.withBody { newKeys, newChilds in + UnsafeMutableRawBufferPointer(newChilds).copyBytes( + from: UnsafeMutableRawBufferPointer(fromChilds)) + for (idx, key) in fromKeys.enumerated() { + newKeys[Int(key)] = UInt8(idx) + } - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - UnsafeMutableRawBufferPointer(newChilds).copyBytes( - from: UnsafeMutableRawBufferPointer(fromChilds)) - for (idx, key) in fromKeys.enumerated() { - newKeys[Int(key)] = UInt8(idx) + Self.retainChildren(newChilds, count: node.count) } - - Self.retainChildren(newChilds, count: node.count) } } - return node + return storage } - static func allocate(copyFrom: Node256) -> Self { - var node = Self.allocate() - node.copyHeader(from: copyFrom) - - copyFrom.withBody { fromChilds in - node.withBody { newKeys, newChilds in - var slot = 0 - for (key, child) in fromChilds.enumerated() { - if child == nil { - continue + static func allocate(copyFrom: Node256) -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: copyFrom) + copyFrom.withBody { fromChilds in + node.withBody { newKeys, newChilds in + var slot = 0 + for (key, child) in fromChilds.enumerated() { + if child == nil { + continue + } + + newKeys[key] = UInt8(slot) + newChilds[slot] = child + slot += 1 } - newKeys[key] = UInt8(slot) - newChilds[slot] = child - slot += 1 + Self.retainChildren(newChilds, count: node.count) } - - Self.retainChildren(newChilds, count: node.count) } } - return node + return storage } } @@ -141,7 +146,7 @@ extension Node48: InternalNode { } } - mutating func addChild(forKey k: KeyPart, node: any ManagedNode) -> UpdateResult { + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if count < Self.numKeys { withBody { keys, childs in assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") @@ -152,15 +157,16 @@ extension Node48: InternalNode { } keys[Int(k)] = KeyPart(slot) - childs[slot] = node.rawNode + childs[slot] = node } self.count += 1 return .noop } else { - var newNode = Node256.allocate(copyFrom: self) - _ = newNode.addChild(forKey: k, node: node) - return .replaceWith(newNode.rawNode) + return Node256.allocate(copyFrom: self).update { newNode in + _ = newNode.addChild(forKey: k, node: node) + return .replaceWith(newNode.rawNode) + } } } @@ -193,7 +199,7 @@ extension Node48: InternalNode { // 6. Shrink the node to Node16 if needed. if count == 13 { let newNode = Node16.allocate(copyFrom: self) - return .replaceWith(newNode.rawNode) + return .replaceWith(newNode.node.rawNode) } return .noop @@ -219,12 +225,9 @@ extension Node48: InternalNode { return body(ref) } } - - static func deinitialize(_ storage: NodeStorage) { - } } -extension Node48: ManagedNode { +extension Node48: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node48(buffer: self) @@ -238,22 +241,24 @@ extension Node48: ManagedNode { } } - func clone() -> Self { - var node = Self.allocate() - node.copyHeader(from: self) - - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0..<256 { - let slot = fromKeys[idx] - newKeys[idx] = slot - if slot != 0xFF { - newChildren[Int(slot)] = fromChildren[Int(slot)] + func clone() -> NodeStorage { + let storage = Self.allocate() + + storage.update { node in + node.copyHeader(from: self) + self.withBody { fromKeys, fromChildren in + node.withBody { newKeys, newChildren in + for idx in 0..<256 { + let slot = fromKeys[idx] + newKeys[idx] = slot + if slot != 0xFF { + newChildren[Int(slot)] = fromChildren[Int(slot)] + } } } } } - return node + return storage } } diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 057278329..57043314f 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -16,27 +16,24 @@ struct NodeLeaf { extension NodeLeaf { static var type: NodeType { .leaf } - - init(buffer: RawNodeBuffer) { - self.init(storage: Storage(raw: buffer)) - } } extension NodeLeaf { - static func allocate(key: Key, value: Value) -> Self { + static func allocate(key: Key, value: Value) -> NodeStorage { let size = MemoryLayout.stride + key.count + MemoryLayout.stride - let buf = NodeStorage.create(type: .leaf, size: size) - var leaf = Self(buffer: buf) + let storage = NodeStorage.create(type: .leaf, size: size) - leaf.keyLength = key.count - leaf.withKeyValue { keyPtr, valuePtr in - key.withUnsafeBytes { - UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) + storage.update { leaf in + leaf.keyLength = key.count + leaf.withKeyValue { keyPtr, valuePtr in + key.withUnsafeBytes { + UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) + } + valuePtr.pointee = value } - valuePtr.pointee = value } - return leaf + return storage } } @@ -132,7 +129,7 @@ extension NodeLeaf { } } -extension NodeLeaf: ManagedNode { +extension NodeLeaf: ArtNode { final class Buffer: RawNodeBuffer { deinit { _ = NodeLeaf(buffer: self).withValue { @@ -141,7 +138,7 @@ extension NodeLeaf: ManagedNode { } } - func clone() -> Self { + func clone() -> NodeStorage { return Self.allocate(key: key, value: value) } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 0f677a31a..843c9769d 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -10,10 +10,10 @@ //===----------------------------------------------------------------------===// struct RawNode { - var storage: RawNodeBuffer + var buf: RawNodeBuffer - init(storage: RawNodeBuffer){ - self.storage = storage + init(buf: RawNodeBuffer){ + self.buf = buf } } @@ -21,26 +21,41 @@ extension RawNode { typealias SlotRef = UnsafeMutablePointer var type: NodeType { - @inline(__always) get { return storage.header } + @inline(__always) get { return buf.header } } mutating func isUnique() -> Bool { - return isKnownUniquelyReferenced(&storage) + return isKnownUniquelyReferenced(&buf) } } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { + func clone(spec: Spec.Type) -> RawNode { + switch type { + case .node4: + return NodeStorage>(raw: buf).clone().rawNode + case .node16: + return NodeStorage>(raw: buf).clone().rawNode + case .node48: + return NodeStorage>(raw: buf).clone().rawNode + case .node256: + return NodeStorage>(raw: buf).clone().rawNode + case .leaf: + return NodeStorage>(raw: buf).clone().rawNode + } + } + func toInternalNode() -> any InternalNode { switch type { case .node4: - return Node4(buffer: storage) + return Node4(buffer: buf) case .node16: - return Node16(buffer: storage) + return Node16(buffer: buf) case .node48: - return Node48(buffer: storage) + return Node48(buffer: buf) case .node256: - return Node256(buffer: storage) + return Node256(buffer: buf) default: assert(false, "leaf nodes are not internal nodes") } @@ -48,10 +63,10 @@ extension RawNode { func toLeafNode() -> NodeLeaf { assert(type == .leaf) - return NodeLeaf(buffer: storage) + return NodeLeaf(buffer: buf) } - func toManagedNode() -> any ManagedNode { + func toArtNode() -> any ArtNode { switch type { case .leaf: return toLeafNode() diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 4269e13f0..56b59ca94 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -23,24 +23,24 @@ struct Tree { } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension InternalNode { - mutating func addChildReturn(forKey k: KeyPart, node: any ManagedNode) - -> (any ManagedNode)? { +extension NodeStorage where Mn: InternalNode { + mutating func addChildReturn(forKey k: KeyPart, + node: NodeStorage>) -> RawNode? { switch addChild(forKey: k, node: node) { case .noop: - return self + return self.rawNode case .replaceWith(let newValue): - return newValue?.toManagedNode() + return newValue } } - mutating func deleteChildReturn(at idx: Index) -> (any ManagedNode)? { + mutating func deleteChildReturn(at idx: Index) -> RawNode? { switch deleteChild(at: idx) { case .noop: - return self + return self.rawNode case .replaceWith(let newValue): - return newValue?.toManagedNode() + return newValue } } } @@ -155,7 +155,7 @@ final class ARTreeNode4Tests: XCTestCase { let newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) XCTAssertEqual( - newNode!.description, + newNode!.print(with: T.Spec.self), "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + "├──○ 2: 1[2] -> [2]\n" + diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index 3eb176e56..5e63bdcf5 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -13,6 +13,7 @@ import XCTest @testable import ARTreeModule +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeNodeLeafTests: XCTestCase { func testLeafBasic() throws { typealias L = NodeLeaf> @@ -29,66 +30,67 @@ final class ARTreeNodeLeafTests: XCTestCase { func testLeafKeyEquals() throws { typealias L = NodeLeaf> let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) - XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 50])) - XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30])) - XCTAssertFalse(leaf1.keyEquals(with: [10, 20, 30, 40, 50])) - XCTAssertTrue(leaf1.keyEquals(with: [10, 20, 30, 40])) + XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30, 50])) + XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30])) + XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30, 40, 50])) + XCTAssertTrue(leaf1.node.keyEquals(with: [10, 20, 30, 40])) } func testCasts() throws { typealias L = NodeLeaf> let leaf = L.allocate(key: [10, 20, 30, 40], value: [0]) - XCTAssertEqual(leaf.key, [10, 20, 30, 40]) - XCTAssertEqual(leaf.value, [0]) + XCTAssertEqual(leaf.node.key, [10, 20, 30, 40]) + XCTAssertEqual(leaf.node.value, [0]) } func testLeafLcp() throws { typealias L = NodeLeaf> let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [0, 1, 2, 3], value: [0]), - fromIndex: 0), - 0) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [0], value: [0]), - fromIndex: 0), - 0) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [0, 1], value: [0]), - fromIndex: 0), - 0) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [10, 1], value: [0]), - fromIndex: 0), - 1) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [10, 20], value: [0]), - fromIndex: 0), - 2) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [10, 20], value: [0]), - fromIndex: 1), - 1) - XCTAssertEqual( - leaf1.longestCommonPrefix( - with: L.allocate(key: [10, 20], value: [0]), - fromIndex: 2), - 0) - // Breaks the contract, so its OK that these fail. - // XCTAssertEqual( - // leaf1.longestCommonPrefix(with: L.allocate(key: [], value: [0]), - // fromIndex: 0), - // 0) - // XCTAssertEqual( - // leaf1.longestCommonPrefix(with: L.allocate(key: [10], value: [0]), - // fromIndex: 2), - // 0) + L.allocate(key: [0, 1, 2, 3], value: [0]).read { other in + XCTAssertEqual( + leaf1.node.longestCommonPrefix( + with: other, + fromIndex: 0), + 0) + } + + L.allocate(key: [0], value: [0]).read { other in + XCTAssertEqual( + leaf1.node.longestCommonPrefix( + with:other, + fromIndex: 0), + 0) + } + L.allocate(key: [0, 1], value: [0]).read { other in + XCTAssertEqual( + leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 0) + } + L.allocate(key: [10, 1], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 1) + } + L.allocate(key: [10, 20], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 2) + } + L.allocate(key: [10, 20], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 1), + 1) + } + L.allocate(key: [10, 20], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 2), + 0) + } + // // Breaks the contract, so its OK that these fail. + // // XCTAssertEqual( + // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [], value: [0]), + // // fromIndex: 0), + // // 0) + // // XCTAssertEqual( + // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [10], value: [0]), + // // fromIndex: 2), + // // 0) } } diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 17bc12248..c37a66baf 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -49,11 +49,11 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [1, 2, 3], value: 10) t!.insert(key: [2, 4, 4], value: 20) - XCTAssertEqual(getRc(t!.root!.storage), 2) + XCTAssertEqual(getRc(t!.root!.buf), 2) var n4 = t!.root - XCTAssertEqual(getRc(n4!.storage), 3) + XCTAssertEqual(getRc(n4!.buf), 3) t = nil - XCTAssertEqual(getRc(n4!.storage), 2) + XCTAssertEqual(getRc(n4!.buf), 2) n4 = nil } @@ -66,11 +66,29 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [4, 4, 4], value: 40) t!.insert(key: [5, 4, 4], value: 50) - XCTAssertEqual(getRc(t!.root!.storage), 2) + XCTAssertEqual(getRc(t!.root!.buf), 2) var n4 = t!.root - XCTAssertEqual(getRc(n4!.storage), 3) + XCTAssertEqual(getRc(n4!.buf), 3) t = nil - XCTAssertEqual(getRc(n4!.storage), 2) + XCTAssertEqual(getRc(n4!.buf), 2) n4 = nil } + + func testRefCountStorage() throws { + typealias Tree = ARTree + let node = Node4.allocate() + let count0 = getRc(node.rawNode.buf) + + let a = node.node + let count1 = getRc(node.rawNode.buf) + XCTAssertEqual(count1, count0) + + let b = node.node + let count2 = getRc(node.rawNode.buf) + XCTAssertEqual(count2, count1) + + let c = node.node.rawNode + let count3 = getRc(node.rawNode.buf) + XCTAssertEqual(count3, count2 + 1) + } } From 0953a32917faed71970e828657ded471e95900c1 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Tue, 22 Aug 2023 20:16:58 -0700 Subject: [PATCH 35/67] Start adding tests for checking if we optimizing for unique references when we can --- Sources/ARTreeModule/ARTree+delete.swift | 2 ++ Sources/ARTreeModule/ARTree+insert.swift | 3 +++ Sources/ARTreeModule/Consts and Specs.swift | 1 + Tests/ARTreeModuleTests/DeleteTests.swift | 8 ++++++++ Tests/ARTreeModuleTests/InsertTests.swift | 8 ++++++++ 5 files changed, 22 insertions(+) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 58d367148..6ab45d383 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -31,6 +31,8 @@ extension ARTree { key: Key, depth: Int, isUniquePath: Bool) -> UpdateResult { + assert(!Const.testCheckUnique || isUniquePath, "unique path is expected in this test") + if node.type == .leaf { let leaf: NodeLeaf = node.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index c7139189d..568e8630b 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -89,6 +89,9 @@ extension ARTree { var ref = NodeReference(&root) while depth < key.count { + assert(!Const.testCheckUnique || isUnique, + "unique path is expected in this test, depth=\(depth)") + // Reached leaf already, replace it with a new node, or update the existing value. if current.type == .leaf { let leaf: NodeLeaf = current.toLeafNode() diff --git a/Sources/ARTreeModule/Consts and Specs.swift b/Sources/ARTreeModule/Consts and Specs.swift index 1631e7e5b..291868024 100644 --- a/Sources/ARTreeModule/Consts and Specs.swift +++ b/Sources/ARTreeModule/Consts and Specs.swift @@ -11,6 +11,7 @@ struct Const { static let maxPartialLength = 8 + static var testCheckUnique = false } public protocol ARTreeSpec { diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index a4f7fce9b..2ef537c5c 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -15,6 +15,14 @@ import XCTest @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeDeleteTests: XCTestCase { + override func setUp() { + Const.testCheckUnique = true + } + + override func tearDown() { + Const.testCheckUnique = false + } + func testDeleteBasic() throws { var t = ARTree<[UInt8]>() t.insert(key: [10, 20, 30], value: [11, 21, 31]) diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 2aff6e0d4..175add6dc 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -15,6 +15,14 @@ import XCTest @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeInsertTests: XCTestCase { + override func setUp() { + Const.testCheckUnique = true + } + + override func tearDown() { + Const.testCheckUnique = false + } + func testInsertBasic() throws { var t = ARTree<[UInt8]>() t.insert(key: [10, 20, 30], value: [11, 21, 31]) From da71164dc331e646ec7d3354352e888628472e77 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 23 Aug 2023 02:51:28 -0700 Subject: [PATCH 36/67] Enforce no-copy for copy in unique tree/subtree inserts --- Sources/ARTreeModule/ARTree+delete.swift | 2 +- Sources/ARTreeModule/ARTree+insert.swift | 59 +++++++++++--------- Sources/ARTreeModule/ARTree.swift | 2 +- Sources/ARTreeModule/InternalNode+impl.swift | 32 ++++++----- Sources/ARTreeModule/Node.swift | 4 +- Sources/ARTreeModule/Node256.swift | 2 +- Sources/ARTreeModule/RawNode.swift | 4 +- 7 files changed, 58 insertions(+), 47 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 6ab45d383..341e87a9c 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -42,7 +42,7 @@ extension ARTree { return .replaceWith(nil) } - let isUnique = isUniquePath && node.isUnique() + let isUnique = isUniquePath && node.isUnique var newDepth = depth var node: any InternalNode = node.toInternalNode() diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 568e8630b..35da994c9 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -84,37 +84,22 @@ extension ARTree { // TODO: Make sure that the node returned have fileprivate mutating func _findInsertNode(key: Key) -> (InsertAction, NodeReference)? { var depth = 0 - var isUnique = root!.isUnique() - var current = root! + var current: any ArtNode = root!.toArtNode() + var isUnique = isKnownUniquelyReferenced(&root!.buf) var ref = NodeReference(&root) - while depth < key.count { + while current.type != .leaf && depth < key.count { assert(!Const.testCheckUnique || isUnique, "unique path is expected in this test, depth=\(depth)") - // Reached leaf already, replace it with a new node, or update the existing value. - if current.type == .leaf { - let leaf: NodeLeaf = current.toLeafNode() - if leaf.keyEquals(with: key) { - return (.replace(leaf), ref) - } - - if isUnique { - return (.splitLeaf(leaf, depth: depth), ref) - } else { - let clone = leaf.clone() - ref.pointee = clone.node.rawNode - return (.splitLeaf(clone.node, depth: depth), ref) - } - } - - if !isUnique { - current = current.clone(spec: Spec.self) - ref.pointee = current + // TODO: Why making this one-liner crashes? + let clone = current.rawNode.clone(spec: Spec.self) + current = clone.toArtNode() + ref.pointee = current.rawNode } - var node: any InternalNode = current.toInternalNode() + var node: any InternalNode = current.rawNode.toInternalNode() if node.partialLength > 0 { let partialLength = node.partialLength let prefixDiff = node.prefixMismatch(withKey: key, fromIndex: depth) @@ -128,16 +113,36 @@ extension ARTree { } // Find next child to continue. - guard var next = node.child(forKey: key[depth], ref: &ref) else { - // No child, insert leaf within us. + guard let (next, _isUnique) = + (node.maybeReadChild(forKey: key[depth], ref: &ref) { ($0, $1) }) else { return (.insertInto(node, depth: depth), ref) } - isUnique = next.isUnique() depth += 1 current = next + isUnique = _isUnique + } + + assert(current.type == .leaf) + // Reached leaf already, replace it with a new node, or update the existing value. + if current.type == .leaf { + assert(!Const.testCheckUnique || isUnique, + "unique path is expected in this test, depth=\(depth)") + + let leaf: NodeLeaf = current.rawNode.toLeafNode() + if leaf.keyEquals(with: key) { + return (.replace(leaf), ref) + } + + if isUnique { + return (.splitLeaf(leaf, depth: depth), ref) + } else { + let clone = leaf.clone() + ref.pointee = clone.node.rawNode + return (.splitLeaf(clone.node, depth: depth), ref) + } } - return nil + fatalError("unexpected state") } } diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index dd3e0d4e4..5d2b77d72 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -24,7 +24,7 @@ public typealias ARTree = ARTreeImpl> public struct ARTreeImpl { - public typealias Spec = DefaultSpec + public typealias Spec = Spec public typealias Value = Spec.Value var root: RawNode? diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift index 4fa9d594f..08b1c11da 100644 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ b/Sources/ARTreeModule/InternalNode+impl.swift @@ -54,19 +54,6 @@ extension InternalNode { return index(forKey: k).flatMap { child(at: $0) } } - mutating func child(forKey k: KeyPart, ref: inout NodeReference) -> RawNode? { - if count == 0 { - return nil - } - - return index(forKey: k).flatMap { index in - self.withChildRef(at: index) { ptr in - ref = NodeReference(ptr) - return ptr.pointee - } - } - } - mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult { return addChild(forKey: k, node: node.rawNode) } @@ -102,3 +89,22 @@ extension InternalNode { } } } + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension InternalNode { + mutating func maybeReadChild(forKey k: KeyPart, + ref: inout NodeReference, + _ body: (any ArtNode, Bool) -> R) -> R? { + if count == 0 { + return nil + } + + return index(forKey: k).flatMap { index in + self.withChildRef(at: index) { ptr in + ref = NodeReference(ptr) + return body(ptr.pointee!.toArtNode(), ptr.pointee!.isUnique) + } + } + } + +} diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift index a6a473afd..aeb5ee16b 100644 --- a/Sources/ARTreeModule/Node.swift +++ b/Sources/ARTreeModule/Node.swift @@ -52,8 +52,8 @@ protocol InternalNode: ArtNode { func index() -> Index? func next(index: Index) -> Index? - func child(forKey k: KeyPart) -> RawNode? - func child(at: Index) -> RawNode? + func child(forKey k: KeyPart) -> RawNode? // TODO: Remove + func child(at: Index) -> RawNode? // TODO: Remove mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index 55abc4773..c2f978981 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -51,7 +51,7 @@ extension Node256 { } assert(node.count == 48, "should have exactly 48 childs") } - + return storage } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 843c9769d..b072c61b6 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -24,8 +24,8 @@ extension RawNode { @inline(__always) get { return buf.header } } - mutating func isUnique() -> Bool { - return isKnownUniquelyReferenced(&buf) + var isUnique: Bool { + mutating get { isKnownUniquelyReferenced(&buf) } } } From 0ff4d4c6b4ae029b9ff3c3f954272aa8bbe0c6be Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 23 Aug 2023 07:55:40 -0700 Subject: [PATCH 37/67] Add optional printaddress to string description --- Sources/ARTreeModule/ARTree.swift | 2 +- Sources/ARTreeModule/Consts and Specs.swift | 4 +++- .../ARTreeModule/Node+StringConverter.swift | 19 ++++++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 5d2b77d72..271fc8c7f 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -27,7 +27,7 @@ public struct ARTreeImpl { public typealias Spec = Spec public typealias Value = Spec.Value - var root: RawNode? + internal var root: RawNode? public init() { self.root = Node4.allocate().read { $0.rawNode } diff --git a/Sources/ARTreeModule/Consts and Specs.swift b/Sources/ARTreeModule/Consts and Specs.swift index 291868024..33c7a81db 100644 --- a/Sources/ARTreeModule/Consts and Specs.swift +++ b/Sources/ARTreeModule/Consts and Specs.swift @@ -9,9 +9,11 @@ // //===----------------------------------------------------------------------===// -struct Const { +internal struct Const { static let maxPartialLength = 8 static var testCheckUnique = false + static var testPrintRc = false + static var testPrintAddr = false } public protocol ARTreeSpec { diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index 846e7a22f..b658a104f 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -9,6 +9,10 @@ // //===----------------------------------------------------------------------===// +#if !COLLECTIONS_SINGLE_MODULE +import _CollectionsUtilities +#endif + protocol NodePrettyPrinter { func print() -> String func prettyPrint(depth: Int) -> String @@ -98,14 +102,16 @@ extension InternalNode { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension NodeLeaf: NodePrettyPrinter { func prettyPrint(depth: Int) -> String { - return "\(self.keyLength)\(self.key) -> \(self.value)" + let addr = Const.testPrintAddr ? "\(_addressString(for: self.rawNode.buf))" : "" + return "\(addr)\(self.keyLength)\(self.key) -> \(self.value)" } } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension Node4: NodePrettyPrinter { func prettyPrint(depth: Int) -> String { - var output = "Node4 {childs=\(count), partial=\(partial)}\n" + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node4\(addr){childs=\(count), partial=\(partial)}\n" withBody { keys, childs in for idx in 0.. String { - var output = "Node16 {childs=\(count), partial=\(partial)}\n" + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node16\(addr){childs=\(count), partial=\(partial)}\n" withBody { keys, childs in for idx in 0.. String { - var output = "Node48 {childs=\(count), partial=\(partial)}\n" + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node48\(addr){childs=\(count), partial=\(partial)}\n" var total = 0 withBody { keys, childs in for (key, slot) in keys.enumerated() { @@ -173,7 +181,8 @@ extension Node48: NodePrettyPrinter { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension Node256: NodePrettyPrinter { func prettyPrint(depth: Int) -> String { - var output = "Node256 {childs=\(count), partial=\(partial)}\n" + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node256\(addr){childs=\(count), partial=\(partial)}\n" var total = 0 withBody { childs in for (key, child) in childs.enumerated() { From 30bbb3626bc2e20dad1c10bd92733042195d51e1 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 23 Aug 2023 09:54:17 -0700 Subject: [PATCH 38/67] Add some test cases for delete --- Sources/ARTreeModule/ARTree+insert.swift | 5 + Sources/ARTreeModule/ARTree.swift | 44 +++++--- .../ARTreeModuleTests/CopyOnWriteTests.swift | 106 ++++++++++++++++++ Tests/ARTreeModuleTests/DeleteTests.swift | 23 +++- 4 files changed, 164 insertions(+), 14 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 35da994c9..189947850 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -83,6 +83,11 @@ extension ARTree { // TODO: Make sure that the node returned have fileprivate mutating func _findInsertNode(key: Key) -> (InsertAction, NodeReference)? { + if root == nil { + // NOTE: Should we just create leaf? Likely tree will have more items anyway. + root = Node4.allocate().read { $0.rawNode } + } + var depth = 0 var current: any ArtNode = root!.toArtNode() var isUnique = isKnownUniquelyReferenced(&root!.buf) diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 271fc8c7f..2fdccdb99 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -10,18 +10,33 @@ //===----------------------------------------------------------------------===// // TODO: -// * Range delete. -// * Delete node should delete all sub-childs (for range deletes) -// * Confirm to Swift dictionary protocols. -// * Generic/any serializable type? -// * Binary search Node16. -// * SIMD instructions for Node4. -// * Replace some loops with memcpy. -// * Better test cases. -// * Fuzz testing. -// * Leaf don't need to store entire key. - -public typealias ARTree = ARTreeImpl> +// - Ranged Operations +// - Subsequence Delete +// - Subsequence Iteration +// - Bulk insert +// - Merge operation +// - Disk-backed storage +// - Confirm to Swift Dictionary protocol +// - Optimizations +// - SIMD for Node4 +// - Binary Search for Node16 +// - Replace for loops with memcpy +// - Reduce refcount traffic +// - Leaf shouldn’t store entire key +// - Testing +// - Tests around edge cases in general +// - Tests around prefixes greater than maxPrefixLimit +// - Fuzz testing +// - Instrumentation and Profiling +// - Track number of allocations and deallocations +// - Write some performance benchmarks to compare against other data-structures +// - Use some well known datasets +// - Cost of dynamic dispatch +// - Refactoring and Maintenance +// - Documentation +// - Add assert to ensure invariants +// - Safer use of unmanaged objects +// - Potentially refactor to use FixedSizeArray and hence classes public struct ARTreeImpl { public typealias Spec = Spec @@ -30,6 +45,9 @@ public struct ARTreeImpl { internal var root: RawNode? public init() { - self.root = Node4.allocate().read { $0.rawNode } + self.root = nil } } + +/// A +public typealias ARTree = ARTreeImpl> diff --git a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift index aa20b3f18..4c2b1f5e0 100644 --- a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift +++ b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift @@ -67,4 +67,110 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "├──○ 20: 2[20, 30] -> 20\n" + "└──○ 40: 2[40, 50] -> 40") } + + func testCopyOnWriteSharedPrefixInsert() throws { + var t1 = ARTree() + _ = t1.insert(key: [1, 2, 3, 4, 5], value: 1) + _ = t1.insert(key: [2, 3, 4, 5, 6], value: 2) + _ = t1.insert(key: [3, 4, 5, 6, 7], value: 3) + _ = t1.insert(key: [4, 5, 6, 7, 8], value: 4) + _ = t1.insert(key: [8, 9, 10, 12, 12], value: 5) + _ = t1.insert(key: [1, 2, 3, 5, 6], value: 6) + _ = t1.insert(key: [1, 2, 3, 6, 7], value: 7) + _ = t1.insert(key: [2, 3, 5, 5, 6], value: 8) + _ = t1.insert(key: [4, 5, 6, 8, 8], value: 9) + _ = t1.insert(key: [4, 5, 6, 9, 9], value: 10) + let t1_descp = "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + + "│ ├──○ 4: 5[1, 2, 3, 4, 5] -> 1\n" + + "│ ├──○ 5: 5[1, 2, 3, 5, 6] -> 6\n" + + "│ └──○ 6: 5[1, 2, 3, 6, 7] -> 7\n" + + "├──○ 2: Node4 {childs=2, partial=[3]}\n" + + "│ ├──○ 4: 5[2, 3, 4, 5, 6] -> 2\n" + + "│ └──○ 5: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> 3\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" + XCTAssertEqual(t1_descp, t1.description) + + var t2 = t1 + t2.insert(key: [5, 6, 7], value: 11) + let t2_descp = "○ Node16 {childs=6, partial=[]}\n" + + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + + "│ ├──○ 4: 5[1, 2, 3, 4, 5] -> 1\n" + + "│ ├──○ 5: 5[1, 2, 3, 5, 6] -> 6\n" + + "│ └──○ 6: 5[1, 2, 3, 6, 7] -> 7\n" + + "├──○ 2: Node4 {childs=2, partial=[3]}\n" + + "│ ├──○ 4: 5[2, 3, 4, 5, 6] -> 2\n" + + "│ └──○ 5: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> 3\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "├──○ 5: 3[5, 6, 7] -> 11\n" + + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" + XCTAssertEqual(t1.description, t1_descp) + XCTAssertEqual(t2.description, t2_descp) + t2.delete(key: [2, 3, 4, 5, 6]) + let t2_descp_2 = "○ Node16 {childs=6, partial=[]}\n" + + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + + "│ ├──○ 4: 5[1, 2, 3, 4, 5] -> 1\n" + + "│ ├──○ 5: 5[1, 2, 3, 5, 6] -> 6\n" + + "│ └──○ 6: 5[1, 2, 3, 6, 7] -> 7\n" + + "├──○ 2: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> 3\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "├──○ 5: 3[5, 6, 7] -> 11\n" + + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" + XCTAssertEqual(t1.description, t1_descp) + XCTAssertEqual(t2.description, t2_descp_2) + + var t3 = t2 + t3.insert(key: [3, 4, 7], value: 11) + XCTAssertEqual(t1.description, t1_descp) + XCTAssertEqual(t2.description, t2_descp_2) + t3.delete(key: [1, 2, 3, 4, 5]) + t3.delete(key: [1, 2, 3, 6, 7]) + t3.insert(key: [5, 6, 8], value: 14) + t3.insert(key: [8, 9, 10, 13, 14], value: 15) + let t3_descp = "○ Node16 {childs=6, partial=[]}\n" + + "├──○ 1: 5[1, 2, 3, 5, 6] -> 6\n" + + "├──○ 2: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: Node4 {childs=2, partial=[4]}\n" + + "│ ├──○ 5: 5[3, 4, 5, 6, 7] -> 3\n" + + "│ └──○ 7: 3[3, 4, 7] -> 11\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "├──○ 5: Node4 {childs=2, partial=[6]}\n" + + "│ ├──○ 7: 3[5, 6, 7] -> 11\n" + + "│ └──○ 8: 3[5, 6, 8] -> 14\n" + + "└──○ 8: Node4 {childs=2, partial=[9, 10]}\n" + + "│ ├──○ 12: 5[8, 9, 10, 12, 12] -> 5\n" + + "│ └──○ 13: 5[8, 9, 10, 13, 14] -> 15" + XCTAssertEqual(t1.description, t1_descp) + XCTAssertEqual(t2.description, t2_descp_2) + XCTAssertEqual(t3.description, t3_descp) + t1.delete(key: [1, 2, 3, 4, 5]) + t1.delete(key: [2, 3, 4, 5, 6]) + t1.delete(key: [3, 4, 5, 6, 7]) + t1.delete(key: [4, 5, 6, 7, 8]) + t1.delete(key: [8, 9, 10, 12, 12]) + t1.delete(key: [1, 2, 3, 5, 6]) + t1.delete(key: [1, 2, 3, 6, 7]) + t1.delete(key: [2, 3, 5, 5, 6]) + t1.delete(key: [4, 5, 6, 8, 8]) + t1.delete(key: [4, 5, 6, 9, 9]) + XCTAssertEqual(t1.description, "<>") + XCTAssertEqual(t2.description, t2_descp_2) + XCTAssertEqual(t3.description, t3_descp) + } } diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 2ef537c5c..1a082c608 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -95,7 +95,7 @@ final class ARTreeDeleteTests: XCTestCase { "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") } - func testDeleteCompressToLeaf() throws { + func testDeleteCompressToLeafThenDeleteAllThenInsert() throws { var t = ARTree<[UInt8]>() t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) @@ -111,6 +111,27 @@ final class ARTreeDeleteTests: XCTestCase { "│ ├──○ 3: 6[1, 2, 3, 4, 9, 9] -> [5]\n" + "│ └──○ 4: 6[1, 2, 4, 5, 6, 7] -> [3]\n" + "└──○ 4: 6[4, 5, 6, 7, 8, 9] -> [2]") + t.delete(key: [1, 2, 4, 5, 6, 7]) + t.delete(key: [4, 5, 6, 7, 8, 9]) + t.delete(key: [1, 2, 3, 4, 9, 9]) + XCTAssertEqual(t.description, "<>") + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + XCTAssertEqual(t.description, "○ Node4 {childs=1, partial=[]}\n" + + "└──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]") + } + + func testMultiLevelTreeDeleteAll() throws { + var t1 = ARTree() + _ = t1.insert(key: [2, 3, 5, 5, 6], value: 8) + _ = t1.insert(key: [4, 5, 6, 7, 8], value: 4) + _ = t1.insert(key: [4, 5, 6, 8, 8], value: 9) + print(t1) + t1.delete(key: [2, 3, 5, 5, 6]) + print("----------") + print(t1) + t1.delete(key: [4, 5, 6, 7, 8]) + t1.delete(key: [4, 5, 6, 8, 8]) + XCTAssertEqual(t1.description, "<>") } func testDeleteCompressToNode4() throws { From 6ab1b17e0840eed2f6bc19a45075ba1acb0b8ed7 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 23 Aug 2023 15:23:49 -0700 Subject: [PATCH 39/67] Start writing some documentation --- Sources/ARTreeModule/ARTree.swift | 17 ++++++++++++++--- Sources/ARTreeModule/FixedSizedArray.swift | 6 ++++++ Sources/ARTreeModule/RawNode.swift | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 2fdccdb99..a6da647f0 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -10,6 +10,7 @@ //===----------------------------------------------------------------------===// // TODO: +// // - Ranged Operations // - Subsequence Delete // - Subsequence Iteration @@ -38,16 +39,26 @@ // - Safer use of unmanaged objects // - Potentially refactor to use FixedSizeArray and hence classes +/// An ordered collection of unique keys and associated values, optimized for space, +/// mutating shared copies, and efficient range operations, particularly read +/// operations. +/// +/// `ARTree` has the same functionality as a standard `Dictionary`, and it largely +/// implements the same APIs. However, `ARTree` is optimized specifically for use cases +/// where underlying keys share common prefixes. The underlying data-structure is a +/// _persistent variant of _Adaptive Radix Tree (ART)_. +public typealias ARTree = ARTreeImpl> + +/// Implements a persistent Adaptive Radix Tree (ART). public struct ARTreeImpl { public typealias Spec = Spec public typealias Value = Spec.Value + @usableFromInline internal var root: RawNode? + @inlinable public init() { self.root = nil } } - -/// A -public typealias ARTree = ARTreeImpl> diff --git a/Sources/ARTreeModule/FixedSizedArray.swift b/Sources/ARTreeModule/FixedSizedArray.swift index f90348366..9b5b5d487 100644 --- a/Sources/ARTreeModule/FixedSizedArray.swift +++ b/Sources/ARTreeModule/FixedSizedArray.swift @@ -37,6 +37,12 @@ struct FixedSizedArray8 { } } + mutating func shiftRight() { + for ii in (1.. Elem { get { precondition(0 <= position && position < FixedArray8Count, "\(position)") diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index b072c61b6..ebc9801ce 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +@usableFromInline struct RawNode { var buf: RawNodeBuffer From 5cdc9a5b13ea916d3e587357b5a23698f04a76fc Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 23 Aug 2023 16:37:42 -0700 Subject: [PATCH 40/67] Move things around --- Sources/ARTreeModule/ArtNode.swift | 42 ++++ Sources/ARTreeModule/InternalNode+impl.swift | 110 --------- Sources/ARTreeModule/InternalNode.swift | 235 +++++++++++++++++++ Sources/ARTreeModule/Node.swift | 158 ------------- Tests/ARTreeModuleTests/DeleteTests.swift | 6 +- 5 files changed, 280 insertions(+), 271 deletions(-) create mode 100644 Sources/ARTreeModule/ArtNode.swift delete mode 100644 Sources/ARTreeModule/InternalNode+impl.swift create mode 100644 Sources/ARTreeModule/InternalNode.swift delete mode 100644 Sources/ARTreeModule/Node.swift diff --git a/Sources/ARTreeModule/ArtNode.swift b/Sources/ARTreeModule/ArtNode.swift new file mode 100644 index 000000000..8e1619efa --- /dev/null +++ b/Sources/ARTreeModule/ArtNode.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 typealias KeyPart = UInt8 +public typealias Key = [KeyPart] + +protocol ArtNode { + associatedtype Spec: ARTreeSpec + associatedtype Buffer: RawNodeBuffer + + typealias Value = Spec.Value + typealias Storage = UnmanagedNodeStorage + + static var type: NodeType { get } + + var storage: Storage { get } + var type: NodeType { get } + var rawNode: RawNode { get } + + func clone() -> NodeStorage + + init(storage: Storage) +} + +extension ArtNode { + init(buffer: RawNodeBuffer) { + self.init(storage: Self.Storage(raw: buffer)) + } +} + +extension ArtNode { + var rawNode: RawNode { RawNode(buf: self.storage.ref.takeUnretainedValue()) } + var type: NodeType { Self.type } +} diff --git a/Sources/ARTreeModule/InternalNode+impl.swift b/Sources/ARTreeModule/InternalNode+impl.swift deleted file mode 100644 index 08b1c11da..000000000 --- a/Sources/ARTreeModule/InternalNode+impl.swift +++ /dev/null @@ -1,110 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 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 InternalNode { - var partialLength: Int { - get { - storage.withHeaderPointer { - Int($0.pointee.partialLength) - } - } - set { - assert(newValue <= Const.maxPartialLength) - storage.withHeaderPointer { - $0.pointee.partialLength = KeyPart(newValue) - } - } - } - - var partialBytes: PartialBytes { - get { - storage.withHeaderPointer { - $0.pointee.partialBytes - } - } - set { - storage.withHeaderPointer { - $0.pointee.partialBytes = newValue - } - } - } - - var count: Int { - get { - storage.withHeaderPointer { - Int($0.pointee.count) - } - } - set { - storage.withHeaderPointer { - $0.pointee.count = UInt16(newValue) - } - } - } - - func child(forKey k: KeyPart) -> RawNode? { - return index(forKey: k).flatMap { child(at: $0) } - } - - mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult { - return addChild(forKey: k, node: node.rawNode) - } - - mutating func copyHeader(from: any InternalNode) { - self.storage.withHeaderPointer { header in - header.pointee.count = UInt16(from.count) - header.pointee.partialLength = UInt8(from.partialLength) - header.pointee.partialBytes = from.partialBytes - } - } - - // Calculates the index at which prefix mismatches. - func prefixMismatch(withKey key: Key, fromIndex depth: Int) -> Int { - assert(partialLength <= Const.maxPartialLength, "partial length is always bounded") - let maxComp = min(partialLength, key.count - depth) - - for index in 0..(forKey k: KeyPart, - ref: inout NodeReference, - _ body: (any ArtNode, Bool) -> R) -> R? { - if count == 0 { - return nil - } - - return index(forKey: k).flatMap { index in - self.withChildRef(at: index) { ptr in - ref = NodeReference(ptr) - return body(ptr.pointee!.toArtNode(), ptr.pointee!.isUnique) - } - } - } - -} diff --git a/Sources/ARTreeModule/InternalNode.swift b/Sources/ARTreeModule/InternalNode.swift new file mode 100644 index 000000000..ce2ba53de --- /dev/null +++ b/Sources/ARTreeModule/InternalNode.swift @@ -0,0 +1,235 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +protocol InternalNode: ArtNode { + typealias Value = Spec.Value + typealias Index = Int + typealias Header = InternalNodeHeader + typealias Children = UnsafeMutableBufferPointer + + static var size: Int { get } + + var count: Int { get set } + var partialLength: Int { get } + var partialBytes: PartialBytes { get set } + + func index(forKey k: KeyPart) -> Index? + func index() -> Index? + func next(index: Index) -> Index? + + func child(forKey k: KeyPart) -> RawNode? // TODO: Remove + func child(at: Index) -> RawNode? // TODO: Remove + + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult + mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult + + mutating func deleteChild(at index: Index) -> UpdateResult + + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R +} + +struct NodeReference { + var _ptr: RawNode.SlotRef + + init(_ ptr: RawNode.SlotRef) { + self._ptr = ptr + } +} + + +extension NodeReference { + var pointee: RawNode? { + @inline(__always) get { _ptr.pointee } + @inline(__always) set {_ptr.pointee = newValue } + } +} + +extension InternalNode { + var partialLength: Int { + get { + storage.withHeaderPointer { + Int($0.pointee.partialLength) + } + } + set { + assert(newValue <= Const.maxPartialLength) + storage.withHeaderPointer { + $0.pointee.partialLength = KeyPart(newValue) + } + } + } + + var partialBytes: PartialBytes { + get { + storage.withHeaderPointer { + $0.pointee.partialBytes + } + } + set { + storage.withHeaderPointer { + $0.pointee.partialBytes = newValue + } + } + } + + var count: Int { + get { + storage.withHeaderPointer { + Int($0.pointee.count) + } + } + set { + storage.withHeaderPointer { + $0.pointee.count = UInt16(newValue) + } + } + } + + func child(forKey k: KeyPart) -> RawNode? { + return index(forKey: k).flatMap { child(at: $0) } + } + + mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult { + return addChild(forKey: k, node: node.rawNode) + } + + mutating func copyHeader(from: any InternalNode) { + self.storage.withHeaderPointer { header in + header.pointee.count = UInt16(from.count) + header.pointee.partialLength = UInt8(from.partialLength) + header.pointee.partialBytes = from.partialBytes + } + } + + // Calculates the index at which prefix mismatches. + func prefixMismatch(withKey key: Key, fromIndex depth: Int) -> Int { + assert(partialLength <= Const.maxPartialLength, "partial length is always bounded") + let maxComp = min(partialLength, key.count - depth) + + for index in 0..(forKey k: KeyPart, + ref: inout NodeReference, + _ body: (any ArtNode, Bool) -> R) -> R? { + if count == 0 { + return nil + } + + return index(forKey: k).flatMap { index in + self.withChildRef(at: index) { ptr in + ref = NodeReference(ptr) + return body(ptr.pointee!.toArtNode(), ptr.pointee!.isUnique) + } + } + } +} + + +enum UpdateResult { + case noop + case replaceWith(T) +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension InternalNode { + @inline(__always) + fileprivate mutating func withSelfOrClone(isUnique: Bool, + _ body: (any InternalNode) -> R) -> R { + if isUnique { + return body(self) + } + + let clone = clone() + let node: any InternalNode = clone.node + return body(node) + } + + mutating func updateChild(forKey k: KeyPart, isUnique: Bool, + body: (RawNode?) -> UpdateResult) -> UpdateResult { + + guard let childPosition = index(forKey: k) else { + return .noop + } + + let child = child(at: childPosition) + let action = body(child) + + // TODO: This is ugly. Rewrite. + switch action { + case .noop: + // No action asked to be executed from body. + return .noop + + case .replaceWith(nil) where self.count == 1: + // Body asked to remove the last child. So just delete ourselves too. + return .replaceWith(nil) + + case .replaceWith(nil): + // Body asked to remove the child. Removing the child can lead to these situations: + // - Remove successful. No more action need. + // - Remove successful, but that left us with one child, and we can apply + // path compression right now. + // - Remove successful, but we can shrink ourselves now with newValue. + return withSelfOrClone(isUnique: isUnique) { + var selfRef = $0 + switch selfRef.deleteChild(at: childPosition) { + case .noop: + // Child removed successfully, nothing to do. Keep ourselves. + return .replaceWith(selfRef.rawNode) + + case .replaceWith(let newValue?): + if newValue.type != .leaf && selfRef.count == 1 { + assert(selfRef.type == .node4, "only node4 can have count = 1") + let slf: Node4 = selfRef as! Node4 + var node: any InternalNode = newValue.toInternalNode() + node.partialBytes.shiftRight() + node.partialBytes[0] = slf.withBody { k, _ in k[0] } + node.partialLength += 1 + } + return .replaceWith(newValue) + + case .replaceWith(nil): + fatalError("unexpected state: deleteChild should not be called with count == 1") + } + } + + case .replaceWith(let newValue?): + // Body asked to replace the child, with a new one. Wont affect + // the self count. + return withSelfOrClone(isUnique: isUnique) { + var selfRef = $0 + selfRef.withChildRef(at: childPosition) { + $0.pointee = newValue + } + return .replaceWith(selfRef.rawNode) + } + } + } +} diff --git a/Sources/ARTreeModule/Node.swift b/Sources/ARTreeModule/Node.swift deleted file mode 100644 index aeb5ee16b..000000000 --- a/Sources/ARTreeModule/Node.swift +++ /dev/null @@ -1,158 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 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 typealias KeyPart = UInt8 -public typealias Key = [KeyPart] - -protocol ArtNode { - associatedtype Spec: ARTreeSpec - associatedtype Buffer: RawNodeBuffer - - typealias Value = Spec.Value - typealias Storage = UnmanagedNodeStorage - - static var type: NodeType { get } - - var storage: Storage { get } - var type: NodeType { get } - var rawNode: RawNode { get } - - func clone() -> NodeStorage - - init(storage: Storage) -} - -extension ArtNode { - init(buffer: RawNodeBuffer) { - self.init(storage: Self.Storage(raw: buffer)) - } -} - -protocol InternalNode: ArtNode { - typealias Value = Spec.Value - typealias Index = Int - typealias Header = InternalNodeHeader - typealias Children = UnsafeMutableBufferPointer - - static var size: Int { get } - - var count: Int { get set } - var partialLength: Int { get } - var partialBytes: PartialBytes { get set } - - func index(forKey k: KeyPart) -> Index? - func index() -> Index? - func next(index: Index) -> Index? - - func child(forKey k: KeyPart) -> RawNode? // TODO: Remove - func child(at: Index) -> RawNode? // TODO: Remove - - mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult - mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult - - mutating func deleteChild(at index: Index) -> UpdateResult - - mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R -} - -extension ArtNode { - var rawNode: RawNode { RawNode(buf: self.storage.ref.takeUnretainedValue()) } - var type: NodeType { Self.type } -} - -struct NodeReference { - var _ptr: RawNode.SlotRef - - init(_ ptr: RawNode.SlotRef) { - self._ptr = ptr - } -} - -extension NodeReference { - var pointee: RawNode? { - @inline(__always) get { _ptr.pointee } - @inline(__always) set { - _ptr.pointee = newValue - } - } -} - -enum UpdateResult { - case noop - case replaceWith(T) -} - -extension InternalNode { - mutating func updateChild(forKey k: KeyPart, isUnique: Bool, - body: (RawNode?) -> UpdateResult) -> UpdateResult { - - guard let childPosition = index(forKey: k) else { - return .noop - } - - let child = child(at: childPosition) - - // TODO: This is ugly. Rewrite. - let action = body(child) - if isUnique { - switch action { - case .noop: - return .noop - case .replaceWith(nil): - let shouldDeleteMyself = count == 1 - switch deleteChild(at: childPosition) { - case .noop: - return .noop - case .replaceWith(nil) where shouldDeleteMyself: - return .replaceWith(nil) - case .replaceWith(let newValue): - return .replaceWith(newValue) - } - case .replaceWith(let newValue): - withChildRef(at: childPosition) { - $0.pointee = newValue - } - - return .noop - } - } else { - switch action { - case .noop: - // Key wasn't in the tree. - return .noop - case .replaceWith(nil) where count == 1: - // Subtree was deleted, and that was our last child. Delete myself. - return .replaceWith(nil) - case .replaceWith(nil): - // Clone myself and remove the child from the clone. - return self.clone().update { myClone in - switch myClone.deleteChild(at: childPosition) { - case .noop: - return .replaceWith(myClone.rawNode) - case .replaceWith(nil): - fatalError("unexpected state: should be handled in branch where count == 1") - case .replaceWith(let newValue): - // Our clone got shrunk down after delete. - return .replaceWith(newValue) - } - } - case .replaceWith(let newValue): - // Clone myself and update the subtree. - return clone().update { clone in - clone.withChildRef(at: childPosition) { - $0.pointee = newValue - } - return .replaceWith(clone.rawNode) - } - } - } - } -} diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 1a082c608..04392379a 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -125,10 +125,10 @@ final class ARTreeDeleteTests: XCTestCase { _ = t1.insert(key: [2, 3, 5, 5, 6], value: 8) _ = t1.insert(key: [4, 5, 6, 7, 8], value: 4) _ = t1.insert(key: [4, 5, 6, 8, 8], value: 9) - print(t1) t1.delete(key: [2, 3, 5, 5, 6]) - print("----------") - print(t1) + XCTAssertEqual(t1.description, "○ Node4 {childs=2, partial=[4, 5, 6]}\n" + + "├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "└──○ 8: 5[4, 5, 6, 8, 8] -> 9") t1.delete(key: [4, 5, 6, 7, 8]) t1.delete(key: [4, 5, 6, 8, 8]) XCTAssertEqual(t1.description, "<>") From 602b4c5ca7238de739f32cd29b4de657bbe55cd9 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 00:57:22 -0700 Subject: [PATCH 41/67] Check unique references for delete --- Sources/ARTreeModule/ARTree+delete.swift | 28 +++++++++++++----------- Sources/ARTreeModule/InternalNode.swift | 11 ++++++---- Sources/ARTreeModule/Node16.swift | 3 ++- Sources/ARTreeModule/Node256.swift | 2 +- Sources/ARTreeModule/Node4.swift | 5 +++-- Sources/ARTreeModule/Node48.swift | 4 ++-- 6 files changed, 30 insertions(+), 23 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 341e87a9c..b772718e2 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -12,9 +12,13 @@ @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { public mutating func delete(key: Key) { - guard var node = root else { return } - let isUnique = true - switch _delete(node: &node, key: key, depth: 0, isUniquePath: isUnique) { + if root == nil { + return + } + + let isUnique = root!.isUnique + var child = root + switch _delete(child: &child, key: key, depth: 0, isUniquePath: isUnique) { case .noop: return case .replaceWith(let newValue): @@ -27,14 +31,12 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(node: inout RawNode, + private mutating func _delete(child: inout RawNode?, key: Key, depth: Int, isUniquePath: Bool) -> UpdateResult { - assert(!Const.testCheckUnique || isUniquePath, "unique path is expected in this test") - - if node.type == .leaf { - let leaf: NodeLeaf = node.toLeafNode() + if child?.type == .leaf { + let leaf: NodeLeaf = child!.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { return .noop } @@ -42,9 +44,9 @@ extension ARTree { return .replaceWith(nil) } - let isUnique = isUniquePath && node.isUnique + assert(!Const.testCheckUnique || isUniquePath, "unique path is expected in this test") + var node: any InternalNode = child!.toInternalNode() var newDepth = depth - var node: any InternalNode = node.toInternalNode() if node.partialLength > 0 { let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) @@ -52,9 +54,9 @@ extension ARTree { newDepth += matchedBytes } - return node.updateChild(forKey: key[newDepth], isUnique: isUnique) { - guard var child = $0 else { return .noop } - return _delete(node: &child, key: key, depth: newDepth + 1, isUniquePath: isUnique) + return node.updateChild(forKey: key[newDepth], isUniquePath: isUniquePath) { + var child = $0 + return _delete(child: &child, key: key, depth: newDepth + 1, isUniquePath: $1) } } } diff --git a/Sources/ARTreeModule/InternalNode.swift b/Sources/ARTreeModule/InternalNode.swift index ce2ba53de..1d404280c 100644 --- a/Sources/ARTreeModule/InternalNode.swift +++ b/Sources/ARTreeModule/InternalNode.swift @@ -171,15 +171,18 @@ extension InternalNode { return body(node) } - mutating func updateChild(forKey k: KeyPart, isUnique: Bool, - body: (RawNode?) -> UpdateResult) -> UpdateResult { + mutating func updateChild(forKey k: KeyPart, + isUniquePath: Bool, + body: (inout RawNode?, Bool) -> UpdateResult) + -> UpdateResult { guard let childPosition = index(forKey: k) else { return .noop } - let child = child(at: childPosition) - let action = body(child) + let isUnique = isUniquePath && withChildRef(at: childPosition) { $0.pointee!.isUnique } + var child = child(at: childPosition) + let action = body(&child, isUnique) // TODO: This is ugly. Rewrite. switch action { diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 71ac62407..d676079e2 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -182,6 +182,7 @@ extension Node16: InternalNode { count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs[count] = nil // Clear the last item. if count == 3 { // Shrink to Node4. @@ -208,7 +209,7 @@ extension Node16: ArtNode { var node = Node16(buffer: self) let count = node.count node.withBody { _, childs in - for idx in 0.. NodeStorage { let storage = NodeStorage.allocate() - storage.update { node in + storage.update { node in node.withBody { keys, childs in UnsafeMutableRawPointer(keys.baseAddress!) .bindMemory(to: UInt8.self, capacity: Self.numKeys) @@ -154,6 +154,7 @@ extension Node4: InternalNode { count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs[count] = nil // Clear the last item. if count == 1 { // Shrink to leaf node. @@ -179,7 +180,7 @@ extension Node4: ArtNode { var node = Node4(buffer: self) let count = node.count node.withBody { _, childs in - for idx in 0.. NodeStorage { let storage = Self.allocate() - storage.update { node in + storage.update { node in node.copyHeader(from: self) self.withBody { fromKeys, fromChildren in node.withBody { newKeys, newChildren in From 38315955a5d75073731cfb03e809496af1435f4f Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 01:00:20 -0700 Subject: [PATCH 42/67] Format ARTree --- Sources/ARTreeModule/ARTree+Sequence.swift | 2 +- Sources/ARTreeModule/ARTree+delete.swift | 10 +-- Sources/ARTreeModule/ARTree+insert.swift | 18 +++-- Sources/ARTreeModule/ARTree.swift | 2 +- Sources/ARTreeModule/FixedArray+Storage.swift | 67 +++++++++++-------- Sources/ARTreeModule/FixedArray+impl.swift | 12 ++-- Sources/ARTreeModule/FixedArray.swift | 1 - Sources/ARTreeModule/InternalNode.swift | 33 +++++---- Sources/ARTreeModule/Node+Storage.swift | 13 ++-- .../ARTreeModule/Node+StringConverter.swift | 3 +- Sources/ARTreeModule/Node16.swift | 10 +-- Sources/ARTreeModule/Node256.swift | 2 - Sources/ARTreeModule/Node4.swift | 8 +-- Sources/ARTreeModule/Node48.swift | 10 +-- Sources/ARTreeModule/NodeLeaf.swift | 9 +-- Sources/ARTreeModule/RawNode.swift | 2 +- 16 files changed, 113 insertions(+), 89 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 6f1365698..5e038ceb6 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -40,7 +40,7 @@ extension ARTreeImpl: Sequence { // TODO: Instead of index, use node iterators, to advance to next child. @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTreeImpl._Iterator: IteratorProtocol { - public typealias Element = (Key, Spec.Value) // TODO: Why just Value fails? + public typealias Element = (Key, Spec.Value) // TODO: Why just Value fails? // Exhausted childs on the tip of path. Forward to sibling. mutating private func advanceToSibling() { diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index b772718e2..454e6fa81 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -31,10 +31,12 @@ extension ARTree { fatalError("not implemented") } - private mutating func _delete(child: inout RawNode?, - key: Key, - depth: Int, - isUniquePath: Bool) -> UpdateResult { + private mutating func _delete( + child: inout RawNode?, + key: Key, + depth: Int, + isUniquePath: Bool + ) -> UpdateResult { if child?.type == .leaf { let leaf: NodeLeaf = child!.toLeafNode() if !leaf.keyEquals(with: key, depth: depth) { diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 189947850..61d34d4cd 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -57,7 +57,7 @@ extension ARTree { case .splitNode(var node, let depth, let prefixDiff): var newNode = Node4.allocate() newNode.partialLength = prefixDiff - newNode.partialBytes = node.partialBytes // TODO: Just copy min(maxPartialLength, prefixDiff) + newNode.partialBytes = node.partialBytes // TODO: Just copy min(maxPartialLength, prefixDiff) assert( node.partialLength <= Const.maxPartialLength, @@ -94,8 +94,9 @@ extension ARTree { var ref = NodeReference(&root) while current.type != .leaf && depth < key.count { - assert(!Const.testCheckUnique || isUnique, - "unique path is expected in this test, depth=\(depth)") + assert( + !Const.testCheckUnique || isUnique, + "unique path is expected in this test, depth=\(depth)") if !isUnique { // TODO: Why making this one-liner crashes? @@ -118,8 +119,10 @@ extension ARTree { } // Find next child to continue. - guard let (next, _isUnique) = - (node.maybeReadChild(forKey: key[depth], ref: &ref) { ($0, $1) }) else { + guard + let (next, _isUnique) = + (node.maybeReadChild(forKey: key[depth], ref: &ref) { ($0, $1) }) + else { return (.insertInto(node, depth: depth), ref) } @@ -131,8 +134,9 @@ extension ARTree { assert(current.type == .leaf) // Reached leaf already, replace it with a new node, or update the existing value. if current.type == .leaf { - assert(!Const.testCheckUnique || isUnique, - "unique path is expected in this test, depth=\(depth)") + assert( + !Const.testCheckUnique || isUnique, + "unique path is expected in this test, depth=\(depth)") let leaf: NodeLeaf = current.rawNode.toLeafNode() if leaf.keyEquals(with: key) { diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index a6da647f0..9d8366a1f 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -33,7 +33,7 @@ // - Write some performance benchmarks to compare against other data-structures // - Use some well known datasets // - Cost of dynamic dispatch -// - Refactoring and Maintenance +// - Refactoring and Maintenance // - Documentation // - Add assert to ensure invariants // - Safer use of unmanaged objects diff --git a/Sources/ARTreeModule/FixedArray+Storage.swift b/Sources/ARTreeModule/FixedArray+Storage.swift index 43efb3be5..9b87772d9 100644 --- a/Sources/ARTreeModule/FixedArray+Storage.swift +++ b/Sources/ARTreeModule/FixedArray+Storage.swift @@ -43,13 +43,18 @@ struct FixedStorage8: FixedStorage { } struct FixedStorage16: FixedStorage { - internal var items: (T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T) + internal var items: + ( + T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T + ) @inline(__always) init(repeating v: T) { - self.items = (v, v, v, v, v, v, v, v, - v, v, v, v, v, v, v, v) + self.items = ( + v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v + ) } static var capacity: Int { @@ -58,15 +63,20 @@ struct FixedStorage16: FixedStorage { } struct FixedStorage48: FixedStorage { - internal var items: (T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) + internal var items: + ( + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T + ) @inline(__always) init(repeating v: T) { - self.items = (v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, - v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, - v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v) + self.items = ( + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, + v, v, v, v, v, v, v, v, v, v, v, v, v, v, v, v + ) } static var capacity: Int { @@ -75,24 +85,25 @@ struct FixedStorage48: FixedStorage { } struct FixedStorage256: FixedStorage { - internal var items: ( - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, - T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T - ) + internal var items: + ( + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, + T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T + ) @inline(__always) init(repeating v: T) { diff --git a/Sources/ARTreeModule/FixedArray+impl.swift b/Sources/ARTreeModule/FixedArray+impl.swift index fc7f07c80..a322e3df2 100644 --- a/Sources/ARTreeModule/FixedArray+impl.swift +++ b/Sources/ARTreeModule/FixedArray+impl.swift @@ -28,7 +28,7 @@ extension FixedArray: RandomAccessCollection, MutableCollection { let res: Element = withUnsafeBytes(of: storage) { (rawPtr: UnsafeRawBufferPointer) -> Element in let stride = MemoryLayout.stride - assert(rawPtr.count == capacity*stride, "layout mismatch?") + assert(rawPtr.count == capacity * stride, "layout mismatch?") let bufPtr = UnsafeBufferPointer( start: rawPtr.baseAddress!.assumingMemoryBound(to: Element.self), count: capacity) @@ -47,12 +47,12 @@ extension FixedArray: RandomAccessCollection, MutableCollection { @inline(__always) internal func index(after i: Index) -> Index { - return i+1 + return i + 1 } @inline(__always) internal func index(before i: Index) -> Index { - return i-1 + return i - 1 } } @@ -62,7 +62,8 @@ extension FixedArray { ) rethrows -> R { let capacity = Self.capacity return try withUnsafeMutableBytes(of: &storage) { rawBuffer in - assert(rawBuffer.count == capacity*MemoryLayout.stride, + assert( + rawBuffer.count == capacity * MemoryLayout.stride, "layout mismatch?") let buffer = UnsafeMutableBufferPointer( start: rawBuffer.baseAddress!.assumingMemoryBound(to: Element.self), @@ -76,7 +77,8 @@ extension FixedArray { ) rethrows -> R { let capacity = Self.capacity return try withUnsafeBytes(of: &storage) { rawBuffer in - assert(rawBuffer.count == capacity*MemoryLayout.stride, + assert( + rawBuffer.count == capacity * MemoryLayout.stride, "layout mismatch?") let buffer = UnsafeBufferPointer( start: rawBuffer.baseAddress!.assumingMemoryBound(to: Element.self), diff --git a/Sources/ARTreeModule/FixedArray.swift b/Sources/ARTreeModule/FixedArray.swift index 35bb27f0e..b2568e993 100644 --- a/Sources/ARTreeModule/FixedArray.swift +++ b/Sources/ARTreeModule/FixedArray.swift @@ -13,7 +13,6 @@ // //===----------------------------------------------------------------------===// - typealias FixedArray4 = FixedArray> typealias FixedArray8 = FixedArray> typealias FixedArray16 = FixedArray> diff --git a/Sources/ARTreeModule/InternalNode.swift b/Sources/ARTreeModule/InternalNode.swift index 1d404280c..aedea6019 100644 --- a/Sources/ARTreeModule/InternalNode.swift +++ b/Sources/ARTreeModule/InternalNode.swift @@ -25,8 +25,8 @@ protocol InternalNode: ArtNode { func index() -> Index? func next(index: Index) -> Index? - func child(forKey k: KeyPart) -> RawNode? // TODO: Remove - func child(at: Index) -> RawNode? // TODO: Remove + func child(forKey k: KeyPart) -> RawNode? // TODO: Remove + func child(at: Index) -> RawNode? // TODO: Remove mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult @@ -44,11 +44,10 @@ struct NodeReference { } } - extension NodeReference { var pointee: RawNode? { @inline(__always) get { _ptr.pointee } - @inline(__always) set {_ptr.pointee = newValue } + @inline(__always) set { _ptr.pointee = newValue } } } @@ -135,9 +134,11 @@ extension InternalNode { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { - mutating func maybeReadChild(forKey k: KeyPart, - ref: inout NodeReference, - _ body: (any ArtNode, Bool) -> R) -> R? { + mutating func maybeReadChild( + forKey k: KeyPart, + ref: inout NodeReference, + _ body: (any ArtNode, Bool) -> R + ) -> R? { if count == 0 { return nil } @@ -151,7 +152,6 @@ extension InternalNode { } } - enum UpdateResult { case noop case replaceWith(T) @@ -160,8 +160,10 @@ enum UpdateResult { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { @inline(__always) - fileprivate mutating func withSelfOrClone(isUnique: Bool, - _ body: (any InternalNode) -> R) -> R { + fileprivate mutating func withSelfOrClone( + isUnique: Bool, + _ body: (any InternalNode) -> R + ) -> R { if isUnique { return body(self) } @@ -171,10 +173,13 @@ extension InternalNode { return body(node) } - mutating func updateChild(forKey k: KeyPart, - isUniquePath: Bool, - body: (inout RawNode?, Bool) -> UpdateResult) - -> UpdateResult { + mutating func updateChild( + forKey k: KeyPart, + isUniquePath: Bool, + body: (inout RawNode?, Bool) -> UpdateResult + ) + -> UpdateResult + { guard let childPosition = index(forKey: k) else { return .noop diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/Node+Storage.swift index c3d04bbae..f8a03a76c 100644 --- a/Sources/ARTreeModule/Node+Storage.swift +++ b/Sources/ARTreeModule/Node+Storage.swift @@ -24,8 +24,9 @@ struct NodeStorage { extension NodeStorage { static func create(type: NodeType, size: Int) -> NodeStorage { - let buf = Mn.Buffer.create(minimumCapacity: size, - makingHeaderWith: {_ in type }) + let buf = Mn.Buffer.create( + minimumCapacity: size, + makingHeaderWith: { _ in type }) buf.withUnsafeMutablePointerToElements { $0.initialize(repeating: 0, count: size) } @@ -79,7 +80,8 @@ extension NodeStorage where Mn: InternalNode { // TODO: Remove this so that we don't use it by mistake where `node` is invalid. mutating func addChild(forKey k: KeyPart, node: some ArtNode) - -> UpdateResult { + -> UpdateResult + { update { $0.addChild(forKey: k, node: node.rawNode) @@ -87,7 +89,8 @@ extension NodeStorage where Mn: InternalNode { } mutating func addChild(forKey k: KeyPart, node: NodeStorage) - -> UpdateResult where N.Spec == Mn.Spec { + -> UpdateResult where N.Spec == Mn.Spec + { update { $0.addChild(forKey: k, node: node.rawNode) } @@ -107,7 +110,7 @@ extension NodeStorage where Mn: InternalNode { } extension NodeStorage { - func read(_ body: (Mn) throws -> R) rethrows -> R{ + func read(_ body: (Mn) throws -> R) rethrows -> R { try body(self.node) } diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index b658a104f..e0773cf1a 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// #if !COLLECTIONS_SINGLE_MODULE -import _CollectionsUtilities + import _CollectionsUtilities #endif protocol NodePrettyPrinter { @@ -86,7 +86,6 @@ extension NodeStorage { } } - @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension InternalNode { fileprivate var partial: [UInt8] { diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index d676079e2..2a13c53c8 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -91,7 +91,8 @@ extension Node16 { start: bodyPtr.assumingMemoryBound(to: KeyPart.self), count: Self.numKeys ) - let childPtr = bodyPtr + let childPtr = + bodyPtr .advanced(by: Self.numKeys * MemoryLayout.stride) .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) @@ -154,7 +155,7 @@ extension Node16: InternalNode { mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { - withBody {keys, childs in + withBody { keys, childs in assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) @@ -182,7 +183,7 @@ extension Node16: InternalNode { count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs[count] = nil // Clear the last item. + childs[count] = nil // Clear the last item. if count == 3 { // Shrink to Node4. @@ -196,7 +197,7 @@ extension Node16: InternalNode { mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") - return withBody {_, childs in + return withBody { _, childs in let ref = childs.baseAddress! + index return body(ref) } @@ -207,7 +208,6 @@ extension Node16: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node16(buffer: self) - let count = node.count node.withBody { _, childs in for idx in 0..<16 { childs[idx] = nil diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/Node256.swift index ca91fa01d..f76f07856 100644 --- a/Sources/ARTreeModule/Node256.swift +++ b/Sources/ARTreeModule/Node256.swift @@ -136,12 +136,10 @@ extension Node256: InternalNode { } } - extension Node256: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node256(buffer: self) - let count = node.count node.withBody { childs in for idx in 0..<256 { childs[idx] = nil diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index 32e608771..ce603b1c2 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -65,7 +65,8 @@ extension Node4 { start: bodyPtr.assumingMemoryBound(to: KeyPart.self), count: Self.numKeys ) - let childPtr = bodyPtr + let childPtr = + bodyPtr .advanced(by: Self.numKeys * MemoryLayout.stride) .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) @@ -154,7 +155,7 @@ extension Node4: InternalNode { count -= 1 keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs[count] = nil // Clear the last item. + childs[count] = nil // Clear the last item. if count == 1 { // Shrink to leaf node. @@ -167,7 +168,7 @@ extension Node4: InternalNode { mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "index=\(index) less than count=\(count)") - return withBody {_, childs in + return withBody { _, childs in let ref = childs.baseAddress! + index return body(ref) } @@ -178,7 +179,6 @@ extension Node4: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node4(buffer: self) - let count = node.count node.withBody { _, childs in for idx in 0..<4 { childs[idx] = nil diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/Node48.swift index f8d820c5b..6a0cf0927 100644 --- a/Sources/ARTreeModule/Node48.swift +++ b/Sources/ARTreeModule/Node48.swift @@ -100,7 +100,8 @@ extension Node48 { // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will // refer to the first child. // TODO: Can we initialize buffer using any stdlib method? - let childPtr = bodyPtr + let childPtr = + bodyPtr .advanced(by: 256 * MemoryLayout.stride) .assumingMemoryBound(to: RawNode?.self) let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) @@ -112,8 +113,8 @@ extension Node48 { extension Node48: InternalNode { static var size: Int { - MemoryLayout.stride + 256*MemoryLayout.stride + - Self.numKeys*MemoryLayout.stride + MemoryLayout.stride + 256 * MemoryLayout.stride + Self.numKeys + * MemoryLayout.stride } func index(forKey k: KeyPart) -> Index? { @@ -220,7 +221,7 @@ extension Node48: InternalNode { mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") - return withBody {_, childs in + return withBody { _, childs in let ref = childs.baseAddress! + index return body(ref) } @@ -231,7 +232,6 @@ extension Node48: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node48(buffer: self) - let count = node.count node.withBody { _, childs in for idx in 0..<48 { childs[idx] = nil diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/NodeLeaf.swift index 57043314f..9d6a1e1d3 100644 --- a/Sources/ARTreeModule/NodeLeaf.swift +++ b/Sources/ARTreeModule/NodeLeaf.swift @@ -23,7 +23,7 @@ extension NodeLeaf { let size = MemoryLayout.stride + key.count + MemoryLayout.stride let storage = NodeStorage.create(type: .leaf, size: size) - storage.update { leaf in + storage.update { leaf in leaf.keyLength = key.count leaf.withKeyValue { keyPtr, valuePtr in key.withUnsafeBytes { @@ -43,7 +43,8 @@ extension NodeLeaf { func withKey(body: (KeyPtr) throws -> R) rethrows -> R { return try storage.withUnsafePointer { let keyPtr = UnsafeMutableBufferPointer( - start: $0 + start: + $0 .advanced(by: MemoryLayout.stride) .assumingMemoryBound(to: KeyPart.self), count: Int(keyLength)) @@ -75,7 +76,7 @@ extension NodeLeaf { } var key: Key { - get { withKey { k in Array(k) } } + withKey { k in Array(k) } } var keyLength: Int { @@ -92,7 +93,7 @@ extension NodeLeaf { } var value: Value { - get { withValue() { $0.pointee } } + withValue { $0.pointee } } } diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index ebc9801ce..3ffdaac71 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -13,7 +13,7 @@ struct RawNode { var buf: RawNodeBuffer - init(buf: RawNodeBuffer){ + init(buf: RawNodeBuffer) { self.buf = buf } } From 9e7c5f972fce482ec3dcac6e780c02e54261fdfd Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 08:02:51 -0700 Subject: [PATCH 43/67] Rename some private variables --- Sources/ARTreeModule/ARTree+Sequence.swift | 2 +- Sources/ARTreeModule/ARTree+delete.swift | 8 ++--- Sources/ARTreeModule/ARTree+get.swift | 4 +-- Sources/ARTreeModule/ARTree+insert.swift | 10 +++---- Sources/ARTreeModule/ARTree.swift | 5 ++-- .../ARTreeModule/Node+StringConverter.swift | 2 +- Tests/ARTreeModuleTests/DeleteTests.swift | 4 +-- Tests/ARTreeModuleTests/InsertTests.swift | 30 +++++++++---------- Tests/ARTreeModuleTests/Node4Tests.swift | 8 +++++ .../ARTreeModuleTests/TreeRefCountTests.swift | 8 ++--- 10 files changed, 45 insertions(+), 36 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree+Sequence.swift index 5e038ceb6..4a64434c1 100644 --- a/Sources/ARTreeModule/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree+Sequence.swift @@ -22,7 +22,7 @@ extension ARTreeImpl: Sequence { init(tree: ARTreeImpl) { self.tree = tree self.path = [] - guard let node = tree.root else { return } + guard let node = tree._root else { return } assert(node.type != .leaf, "root can't be leaf") let n: any InternalNode = node.toInternalNode() diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree+delete.swift index 454e6fa81..0e8587862 100644 --- a/Sources/ARTreeModule/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree+delete.swift @@ -12,17 +12,17 @@ @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTree { public mutating func delete(key: Key) { - if root == nil { + if _root == nil { return } - let isUnique = root!.isUnique - var child = root + let isUnique = _root!.isUnique + var child = _root switch _delete(child: &child, key: key, depth: 0, isUniquePath: isUnique) { case .noop: return case .replaceWith(let newValue): - root = newValue + _root = newValue } } diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index d6dbff5b2..844d5b51f 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -12,8 +12,8 @@ @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTreeImpl { public func getValue(key: Key) -> Value? { - assert(root != nil, "root can't be nil") - var current = root + assert(_root != nil, "root can't be nil") + var current = _root var depth = 0 while depth <= key.count { guard let _rawNode = current else { diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 61d34d4cd..6a00b98ef 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -83,15 +83,15 @@ extension ARTree { // TODO: Make sure that the node returned have fileprivate mutating func _findInsertNode(key: Key) -> (InsertAction, NodeReference)? { - if root == nil { + if _root == nil { // NOTE: Should we just create leaf? Likely tree will have more items anyway. - root = Node4.allocate().read { $0.rawNode } + _root = Node4.allocate().read { $0.rawNode } } var depth = 0 - var current: any ArtNode = root!.toArtNode() - var isUnique = isKnownUniquelyReferenced(&root!.buf) - var ref = NodeReference(&root) + var current: any ArtNode = _root!.toArtNode() + var isUnique = isKnownUniquelyReferenced(&_root!.buf) + var ref = NodeReference(&_root) while current.type != .leaf && depth < key.count { assert( diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree.swift index 9d8366a1f..aa991a9f7 100644 --- a/Sources/ARTreeModule/ARTree.swift +++ b/Sources/ARTreeModule/ARTree.swift @@ -38,6 +38,7 @@ // - Add assert to ensure invariants // - Safer use of unmanaged objects // - Potentially refactor to use FixedSizeArray and hence classes +// - ArtNode* is unmanaged and make its use contained within closures. /// An ordered collection of unique keys and associated values, optimized for space, /// mutating shared copies, and efficient range operations, particularly read @@ -55,10 +56,10 @@ public struct ARTreeImpl { public typealias Value = Spec.Value @usableFromInline - internal var root: RawNode? + internal var _root: RawNode? @inlinable public init() { - self.root = nil + self._root = nil } } diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index e0773cf1a..40d722127 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -37,7 +37,7 @@ func indent(_ width: Int, last: Bool) -> String { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTreeImpl: CustomStringConvertible { public var description: String { - if let node = root { + if let node = _root { return "○ " + node.prettyPrint(depth: 0, with: Spec.self) } else { return "<>" diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 04392379a..6b56cec86 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -164,7 +164,7 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...16 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t.root?.type, .node48) + XCTAssertEqual(t._root?.type, .node48) t.delete(key: [3, 4]) t.delete(key: [4, 5]) XCTAssertEqual( @@ -210,7 +210,7 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...48 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t.root?.type, .node256) + XCTAssertEqual(t._root?.type, .node256) for i: UInt8 in 24...40 { if i % 2 == 0 { t.delete(key: [i, i + 1]) diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 175add6dc..b37d7e469 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -84,15 +84,15 @@ final class ARTreeInsertTests: XCTestCase { + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t.root?.type, .node4) + XCTAssertEqual(t._root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t.root?.type, .node16) + XCTAssertEqual(t._root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t.root?.type, .node48) + XCTAssertEqual(t._root?.type, .node48) } } - let root: any InternalNode = t.root!.toInternalNode() + let root: any InternalNode = t._root!.toInternalNode() XCTAssertEqual(root.count, 40) XCTAssertEqual( t.description, @@ -147,15 +147,15 @@ final class ARTreeInsertTests: XCTestCase { + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t.root?.type, .node4) + XCTAssertEqual(t._root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t.root?.type, .node16) + XCTAssertEqual(t._root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t.root?.type, .node48) + XCTAssertEqual(t._root?.type, .node48) } } - let root: any InternalNode = t.root!.toInternalNode() + let root: any InternalNode = t._root!.toInternalNode() XCTAssertEqual(root.count, 70) XCTAssertEqual( t.description, @@ -255,7 +255,7 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node16) + XCTAssertEqual(tree._root?.type, .node16) testCase.shuffle() for (k, v) in testCase { @@ -304,7 +304,7 @@ final class ARTreeInsertTests: XCTestCase { for (k, v) in testCase { tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node48) + XCTAssertEqual(tree._root?.type, .node48) testCase.shuffle() for (k, v) in testCase { @@ -333,7 +333,7 @@ final class ARTreeInsertTests: XCTestCase { total += 1 } XCTAssertEqual(total, testCase.count) - XCTAssertEqual(tree.root?.type, .node4) + XCTAssertEqual(tree._root?.type, .node4) testCase.shuffle() for (k, v) in testCase { @@ -359,7 +359,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node4) + XCTAssertEqual(tree._root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -379,7 +379,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node4) + XCTAssertEqual(tree._root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -401,7 +401,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node4) + XCTAssertEqual(tree._root?.type, .node4) testCase.reverse() for (k, v) in testCase { @@ -422,7 +422,7 @@ final class ARTreeInsertTests: XCTestCase { print("Inserting \(k) \(v)") tree.insert(key: k, value: v) } - XCTAssertEqual(tree.root?.type, .node4) + XCTAssertEqual(tree._root?.type, .node4) testCase.reverse() for (k, v) in testCase { diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 56b59ca94..c26775f01 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -87,6 +87,14 @@ final class ARTreeNode4Tests: XCTestCase { "└──○ 30: 1[30] -> [3]") } + // NOTE: Should fail. + // func test4AddAlreadyExist() throws { + // typealias T = Tree<[UInt8]> + // var node = T.N4.allocate() + // _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + // node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [2])) + // } + func test4DeleteAtIndex() throws { typealias T = Tree<[UInt8]> var node = T.N4.allocate() diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index c37a66baf..a9b2c9208 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -49,8 +49,8 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [1, 2, 3], value: 10) t!.insert(key: [2, 4, 4], value: 20) - XCTAssertEqual(getRc(t!.root!.buf), 2) - var n4 = t!.root + XCTAssertEqual(getRc(t!._root!.buf), 2) + var n4 = t!._root XCTAssertEqual(getRc(n4!.buf), 3) t = nil XCTAssertEqual(getRc(n4!.buf), 2) @@ -66,8 +66,8 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [4, 4, 4], value: 40) t!.insert(key: [5, 4, 4], value: 50) - XCTAssertEqual(getRc(t!.root!.buf), 2) - var n4 = t!.root + XCTAssertEqual(getRc(t!._root!.buf), 2) + var n4 = t!._root XCTAssertEqual(getRc(n4!.buf), 3) t = nil XCTAssertEqual(getRc(n4!.buf), 2) From 70465274719d2a294db24ecb3bd7c247b3c21b56 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 08:40:14 -0700 Subject: [PATCH 44/67] Add CMakeLists.txt --- Sources/ARTreeModule/CMakeLists.txt | 44 +++++++++++++++++++++++++++++ Sources/CMakeLists.txt | 1 + 2 files changed, 45 insertions(+) create mode 100644 Sources/ARTreeModule/CMakeLists.txt diff --git a/Sources/ARTreeModule/CMakeLists.txt b/Sources/ARTreeModule/CMakeLists.txt new file mode 100644 index 000000000..61097d884 --- /dev/null +++ b/Sources/ARTreeModule/CMakeLists.txt @@ -0,0 +1,44 @@ +#[[ +This source file is part of the Swift Collections Open Source Project + +Copyright (c) 2023 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(ARTreeModule + "ArtNode.swift" + "ARTree+insert.swift" + "ARTree.swift" + "FixedArray+impl.swift" + "FixedSizedArray.swift" + "Node+StringConverter.swift" + "Node16.swift" + "NodeHeader.swift" + "RawNode.swift" + "ARTree+delete.swift" + "ARTree+Sequence.swift" + "CMakeLists.txt" + "FixedArray+Storage.swift" + "InternalNode.swift" + "Node+UnmanagedStorage.swift" + "Node48.swift" + "NodeLeaf.swift" + "Utils.swift" + "ARTree+get.swift" + "ARTree+utils.swift" + "Consts and Specs.swift" + "FixedArray.swift" + "Node+Storage.swif" + "Node4.swift" + "Node256.swift" + "NodeType.swift" +) +target_link_libraries(ARTreeModule PRIVATE + _CollectionsUtilities) +set_target_properties(ARTreeModule PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY}) + +_install_target(ARTreeModule) +set_property(GLOBAL APPEND PROPERTY SWIFT_COLLECTIONS_EXPORTS ARTreeModule) diff --git a/Sources/CMakeLists.txt b/Sources/CMakeLists.txt index ad81e77d9..56fb9983e 100644 --- a/Sources/CMakeLists.txt +++ b/Sources/CMakeLists.txt @@ -7,6 +7,7 @@ Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information #]] +add_subdirectory(ARTreeModule) add_subdirectory(BitCollections) add_subdirectory(Collections) add_subdirectory(DequeModule) From 6e065aac1fa490135d115751bd34e4157fe504e4 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 14:11:54 -0700 Subject: [PATCH 45/67] Add some more tests --- Tests/ARTreeModuleTests/Node16Tests.swift | 30 +++++++++++++++++++++++ Tests/ARTreeModuleTests/Node4Tests.swift | 23 +++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index 67df6a3e8..e5f8cac60 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -96,4 +96,34 @@ final class ARTreeNode16Tests: XCTestCase { _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } XCTAssertEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } + + func test16ExpandTo48AndThenShrinkTo4() throws { + typealias T = Tree + var node = T.N16.allocate() + for ii: UInt8 in 0...15 { + switch node.addChild(forKey: ii, node: T.Leaf.allocate(key: [ii], value: Int(ii) + 10)) { + case .noop: break + case .replaceWith(_): XCTAssert(false, "node16 shouldn't expand just yet") + } + } + + var newNode = node.addChildReturn(forKey: UInt8(16), node: T.Leaf.allocate(key: [16], value: 26)) + do { + var count = 48 + while newNode?.type != .node16 && count > 0 { + newNode = node.deleteChildReturn(at: 4) + count -= 1 + } + } + XCTAssertEqual(newNode?.type, .node16) + + do { + var count = 16 + while newNode?.type != .node4 && count > 0 { + newNode = node.deleteChildReturn(at: 2) + count -= 1 + } + } + XCTAssertEqual(newNode?.type, .node4) + } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index c26775f01..bfc16f266 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -146,7 +146,7 @@ final class ARTreeNode4Tests: XCTestCase { XCTAssertEqual(newNode?.type, .leaf) } - func test4ExapandTo16() throws { + func test4ExpandTo16ThenShrinkTo4() throws { typealias T = Tree<[UInt8]> var node = T.N4.allocate() _ = node.addChild(forKey: 1, node: T.Leaf.allocate(key: [1], value: [1])) @@ -161,7 +161,7 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - let newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) + var newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) XCTAssertEqual( newNode!.print(with: T.Spec.self), "○ Node16 {childs=5, partial=[]}\n" + @@ -170,6 +170,15 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "├──○ 4: 1[4] -> [4]\n" + "└──○ 5: 1[5] -> [5]") + + do { + var node: any InternalNode = newNode!.toInternalNode() + node.deleteChild(at: 4) + switch node.deleteChild(at: 3) { + case .noop, .replaceWith(nil): XCTAssert(false, "node should shrink") + case .replaceWith(let newValue?): XCTAssertEqual(newValue.type, .node4) + } + } } func test4DeleteKey() throws { @@ -197,5 +206,15 @@ final class ARTreeNode4Tests: XCTestCase { "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } XCTAssertEqual(node.print(), "○ Node4 {childs=0, partial=[]}\n") + + _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) + _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) + _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } + XCTAssertEqual( + node.print(), + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "└──○ 20: 1[20] -> [3]") } } From 17604fb0870c50dfd8036742fe676a54c673abb2 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 17:28:59 -0700 Subject: [PATCH 46/67] Add few test and fixed a bug when setting partial length --- Sources/ARTreeModule/ARTree+get.swift | 1 - Sources/ARTreeModule/ARTree+insert.swift | 7 +- Sources/ARTreeModule/RawNode.swift | 5 + Tests/ARTreeModuleTests/GetValueTests.swift | 5 + Tests/ARTreeModuleTests/InsertTests.swift | 119 ++++++++++++++++++++ Tests/ARTreeModuleTests/Node4Tests.swift | 16 +-- Tests/ARTreeModuleTests/NodeLeafTests.swift | 18 ++- 7 files changed, 153 insertions(+), 18 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree+get.swift index 844d5b51f..aff9ca38c 100644 --- a/Sources/ARTreeModule/ARTree+get.swift +++ b/Sources/ARTreeModule/ARTree+get.swift @@ -12,7 +12,6 @@ @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension ARTreeImpl { public func getValue(key: Key) -> Value? { - assert(_root != nil, "root can't be nil") var current = _root var depth = 0 while depth <= key.count { diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 6a00b98ef..2f3c7509a 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -36,12 +36,16 @@ extension ARTree { _ = newNode.addChild(forKey: leaf.key[depth + longestPrefix], node: leaf) _ = newNode.addChild(forKey: key[depth + longestPrefix], node: newLeaf) + // TODO: Flip the direction of node creation. + // TODO: Optimization: Just set partialLength = longestPrefix, and look at minimum leaf for + // rest of the bytes (at-least until we are storing entire keys inside the leaf). + // Probably useful for cases where nodes share significantly long common prefix. while longestPrefix > 0 { let nBytes = Swift.min(Const.maxPartialLength, longestPrefix) let start = depth + longestPrefix - nBytes newNode.partialLength = nBytes newNode.partialBytes.copy(src: key[...], start: start, count: nBytes) - longestPrefix -= nBytes + 1 + longestPrefix -= nBytes if longestPrefix <= 0 { break @@ -50,6 +54,7 @@ extension ARTree { var next = Node4.allocate() _ = next.addChild(forKey: key[start - 1], node: newNode) newNode = next + longestPrefix -= 1 // One keys goes for mapping the child in next node. } ref.pointee = newNode.rawNode // Replace child in parent. diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/RawNode.swift index 3ffdaac71..6a7d4e62f 100644 --- a/Sources/ARTreeModule/RawNode.swift +++ b/Sources/ARTreeModule/RawNode.swift @@ -62,6 +62,11 @@ extension RawNode { } } + func toInternalNode(of: Spec.Type) -> any InternalNode { + let n: any InternalNode = toInternalNode() + return n + } + func toLeafNode() -> NodeLeaf { assert(type == .leaf) return NodeLeaf(buffer: buf) diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index 4285cdcea..48057f6a5 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -22,6 +22,11 @@ func randomByteArray(minSize: Int, maxSize: Int, minByte: UInt8, maxByte: UInt8) @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeGetValueTests: XCTestCase { + func testGetValueNil() throws { + let t = ARTree<[UInt8]>() + XCTAssertEqual(t.getValue(key: [10, 20, 30]), nil) + } + func testGetValueBasic() throws { var t = ARTree<[UInt8]>() t.insert(key: [10, 20, 30], value: [11, 21, 31]) diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index b37d7e469..02f5225e1 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -232,6 +232,125 @@ final class ARTreeInsertTests: XCTestCase { "└──○ 70: 1[70] -> [70]") } + func testInsertPrefixSharedSmall() throws { + let testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 6], + [1, 2, 3, 4, 7] + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongOnNodePrefixFull() throws { + let testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 11], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 12] + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongMultiLayer1() throws { + let testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 18] + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongMultiLayer2() throws { + let testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 11, 12, 13, 14, 17], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 11, 12, 13, 14, 18] + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongMultiLayer3() throws { + var testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 24] + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + + XCTAssertEqual( + tree.description, + "○ Node4 {childs=1, partial=[]}\n" + + "└──○ 1: Node4 {childs=1, partial=[2]}\n" + + "│ └──○ 3: Node4 {childs=1, partial=[4, 5, 6, 7, 8, 9, 10, 11]}\n" + + "│ │ └──○ 12: Node4 {childs=3, partial=[13, 14, 16, 17, 18, 19, 20, 21]}\n" + + "│ │ │ ├──○ 22: 21[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22] -> 10\n" + + "│ │ │ ├──○ 23: 21[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23] -> 11\n" + + "│ │ │ └──○ 24: 21[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 24] -> 12") + + testCase.append([1, 2, 3, 4, 5, 6, 7, 8, 10, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23]) + tree.insert(key:testCase.last!, value: 3 + 10) + for (val, test) in testCase.enumerated() { + XCTAssertEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongMultiLayer5() throws { + let testCase: [[UInt8]] = [ + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99, 13, 14, 15, 17, 18, 19, 66, 21, 22, 77, 24, 25, 26, 27], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 88, 13, 14, 15, 17, 18, 19, 55, 21, 22, 66, 24, 25, 26, 27], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99, 13, 14, 15, 17, 18, 19, 66, 21, 22, 44, 24, 25, 26, 27], + ] + + var tree = ARTree() + for (index, test) in testCase.enumerated() { + tree.insert(key: test, value: index + 10) + } + + for (val, test) in testCase.enumerated() { + let result = tree.getValue(key: test) + XCTAssertEqual(result, val + 10) + } + } + func testInsertAndGetSmallSetRepeat48() throws { for ii in 0...10000 { if ii % 1000 == 0 { diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index bfc16f266..9117111c0 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -161,7 +161,7 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") - var newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) + let newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) XCTAssertEqual( newNode!.print(with: T.Spec.self), "○ Node16 {childs=5, partial=[]}\n" + @@ -173,7 +173,7 @@ final class ARTreeNode4Tests: XCTestCase { do { var node: any InternalNode = newNode!.toInternalNode() - node.deleteChild(at: 4) + _ = node.deleteChild(at: 4) switch node.deleteChild(at: 3) { case .noop, .replaceWith(nil): XCTAssert(false, "node should shrink") case .replaceWith(let newValue?): XCTAssertEqual(newValue.type, .node4) @@ -204,17 +204,5 @@ final class ARTreeNode4Tests: XCTestCase { node.print(), "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual(node.print(), "○ Node4 {childs=0, partial=[]}\n") - - _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) - _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) - _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual( - node.print(), - "○ Node4 {childs=2, partial=[]}\n" + - "├──○ 10: 1[10] -> [1]\n" + - "└──○ 20: 1[20] -> [3]") } } diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index 5e63bdcf5..fd28a87e3 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -45,8 +45,7 @@ final class ARTreeNodeLeafTests: XCTestCase { func testLeafLcp() throws { typealias L = NodeLeaf> - let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) - + var leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) L.allocate(key: [0, 1, 2, 3], value: [0]).read { other in XCTAssertEqual( leaf1.node.longestCommonPrefix( @@ -83,6 +82,21 @@ final class ARTreeNodeLeafTests: XCTestCase { XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 2), 0) } + + leaf1 = L.allocate(key: [1, 2, 3, 4], value: [0]) + L.allocate(key: [1, 2, 3, 4, 5, 6], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 4) + } + L.allocate(key: [1, 2, 3, 5, 5, 6], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 3) + } + L.allocate(key: [1, 2, 3, 4], value: [0]).read { other in + XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 4) + } + // // Breaks the contract, so its OK that these fail. // // XCTAssertEqual( // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [], value: [0]), From 5fa86d54ddbdb16e9c824c0cee83a314fe450d69 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 18:56:24 -0700 Subject: [PATCH 47/67] Refactor to use getters --- Sources/ARTreeModule/ARTree+insert.swift | 2 +- Sources/ARTreeModule/InternalNode.swift | 2 +- .../ARTreeModule/Node+StringConverter.swift | 93 ++++--- Sources/ARTreeModule/Node16.swift | 193 ++++++-------- Sources/ARTreeModule/Node256.swift | 126 ++++------ Sources/ARTreeModule/Node4.swift | 164 ++++++------ Sources/ARTreeModule/Node48.swift | 237 ++++++++---------- Tests/ARTreeModuleTests/DeleteTests.swift | 1 + 8 files changed, 349 insertions(+), 469 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 2f3c7509a..26c103312 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -54,7 +54,7 @@ extension ARTree { var next = Node4.allocate() _ = next.addChild(forKey: key[start - 1], node: newNode) newNode = next - longestPrefix -= 1 // One keys goes for mapping the child in next node. + longestPrefix -= 1 // One keys goes for mapping the child in next node. } ref.pointee = newNode.rawNode // Replace child in parent. diff --git a/Sources/ARTreeModule/InternalNode.swift b/Sources/ARTreeModule/InternalNode.swift index aedea6019..d37f9a26c 100644 --- a/Sources/ARTreeModule/InternalNode.swift +++ b/Sources/ARTreeModule/InternalNode.swift @@ -218,7 +218,7 @@ extension InternalNode { let slf: Node4 = selfRef as! Node4 var node: any InternalNode = newValue.toInternalNode() node.partialBytes.shiftRight() - node.partialBytes[0] = slf.withBody { k, _ in k[0] } + node.partialBytes[0] = slf.keys[0] node.partialLength += 1 } return .replaceWith(newValue) diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/Node+StringConverter.swift index 40d722127..c11cc4208 100644 --- a/Sources/ARTreeModule/Node+StringConverter.swift +++ b/Sources/ARTreeModule/Node+StringConverter.swift @@ -111,16 +111,15 @@ extension Node4: NodePrettyPrinter { func prettyPrint(depth: Int) -> String { let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " var output = "Node4\(addr){childs=\(count), partial=\(partial)}\n" - withBody { keys, childs in - for idx in 0.. String { let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " var output = "Node16\(addr){childs=\(count), partial=\(partial)}\n" - withBody { keys, childs in - for idx in 0..= 0xFF { - continue - } - - total += 1 - let last = total == count - output += indent(depth, last: last) - output += String(key) + ": " - output += child(at: Int(slot))!.prettyPrint(depth: depth + 1, with: Spec.self) - if !last { - output += "\n" - } + + for (key, slot) in keys.enumerated() { + if slot >= 0xFF { + continue + } + + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child(at: Int(slot))!.prettyPrint(depth: depth + 1, with: Spec.self) + if !last { + output += "\n" } } @@ -183,22 +180,22 @@ extension Node256: NodePrettyPrinter { let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " var output = "Node256\(addr){childs=\(count), partial=\(partial)}\n" var total = 0 - withBody { childs in - for (key, child) in childs.enumerated() { - if child == nil { - continue - } - - total += 1 - let last = total == count - output += indent(depth, last: last) - output += String(key) + ": " - output += child!.prettyPrint(depth: depth + 1, with: Spec.self) - if !last { - output += "\n" - } + + for (key, child) in childs.enumerated() { + if child == nil { + continue + } + + total += 1 + let last = total == count + output += indent(depth, last: last) + output += String(key) + ": " + output += child!.prettyPrint(depth: depth + 1, with: Spec.self) + if !last { + output += "\n" } } + return output } } diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/Node16.swift index 2a13c53c8..75347c2ee 100644 --- a/Sources/ARTreeModule/Node16.swift +++ b/Sources/ARTreeModule/Node16.swift @@ -18,17 +18,34 @@ extension Node16 { static var numKeys: Int { 16 } } +extension Node16 { + var keys: UnsafeMutableBufferPointer { + storage.withBodyPointer { + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + } + } + + var childs: UnsafeMutableBufferPointer { + storage.withBodyPointer { + let childPtr = $0.advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + return UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + } + } +} + extension Node16 { static func allocate() -> NodeStorage { let storage = NodeStorage.allocate() storage.update { node in - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) - } + UnsafeMutableRawPointer(node.keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(node.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return storage @@ -37,16 +54,12 @@ extension Node16 { static func allocate(copyFrom: Node4) -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - UnsafeMutableRawBufferPointer(newKeys).copyBytes(from: fromKeys) - UnsafeMutableRawBufferPointer(newChilds).copyBytes( - from: UnsafeMutableRawBufferPointer(fromChilds)) - Self.retainChildren(newChilds, count: node.count) - } - } + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + UnsafeMutableRawBufferPointer(newNode.keys).copyBytes(from: copyFrom.keys) + UnsafeMutableRawBufferPointer(newNode.childs).copyBytes( + from: UnsafeMutableRawBufferPointer(copyFrom.childs)) + Self.retainChildren(newNode.childs, count: newNode.count) } return storage @@ -55,53 +68,29 @@ extension Node16 { static func allocate(copyFrom: Node48) -> NodeStorage { let storage = NodeStorage.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - var slot = 0 - for key: UInt8 in 0...255 { - let childPosition = Int(fromKeys[Int(key)]) - if childPosition == 0xFF { - continue - } - - newKeys[slot] = key - newChilds[slot] = fromChilds[childPosition] - slot += 1 - } - - assert(slot == node.count) - Self.retainChildren(newChilds, count: node.count) + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + + var slot = 0 + for key: UInt8 in 0...255 { + let childPosition = Int(copyFrom.keys[Int(key)]) + if childPosition == 0xFF { + continue } + + newNode.keys[slot] = key + newNode.childs[slot] = copyFrom.childs[childPosition] + slot += 1 } + + assert(slot == newNode.count) + Self.retainChildren(newNode.childs, count: newNode.count) } return storage } } -extension Node16 { - typealias Keys = UnsafeMutableBufferPointer - typealias Children = UnsafeMutableBufferPointer - - func withBody(body: (Keys, Children) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) - let childPtr = - bodyPtr - .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) - - return try body(keys, childs) - } - } -} - extension Node16: InternalNode { static var size: Int { MemoryLayout.stride + Self.numKeys @@ -109,15 +98,13 @@ extension Node16: InternalNode { } func index(forKey k: KeyPart) -> Index? { - return withBody { keys, _ in - for (index, key) in keys.enumerated() { - if key == k { - return index - } + for (index, key) in keys.enumerated() { + if key == k { + return index } - - return nil } + + return nil } func index() -> Index? { @@ -135,34 +122,28 @@ extension Node16: InternalNode { return nil } - return withBody { keys, _ in - for idx in 0..= Int(k) { - return idx - } + for idx in 0..= Int(k) { + return idx } - - return count } + + return count } func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return withBody { _, childs in - return childs[at] - } + return childs[at] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { - withBody { keys, childs in - assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") - keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - keys[slot] = k - childs[slot] = node - count += 1 - } + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 return .noop } else { return Node48.allocate(copyFrom: self).update { newNode in @@ -176,31 +157,27 @@ extension Node16: InternalNode { assert(index < Self.numKeys, "index can't >= 16 in Node16") assert(index < count, "not enough childs in node") - return withBody { keys, childs in - keys[index] = 0 - childs[index] = nil + keys[index] = 0 + childs[index] = nil - count -= 1 - keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs[count] = nil // Clear the last item. + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs[count] = nil // Clear the last item. - if count == 3 { - // Shrink to Node4. - let newNode = Node4.allocate(copyFrom: self) - return .replaceWith(newNode.node.rawNode) - } - - return .noop + if count == 3 { + // Shrink to Node4. + let newNode = Node4.allocate(copyFrom: self) + return .replaceWith(newNode.node.rawNode) } + + return .noop } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") - return withBody { _, childs in - let ref = childs.baseAddress! + index - return body(ref) - } + let ref = childs.baseAddress! + index + return body(ref) } } @@ -208,10 +185,8 @@ extension Node16: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node16(buffer: self) - node.withBody { _, childs in - for idx in 0..<16 { - childs[idx] = nil - } + for idx in 0..<16 { + node.childs[idx] = nil } node.count = 0 } @@ -220,15 +195,11 @@ extension Node16: ArtNode { func clone() -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: self) - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0.. { + storage.withBodyPointer { + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: RawNode?.self), + count: 256) + } + } +} + extension Node256 { static func allocate() -> NodeStorage { let storage = NodeStorage.allocate() - storage.update { node in - _ = node.withBody { childs in - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) - } + storage.update { newNode in + UnsafeMutableRawPointer(newNode.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return storage @@ -35,50 +43,30 @@ extension Node256 { static func allocate(copyFrom: Node48) -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newChilds in - for key in 0..<256 { - let slot = Int(fromKeys[key]) - if slot < 0xFF { - newChilds[key] = fromChilds[slot] - } - } - - Self.retainChildren(newChilds, count: Self.numKeys) + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + for key in 0..<256 { + let slot = Int(copyFrom.keys[key]) + if slot < 0xFF { + newNode.childs[key] = copyFrom.childs[slot] } } - assert(node.count == 48, "should have exactly 48 childs") + + Self.retainChildren(newNode.childs, count: Self.numKeys) + assert(newNode.count == 48, "should have exactly 48 childs") } return storage } } -extension Node256 { - typealias Keys = UnsafeMutableBufferPointer - typealias Children = UnsafeMutableBufferPointer - - func withBody(body: (Children) throws -> R) rethrows -> R { - return try storage.withBodyPointer { - return try body( - UnsafeMutableBufferPointer( - start: $0.assumingMemoryBound(to: RawNode?.self), - count: 256)) - } - } -} - extension Node256: InternalNode { static var size: Int { MemoryLayout.stride + 256 * MemoryLayout.stride } func index(forKey k: KeyPart) -> Index? { - return withBody { childs in - return childs[Int(k)] != nil ? Int(k) : nil - } + return childs[Int(k)] != nil ? Int(k) : nil } func index() -> Index? { @@ -86,53 +74,43 @@ extension Node256: InternalNode { } func next(index: Index) -> Index? { - return withBody { childs in - for idx in index + 1..<256 { - if childs[idx] != nil { - return idx - } + for idx in index + 1..<256 { + if childs[idx] != nil { + return idx } - - return nil } + + return nil } func child(at: Int) -> RawNode? { assert(at < 256, "maximum 256 childs allowed") - return withBody { childs in - return childs[at] - } + return childs[at] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { - return withBody { childs in - assert(childs[Int(k)] == nil, "node for key \(k) already exists") - childs[Int(k)] = node - count += 1 - return .noop - } + assert(childs[Int(k)] == nil, "node for key \(k) already exists") + childs[Int(k)] = node + count += 1 + return .noop } - public mutating func deleteChild(at index: Index) -> UpdateResult { - return withBody { childs in - childs[index] = nil - count -= 1 + mutating func deleteChild(at index: Index) -> UpdateResult { + childs[index] = nil + count -= 1 - if count == 40 { - let newNode = Node48.allocate(copyFrom: self) - return .replaceWith(newNode.node.rawNode) - } - - return .noop + if count == 40 { + let newNode = Node48.allocate(copyFrom: self) + return .replaceWith(newNode.node.rawNode) } + + return .noop } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") - return withBody { childs in - let ref = childs.baseAddress! + index - return body(ref) - } + let ref = childs.baseAddress! + index + return body(ref) } } @@ -140,10 +118,8 @@ extension Node256: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node256(buffer: self) - node.withBody { childs in - for idx in 0..<256 { - childs[idx] = nil - } + for idx in 0..<256 { + node.childs[idx] = nil } node.count = 0 } @@ -152,14 +128,10 @@ extension Node256: ArtNode { func clone() -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: self) - self.withBody { fromChildren in - node.withBody { newChildren in - for idx in 0..<256 { - newChildren[idx] = fromChildren[idx] - } - } + storage.update { newNode in + newNode.copyHeader(from: self) + for idx in 0..<256 { + newNode.childs[idx] = childs[idx] } } diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/Node4.swift index ce603b1c2..ac7434846 100644 --- a/Sources/ARTreeModule/Node4.swift +++ b/Sources/ARTreeModule/Node4.swift @@ -18,17 +18,34 @@ extension Node4 { static var numKeys: Int { 4 } } +extension Node4 { + var keys: UnsafeMutableBufferPointer { + storage.withBodyPointer { + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: KeyPart.self), + count: Self.numKeys + ) + } + } + + var childs: UnsafeMutableBufferPointer { + storage.withBodyPointer { + let childPtr = $0.advanced(by: Self.numKeys * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + return UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + } + } +} + extension Node4 { static func allocate() -> NodeStorage { let storage = NodeStorage.allocate() storage.update { node in - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) - } + UnsafeMutableRawPointer(node.keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(node.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } return storage @@ -37,42 +54,19 @@ extension Node4 { static func allocate(copyFrom: Node16) -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - node.withBody { newKeys, newChilds in - copyFrom.withBody { fromKeys, fromChilds in - UnsafeMutableRawBufferPointer(newKeys).copyBytes( - from: UnsafeBufferPointer(rebasing: fromKeys[0.. - typealias Children = UnsafeMutableBufferPointer + UnsafeMutableRawBufferPointer(newNode.keys).copyBytes( + from: UnsafeBufferPointer(rebasing: copyFrom.keys[0..(body: (Keys, Children) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: Self.numKeys - ) - let childPtr = - bodyPtr - .advanced(by: Self.numKeys * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) - - return try body(keys, childs) + Self.retainChildren(newNode.childs, count: newNode.count) } + + return storage } } @@ -83,15 +77,13 @@ extension Node4: InternalNode { } func index(forKey k: KeyPart) -> Index? { - return withBody { keys, _ in - for (index, key) in keys.enumerated() { - if key == k { - return index - } + for (index, key) in keys.enumerated() { + if key == k { + return index } - - return nil } + + return nil } func index() -> Index? { @@ -108,34 +100,28 @@ extension Node4: InternalNode { return nil } - return withBody { keys, _ in - for idx in 0..= Int(k) { - return idx - } + for idx in 0..= Int(k) { + return idx } - - return count } + + return count } func child(at: Index) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") - return withBody { _, childs in - return childs[at] - } + return childs[at] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if let slot = _insertSlot(forKey: k) { - withBody { keys, childs in - assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") - keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) - keys[slot] = k - childs[slot] = node - count += 1 - } + assert(count == 0 || keys[slot] != k, "node for key \(k) already exists") + keys.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + childs.shiftRight(startIndex: slot, endIndex: count - 1, by: 1) + keys[slot] = k + childs[slot] = node + count += 1 return .noop } else { var newNode = Node16.allocate(copyFrom: self) @@ -148,30 +134,26 @@ extension Node4: InternalNode { assert(index < 4, "index can't >= 4 in Node4") assert(index < count, "not enough childs in node") - return withBody { keys, childs in - keys[index] = 0 - childs[index] = nil + keys[index] = 0 + childs[index] = nil - count -= 1 - keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) - childs[count] = nil // Clear the last item. - - if count == 1 { - // Shrink to leaf node. - return .replaceWith(childs[0]) - } + count -= 1 + keys.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs.shiftLeft(startIndex: index + 1, endIndex: count, by: 1) + childs[count] = nil // Clear the last item. - return .noop + if count == 1 { + // Shrink to leaf node. + return .replaceWith(childs[0]) } + + return .noop } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "index=\(index) less than count=\(count)") - return withBody { _, childs in - let ref = childs.baseAddress! + index - return body(ref) - } + let ref = childs.baseAddress! + index + return body(ref) } } @@ -179,10 +161,8 @@ extension Node4: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node4(buffer: self) - node.withBody { _, childs in - for idx in 0..<4 { - childs[idx] = nil - } + for idx in 0..<4 { + node.childs[idx] = nil } node.count = 0 } @@ -191,15 +171,11 @@ extension Node4: ArtNode { func clone() -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: self) - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0.. { + storage.withBodyPointer { + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: KeyPart.self), + count: 256 + ) + } + } + + var childs: UnsafeMutableBufferPointer { + storage.withBodyPointer { + let childPtr = $0.advanced(by: 256 * MemoryLayout.stride) + .assumingMemoryBound(to: RawNode?.self) + return UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) + } + } +} + extension Node48 { static func allocate() -> NodeStorage { let storage = NodeStorage.allocate() - storage.update { node in - node.withBody { keys, childs in - UnsafeMutableRawPointer(keys.baseAddress!) - .bindMemory(to: UInt8.self, capacity: Self.numKeys) - UnsafeMutableRawPointer(childs.baseAddress!) - .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + storage.update { newNode in + UnsafeMutableRawPointer(newNode.keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(newNode.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) - for idx in 0..<256 { - keys[idx] = 0xFF - } + for idx in 0..<256 { + newNode.keys[idx] = 0xFF } } @@ -41,19 +58,15 @@ extension Node48 { static func allocate(copyFrom: Node16) -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - copyFrom.withBody { fromKeys, fromChilds in - node.withBody { newKeys, newChilds in - UnsafeMutableRawBufferPointer(newChilds).copyBytes( - from: UnsafeMutableRawBufferPointer(fromChilds)) - for (idx, key) in fromKeys.enumerated() { - newKeys[Int(key)] = UInt8(idx) - } - - Self.retainChildren(newChilds, count: node.count) - } + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + UnsafeMutableRawBufferPointer(newNode.childs).copyBytes( + from: UnsafeMutableRawBufferPointer(copyFrom.childs)) + for (idx, key) in copyFrom.keys.enumerated() { + newNode.keys[Int(key)] = UInt8(idx) } + + Self.retainChildren(newNode.childs, count: newNode.count) } return storage @@ -62,55 +75,26 @@ extension Node48 { static func allocate(copyFrom: Node256) -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: copyFrom) - copyFrom.withBody { fromChilds in - node.withBody { newKeys, newChilds in - var slot = 0 - for (key, child) in fromChilds.enumerated() { - if child == nil { - continue - } - - newKeys[key] = UInt8(slot) - newChilds[slot] = child - slot += 1 - } - - Self.retainChildren(newChilds, count: node.count) + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + var slot = 0 + for (key, child) in copyFrom.childs.enumerated() { + if child == nil { + continue } + + newNode.keys[key] = UInt8(slot) + newNode.childs[slot] = child + slot += 1 } + + Self.retainChildren(newNode.childs, count: newNode.count) } return storage } } -extension Node48 { - typealias Keys = UnsafeMutableBufferPointer - typealias Children = UnsafeMutableBufferPointer - - func withBody(body: (Keys, Children) throws -> R) rethrows -> R { - return try storage.withBodyPointer { bodyPtr in - let keys = UnsafeMutableBufferPointer( - start: bodyPtr.assumingMemoryBound(to: KeyPart.self), - count: 256 - ) - - // NOTE: Initializes each key pointer to point to a value > number of children, as 0 will - // refer to the first child. - // TODO: Can we initialize buffer using any stdlib method? - let childPtr = - bodyPtr - .advanced(by: 256 * MemoryLayout.stride) - .assumingMemoryBound(to: RawNode?.self) - let childs = UnsafeMutableBufferPointer(start: childPtr, count: Self.numKeys) - - return try body(keys, childs) - } - } -} - extension Node48: InternalNode { static var size: Int { MemoryLayout.stride + 256 * MemoryLayout.stride + Self.numKeys @@ -118,10 +102,8 @@ extension Node48: InternalNode { } func index(forKey k: KeyPart) -> Index? { - return withBody { keys, _ in - let childIndex = Int(keys[Int(k)]) - return childIndex == 0xFF ? nil : childIndex - } + let childIndex = Int(keys[Int(k)]) + return childIndex == 0xFF ? nil : childIndex } func index() -> Index? { @@ -129,38 +111,31 @@ extension Node48: InternalNode { } func next(index: Index) -> Index? { - return withBody { keys, _ in - for idx: Int in index + 1..<256 { - if keys[idx] != 0xFF { - return Int(keys[idx]) - } + for idx: Int in index + 1..<256 { + if keys[idx] != 0xFF { + return Int(keys[idx]) } - - return nil } + + return nil } func child(at: Int) -> RawNode? { assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return withBody { _, childs in - return childs[at] - } + return childs[at] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { if count < Self.numKeys { - withBody { keys, childs in - assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") + assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") - guard let slot = findFreeSlot() else { - assert(false, "cannot find free slot in Node48") - return - } - - keys[Int(k)] = KeyPart(slot) - childs[slot] = node + guard let slot = findFreeSlot() else { + fatalError("cannot find free slot in Node48") } + keys[Int(k)] = KeyPart(slot) + childs[slot] = node + self.count += 1 return .noop } else { @@ -172,59 +147,53 @@ extension Node48: InternalNode { } public mutating func deleteChild(at index: Index) -> UpdateResult { - return withBody { keys, childs in - let targetSlot = Int(keys[index]) - assert(targetSlot != 0xFF, "slot is empty already") - // 1. Find out who has the last slot. - var lastSlotKey = 0 - for k in 0..<256 { - if keys[k] == count - 1 { - lastSlotKey = k - break - } + let targetSlot = Int(keys[index]) + assert(targetSlot != 0xFF, "slot is empty already") + // 1. Find out who has the last slot. + var lastSlotKey = 0 + for k in 0..<256 { + if keys[k] == count - 1 { + lastSlotKey = k + break } + } - // 2. Move last child slot into current child slot, and reset last child slot. - childs[targetSlot] = childs[count - 1] - childs[count - 1] = nil - - // 3. Map that key to current slot. - keys[lastSlotKey] = UInt8(targetSlot) + // 2. Move last child slot into current child slot, and reset last child slot. + childs[targetSlot] = childs[count - 1] + childs[count - 1] = nil - // 4. Clear input key. - keys[index] = 0xFF + // 3. Map that key to current slot. + keys[lastSlotKey] = UInt8(targetSlot) - // 5. Reduce number of children. - count -= 1 + // 4. Clear input key. + keys[index] = 0xFF - // 6. Shrink the node to Node16 if needed. - if count == 13 { - let newNode = Node16.allocate(copyFrom: self) - return .replaceWith(newNode.node.rawNode) - } + // 5. Reduce number of children. + count -= 1 - return .noop + // 6. Shrink the node to Node16 if needed. + if count == 13 { + let newNode = Node16.allocate(copyFrom: self) + return .replaceWith(newNode.node.rawNode) } + + return .noop } private func findFreeSlot() -> Int? { - return withBody { _, childs in - for (index, child) in childs.enumerated() { - if child == nil { - return index - } + for (index, child) in childs.enumerated() { + if child == nil { + return index } - - return nil } + + return nil } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { assert(index < count, "not enough childs in node") - return withBody { _, childs in - let ref = childs.baseAddress! + index - return body(ref) - } + let ref = childs.baseAddress! + index + return body(ref) } } @@ -232,10 +201,8 @@ extension Node48: ArtNode { final class Buffer: RawNodeBuffer { deinit { var node = Node48(buffer: self) - node.withBody { _, childs in - for idx in 0..<48 { - childs[idx] = nil - } + for idx in 0..<48 { + node.childs[idx] = nil } node.count = 0 } @@ -244,17 +211,13 @@ extension Node48: ArtNode { func clone() -> NodeStorage { let storage = Self.allocate() - storage.update { node in - node.copyHeader(from: self) - self.withBody { fromKeys, fromChildren in - node.withBody { newKeys, newChildren in - for idx in 0..<256 { - let slot = fromKeys[idx] - newKeys[idx] = slot - if slot != 0xFF { - newChildren[Int(slot)] = fromChildren[Int(slot)] - } - } + storage.update { newNode in + newNode.copyHeader(from: self) + for idx in 0..<256 { + let slot = keys[idx] + newNode.keys[idx] = slot + if slot != 0xFF { + newNode.childs[Int(slot)] = childs[Int(slot)] } } } diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 6b56cec86..6fd47df9e 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -216,6 +216,7 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [i, i + 1]) } } + XCTAssertEqual(t._root?.type, .node48) XCTAssertEqual( t.description, "○ Node48 {childs=40, partial=[]}\n" + From 7641a5a635387a15f37e16a9f2b43f8ce3cc487d Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Thu, 24 Aug 2023 23:57:20 -0700 Subject: [PATCH 48/67] Implement replace when inserting --- Sources/ARTreeModule/ARTree+insert.swift | 14 ++++- .../ARTreeModuleTests/CopyOnWriteTests.swift | 55 +++++++++++++++++++ Tests/ARTreeModuleTests/InsertTests.swift | 21 +++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree+insert.swift index 26c103312..f139e527b 100644 --- a/Sources/ARTreeModule/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree+insert.swift @@ -23,8 +23,10 @@ extension ARTree { guard var (action, ref) = _findInsertNode(key: key) else { return false } switch action { - case .replace(_): - fatalError("replace not supported") + case .replace(let leaf): + leaf.withValue { + $0.pointee = value + } case .splitLeaf(let leaf, let depth): let newLeaf = Self.allocateLeaf(key: key, value: value) @@ -145,7 +147,13 @@ extension ARTree { let leaf: NodeLeaf = current.rawNode.toLeafNode() if leaf.keyEquals(with: key) { - return (.replace(leaf), ref) + if isUnique { + return (.replace(leaf), ref) + } else { + let clone = leaf.clone() + ref.pointee = clone.node.rawNode + return (.replace(clone.node), ref) + } } if isUnique { diff --git a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift index 4c2b1f5e0..ada855e92 100644 --- a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift +++ b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift @@ -173,4 +173,59 @@ final class ARTreeCopyOnWriteTests: XCTestCase { XCTAssertEqual(t2.description, t2_descp_2) XCTAssertEqual(t3.description, t3_descp) } + + func testCopyOnWriteReplaceValue() throws { + var t1 = ARTree() + let testCases: [[UInt8]] = [ + [1, 2, 3, 4, 5], + [2, 3, 4, 5, 6], + [3, 4, 5, 6, 7], + [4, 5, 6, 7, 8], + [8, 9, 10, 12, 12], + [1, 2, 3, 5, 6], + [1, 2, 3, 6, 7], + [2, 3, 5, 5, 6], + [4, 5, 6, 8, 8], + [4, 5, 6, 9, 9] + ] + + for (idx, test) in testCases.enumerated() { + t1.insert(key: test, value: idx + 1) + } + var t2 = t1 + for (idx, test) in testCases[2...5].enumerated() { + t2.insert(key: test, value: idx + 10) + } + + let t1_descp = "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + + "│ ├──○ 4: 5[1, 2, 3, 4, 5] -> 1\n" + + "│ ├──○ 5: 5[1, 2, 3, 5, 6] -> 6\n" + + "│ └──○ 6: 5[1, 2, 3, 6, 7] -> 7\n" + + "├──○ 2: Node4 {childs=2, partial=[3]}\n" + + "│ ├──○ 4: 5[2, 3, 4, 5, 6] -> 2\n" + + "│ └──○ 5: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> 3\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" + let t2_descp = "○ Node16 {childs=5, partial=[]}\n" + + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + + "│ ├──○ 4: 5[1, 2, 3, 4, 5] -> 1\n" + + "│ ├──○ 5: 5[1, 2, 3, 5, 6] -> 13\n" + + "│ └──○ 6: 5[1, 2, 3, 6, 7] -> 7\n" + + "├──○ 2: Node4 {childs=2, partial=[3]}\n" + + "│ ├──○ 4: 5[2, 3, 4, 5, 6] -> 2\n" + + "│ └──○ 5: 5[2, 3, 5, 5, 6] -> 8\n" + + "├──○ 3: 5[3, 4, 5, 6, 7] -> 10\n" + + "├──○ 4: Node4 {childs=3, partial=[5, 6]}\n" + + "│ ├──○ 7: 5[4, 5, 6, 7, 8] -> 11\n" + + "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + + "└──○ 8: 5[8, 9, 10, 12, 12] -> 12" + XCTAssertEqual(t1.description, t1_descp) + XCTAssertEqual(t2.description, t2_descp) + } } diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 02f5225e1..cb27e0f6f 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -549,4 +549,25 @@ final class ARTreeInsertTests: XCTestCase { XCTAssertEqual(tree.getValue(key: k), v) } } + + func testReplace() throws { + var t = ARTree() + let testCases: [[UInt8]] = [ + [11, 21, 31], + [12, 22, 32], + [10, 20, 32] + ] + + for (idx, test) in testCases.enumerated() { + t.insert(key: test, value: idx) + } + + for (idx, test) in testCases.enumerated() { + t.insert(key: test, value: idx + 10) + } + + for (idx, test) in testCases.enumerated() { + XCTAssertEqual(t.getValue(key: test), idx + 10) + } + } } From e6fc5b7df30ce910917f14ccc2857ad585ac0bd3 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 25 Aug 2023 06:35:01 -0700 Subject: [PATCH 49/67] Add some refcounting tests --- .../ARTreeModuleTests/TreeRefCountTests.swift | 72 +++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index a9b2c9208..95d9a5ede 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -21,26 +21,22 @@ private class TestBox { } } -@inline(__always) func getRc(_ x: AnyObject) -> UInt { - return _getRetainCount(x) -} - @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class ARTreeRefCountTest: XCTestCase { func testRefCountBasic() throws { // TODO: Why is it 2? var x = TestBox("foo") - XCTAssertEqual(getRc(x), 2) + XCTAssertEqual(_getRetainCount(x), 2) var t = ARTree() - XCTAssertEqual(getRc(x), 2) + XCTAssertEqual(_getRetainCount(x), 2) t.insert(key: [10, 20, 30], value: x) - XCTAssertEqual(getRc(x), 3) + XCTAssertEqual(_getRetainCount(x), 3) x = TestBox("bar") - XCTAssertEqual(getRc(x), 2) + XCTAssertEqual(_getRetainCount(x), 2) x = t.getValue(key: [10, 20, 30])! - XCTAssertEqual(getRc(x), 3) + XCTAssertEqual(_getRetainCount(x), 3) t.delete(key: [10, 20, 30]) - XCTAssertEqual(getRc(x), 2) + XCTAssertEqual(_getRetainCount(x), 2) } func testRefCountNode4() throws { @@ -49,11 +45,11 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [1, 2, 3], value: 10) t!.insert(key: [2, 4, 4], value: 20) - XCTAssertEqual(getRc(t!._root!.buf), 2) + XCTAssertEqual(_getRetainCount(t!._root!.buf), 2) var n4 = t!._root - XCTAssertEqual(getRc(n4!.buf), 3) + XCTAssertEqual(_getRetainCount(n4!.buf), 3) t = nil - XCTAssertEqual(getRc(n4!.buf), 2) + XCTAssertEqual(_getRetainCount(n4!.buf), 2) n4 = nil } @@ -66,29 +62,65 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [4, 4, 4], value: 40) t!.insert(key: [5, 4, 4], value: 50) - XCTAssertEqual(getRc(t!._root!.buf), 2) + XCTAssertEqual(_getRetainCount(t!._root!.buf), 2) var n4 = t!._root - XCTAssertEqual(getRc(n4!.buf), 3) + XCTAssertEqual(_getRetainCount(n4!.buf), 3) t = nil - XCTAssertEqual(getRc(n4!.buf), 2) + XCTAssertEqual(_getRetainCount(n4!.buf), 2) n4 = nil } func testRefCountStorage() throws { typealias Tree = ARTree let node = Node4.allocate() - let count0 = getRc(node.rawNode.buf) + let ref = node.ref + let count0 = _getRetainCount(ref) let a = node.node - let count1 = getRc(node.rawNode.buf) + let count1 = _getRetainCount(ref) XCTAssertEqual(count1, count0) let b = node.node - let count2 = getRc(node.rawNode.buf) + let count2 = _getRetainCount(ref) XCTAssertEqual(count2, count1) let c = node.node.rawNode - let count3 = getRc(node.rawNode.buf) + let count3 = _getRetainCount(ref) XCTAssertEqual(count3, count2 + 1) } + + func testRefCountReplace() throws { + typealias Tree = ARTree + var t = Tree() + var v = TestBox("val1") + XCTAssertTrue(isKnownUniquelyReferenced(&v)) + + let count0 = _getRetainCount(v) + t.insert(key: [1, 2, 3], value: v) + XCTAssertFalse(isKnownUniquelyReferenced(&v)) + XCTAssertEqual(_getRetainCount(v), count0 + 1) + + t.insert(key: [1, 2, 3], value: TestBox("val2")) + XCTAssertEqual(_getRetainCount(v), count0) + XCTAssertTrue(isKnownUniquelyReferenced(&v)) + } + + func testRefCountNode4ChildAndClone() throws { + typealias Tree = ARTree + var node = Node4.allocate() + var newNode = Node4.allocate() + XCTAssertTrue(isKnownUniquelyReferenced(&newNode.ref)) + _ = node.addChild(forKey: 10, node: newNode) + XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref)) + _ = node.deleteChild(at: 0) + XCTAssertTrue(isKnownUniquelyReferenced(&newNode.ref)) + + // Now do same after cloning. + _ = node.addChild(forKey: 10, node: newNode) + XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref)) + let cloneNode = node.clone() + _ = node.deleteChild(at: 0) + XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref), + "newNode can't be unique as it is should be referenced by clone as well") + } } From d0c680e858768a170f523660d7450591d60640e9 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 25 Aug 2023 07:15:38 -0700 Subject: [PATCH 50/67] More testing --- Tests/ARTreeModuleTests/InsertTests.swift | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index cb27e0f6f..5b930ad20 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -36,6 +36,30 @@ final class ARTreeInsertTests: XCTestCase { "└──○ 12: 3[12, 22, 32] -> [13, 23, 33]") } + func testInsertDeleteInsertOnEmptyTree() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [1]) + t.delete(key: [10, 20, 30]) + XCTAssertEqual(t.description, "<>") + t.insert(key: [11, 21, 31], value: [2]) + XCTAssertEqual( + t.description, + "○ Node4 {childs=1, partial=[]}\n" + + "└──○ 11: 3[11, 21, 31] -> [2]") + + t.insert(key: [10, 20, 30], value: [3]) + t.delete(key: [10, 20, 30]) + XCTAssertEqual(t._root?.type, .leaf, "root should shrink to leaf") + + t.insert(key: [10, 20, 30], value: [4]) + XCTAssertEqual(t._root?.type, .node4, "should be able to insert into root leaf node") + XCTAssertEqual( + t.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 3[10, 20, 30] -> [4]\n" + + "└──○ 11: 3[11, 21, 31] -> [2]") + } + func testInsertSharedPrefix() throws { var t = ARTree<[UInt8]>() t.insert(key: [10, 20, 30], value: [11, 21, 31]) From 9f741163873de601d95e537375ecda19d71a8e77 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Fri, 1 Sep 2023 13:57:18 -0700 Subject: [PATCH 51/67] Move ARTree into separate submodule --- .../{ => ARTree}/ARTree+Sequence.swift | 0 .../{ => ARTree}/ARTree+delete.swift | 0 .../{ => ARTree}/ARTree+get.swift | 0 .../{ => ARTree}/ARTree+insert.swift | 0 .../{ => ARTree}/ARTree+utils.swift | 0 .../ARTreeModule/{ => ARTree}/ARTree.swift | 0 .../ARTreeModule/{ => ARTree}/ArtNode.swift | 0 .../{ => ARTree}/Consts and Specs.swift | 0 .../{ => ARTree}/FixedArray+Storage.swift | 0 .../{ => ARTree}/FixedArray+impl.swift | 0 .../{ => ARTree}/FixedArray.swift | 0 .../{ => ARTree}/FixedSizedArray.swift | 0 .../{ => ARTree}/InternalNode.swift | 0 .../{ => ARTree}/Node+Storage.swift | 0 .../{ => ARTree}/Node+StringConverter.swift | 0 .../{ => ARTree}/Node+UnmanagedStorage.swift | 0 .../ARTreeModule/{ => ARTree}/Node16.swift | 0 .../ARTreeModule/{ => ARTree}/Node256.swift | 0 Sources/ARTreeModule/{ => ARTree}/Node4.swift | 0 .../ARTreeModule/{ => ARTree}/Node48.swift | 0 .../{ => ARTree}/NodeHeader.swift | 0 .../ARTreeModule/{ => ARTree}/NodeLeaf.swift | 0 .../ARTreeModule/{ => ARTree}/NodeType.swift | 0 .../ARTreeModule/{ => ARTree}/RawNode.swift | 0 Sources/ARTreeModule/{ => ARTree}/Utils.swift | 0 Sources/ARTreeModule/CMakeLists.txt | 52 +++++++++---------- 26 files changed, 26 insertions(+), 26 deletions(-) rename Sources/ARTreeModule/{ => ARTree}/ARTree+Sequence.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ARTree+delete.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ARTree+get.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ARTree+insert.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ARTree+utils.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ARTree.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/ArtNode.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Consts and Specs.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/FixedArray+Storage.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/FixedArray+impl.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/FixedArray.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/FixedSizedArray.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/InternalNode.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node+Storage.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node+StringConverter.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node+UnmanagedStorage.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node16.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node256.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node4.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Node48.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/NodeHeader.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/NodeLeaf.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/NodeType.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/RawNode.swift (100%) rename Sources/ARTreeModule/{ => ARTree}/Utils.swift (100%) diff --git a/Sources/ARTreeModule/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift similarity index 100% rename from Sources/ARTreeModule/ARTree+Sequence.swift rename to Sources/ARTreeModule/ARTree/ARTree+Sequence.swift diff --git a/Sources/ARTreeModule/ARTree+delete.swift b/Sources/ARTreeModule/ARTree/ARTree+delete.swift similarity index 100% rename from Sources/ARTreeModule/ARTree+delete.swift rename to Sources/ARTreeModule/ARTree/ARTree+delete.swift diff --git a/Sources/ARTreeModule/ARTree+get.swift b/Sources/ARTreeModule/ARTree/ARTree+get.swift similarity index 100% rename from Sources/ARTreeModule/ARTree+get.swift rename to Sources/ARTreeModule/ARTree/ARTree+get.swift diff --git a/Sources/ARTreeModule/ARTree+insert.swift b/Sources/ARTreeModule/ARTree/ARTree+insert.swift similarity index 100% rename from Sources/ARTreeModule/ARTree+insert.swift rename to Sources/ARTreeModule/ARTree/ARTree+insert.swift diff --git a/Sources/ARTreeModule/ARTree+utils.swift b/Sources/ARTreeModule/ARTree/ARTree+utils.swift similarity index 100% rename from Sources/ARTreeModule/ARTree+utils.swift rename to Sources/ARTreeModule/ARTree/ARTree+utils.swift diff --git a/Sources/ARTreeModule/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift similarity index 100% rename from Sources/ARTreeModule/ARTree.swift rename to Sources/ARTreeModule/ARTree/ARTree.swift diff --git a/Sources/ARTreeModule/ArtNode.swift b/Sources/ARTreeModule/ARTree/ArtNode.swift similarity index 100% rename from Sources/ARTreeModule/ArtNode.swift rename to Sources/ARTreeModule/ARTree/ArtNode.swift diff --git a/Sources/ARTreeModule/Consts and Specs.swift b/Sources/ARTreeModule/ARTree/Consts and Specs.swift similarity index 100% rename from Sources/ARTreeModule/Consts and Specs.swift rename to Sources/ARTreeModule/ARTree/Consts and Specs.swift diff --git a/Sources/ARTreeModule/FixedArray+Storage.swift b/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift similarity index 100% rename from Sources/ARTreeModule/FixedArray+Storage.swift rename to Sources/ARTreeModule/ARTree/FixedArray+Storage.swift diff --git a/Sources/ARTreeModule/FixedArray+impl.swift b/Sources/ARTreeModule/ARTree/FixedArray+impl.swift similarity index 100% rename from Sources/ARTreeModule/FixedArray+impl.swift rename to Sources/ARTreeModule/ARTree/FixedArray+impl.swift diff --git a/Sources/ARTreeModule/FixedArray.swift b/Sources/ARTreeModule/ARTree/FixedArray.swift similarity index 100% rename from Sources/ARTreeModule/FixedArray.swift rename to Sources/ARTreeModule/ARTree/FixedArray.swift diff --git a/Sources/ARTreeModule/FixedSizedArray.swift b/Sources/ARTreeModule/ARTree/FixedSizedArray.swift similarity index 100% rename from Sources/ARTreeModule/FixedSizedArray.swift rename to Sources/ARTreeModule/ARTree/FixedSizedArray.swift diff --git a/Sources/ARTreeModule/InternalNode.swift b/Sources/ARTreeModule/ARTree/InternalNode.swift similarity index 100% rename from Sources/ARTreeModule/InternalNode.swift rename to Sources/ARTreeModule/ARTree/InternalNode.swift diff --git a/Sources/ARTreeModule/Node+Storage.swift b/Sources/ARTreeModule/ARTree/Node+Storage.swift similarity index 100% rename from Sources/ARTreeModule/Node+Storage.swift rename to Sources/ARTreeModule/ARTree/Node+Storage.swift diff --git a/Sources/ARTreeModule/Node+StringConverter.swift b/Sources/ARTreeModule/ARTree/Node+StringConverter.swift similarity index 100% rename from Sources/ARTreeModule/Node+StringConverter.swift rename to Sources/ARTreeModule/ARTree/Node+StringConverter.swift diff --git a/Sources/ARTreeModule/Node+UnmanagedStorage.swift b/Sources/ARTreeModule/ARTree/Node+UnmanagedStorage.swift similarity index 100% rename from Sources/ARTreeModule/Node+UnmanagedStorage.swift rename to Sources/ARTreeModule/ARTree/Node+UnmanagedStorage.swift diff --git a/Sources/ARTreeModule/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift similarity index 100% rename from Sources/ARTreeModule/Node16.swift rename to Sources/ARTreeModule/ARTree/Node16.swift diff --git a/Sources/ARTreeModule/Node256.swift b/Sources/ARTreeModule/ARTree/Node256.swift similarity index 100% rename from Sources/ARTreeModule/Node256.swift rename to Sources/ARTreeModule/ARTree/Node256.swift diff --git a/Sources/ARTreeModule/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift similarity index 100% rename from Sources/ARTreeModule/Node4.swift rename to Sources/ARTreeModule/ARTree/Node4.swift diff --git a/Sources/ARTreeModule/Node48.swift b/Sources/ARTreeModule/ARTree/Node48.swift similarity index 100% rename from Sources/ARTreeModule/Node48.swift rename to Sources/ARTreeModule/ARTree/Node48.swift diff --git a/Sources/ARTreeModule/NodeHeader.swift b/Sources/ARTreeModule/ARTree/NodeHeader.swift similarity index 100% rename from Sources/ARTreeModule/NodeHeader.swift rename to Sources/ARTreeModule/ARTree/NodeHeader.swift diff --git a/Sources/ARTreeModule/NodeLeaf.swift b/Sources/ARTreeModule/ARTree/NodeLeaf.swift similarity index 100% rename from Sources/ARTreeModule/NodeLeaf.swift rename to Sources/ARTreeModule/ARTree/NodeLeaf.swift diff --git a/Sources/ARTreeModule/NodeType.swift b/Sources/ARTreeModule/ARTree/NodeType.swift similarity index 100% rename from Sources/ARTreeModule/NodeType.swift rename to Sources/ARTreeModule/ARTree/NodeType.swift diff --git a/Sources/ARTreeModule/RawNode.swift b/Sources/ARTreeModule/ARTree/RawNode.swift similarity index 100% rename from Sources/ARTreeModule/RawNode.swift rename to Sources/ARTreeModule/ARTree/RawNode.swift diff --git a/Sources/ARTreeModule/Utils.swift b/Sources/ARTreeModule/ARTree/Utils.swift similarity index 100% rename from Sources/ARTreeModule/Utils.swift rename to Sources/ARTreeModule/ARTree/Utils.swift diff --git a/Sources/ARTreeModule/CMakeLists.txt b/Sources/ARTreeModule/CMakeLists.txt index 61097d884..41f027e27 100644 --- a/Sources/ARTreeModule/CMakeLists.txt +++ b/Sources/ARTreeModule/CMakeLists.txt @@ -8,32 +8,32 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(ARTreeModule - "ArtNode.swift" - "ARTree+insert.swift" - "ARTree.swift" - "FixedArray+impl.swift" - "FixedSizedArray.swift" - "Node+StringConverter.swift" - "Node16.swift" - "NodeHeader.swift" - "RawNode.swift" - "ARTree+delete.swift" - "ARTree+Sequence.swift" - "CMakeLists.txt" - "FixedArray+Storage.swift" - "InternalNode.swift" - "Node+UnmanagedStorage.swift" - "Node48.swift" - "NodeLeaf.swift" - "Utils.swift" - "ARTree+get.swift" - "ARTree+utils.swift" - "Consts and Specs.swift" - "FixedArray.swift" - "Node+Storage.swif" - "Node4.swift" - "Node256.swift" - "NodeType.swift" + "ARTree/ArtNode.swift" + "ARTree/ARTree+insert.swift" + "ARTree/ARTree.swift" + "ARTree/FixedArray+impl.swift" + "ARTree/FixedSizedArray.swift" + "ARTree/Node+StringConverter.swift" + "ARTree/Node16.swift" + "ARTree/NodeHeader.swift" + "ARTree/RawNode.swift" + "ARTree/ARTree+delete.swift" + "ARTree/ARTree+Sequence.swift" + "ARTree/CMakeLists.txt" + "ARTree/FixedArray+Storage.swift" + "ARTree/InternalNode.swift" + "ARTree/Node+UnmanagedStorage.swift" + "ARTree/Node48.swift" + "ARTree/NodeLeaf.swift" + "ARTree/Utils.swift" + "ARTree/ARTree+get.swift" + "ARTree/ARTree+utils.swift" + "ARTree/Consts and Specs.swift" + "ARTree/FixedArray.swift" + "ARTree/Node+Storage.swif" + "ARTree/Node4.swift" + "ARTree/Node256.swift" + "ARTree/NodeType.swift" ) target_link_libraries(ARTreeModule PRIVATE _CollectionsUtilities) From 1490717dfcf678d5224240aff239d98315d235cd Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sun, 3 Sep 2023 17:26:26 -0700 Subject: [PATCH 52/67] Add support for unsigned integers and RadixTree type --- .../ARTreeModule/ARTree/ARTree+Sequence.swift | 5 +- .../ARTreeModule/ARTree/ARTree+delete.swift | 2 +- .../ARTreeModule/ARTree/ARTree+insert.swift | 2 +- .../ARTreeModule/ARTree/ARTree+utils.swift | 2 +- Sources/ARTreeModule/ARTree/ARTree.swift | 4 +- .../ARTree/Node+StringConverter.swift | 2 +- Sources/ARTreeModule/ARTree/Node16.swift | 2 +- Sources/ARTreeModule/ARTree/Node256.swift | 5 +- Sources/ARTreeModule/ARTree/Node4.swift | 2 +- Sources/ARTreeModule/ARTree/Node48.swift | 15 +-- Sources/ARTreeModule/KeyConversions.swift | 81 ++++++++++++++++ Sources/ARTreeModule/RadixTree.swift | 54 +++++++++++ Tests/ARTreeModuleTests/GetValueTests.swift | 5 +- Tests/ARTreeModuleTests/IntMapTests.swift | 92 +++++++++++++++++++ 14 files changed, 252 insertions(+), 21 deletions(-) create mode 100644 Sources/ARTreeModule/KeyConversions.swift create mode 100644 Sources/ARTreeModule/RadixTree.swift create mode 100644 Tests/ARTreeModuleTests/IntMapTests.swift diff --git a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift index 4a64434c1..355aedfce 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift @@ -56,7 +56,7 @@ extension ARTreeImpl._Iterator: IteratorProtocol { path.append((node, node.next(index: index!))) } - mutating public func next() -> Element? { + mutating func next() -> Element? { while !path.isEmpty { while let (node, _index) = path.last { guard let index = _index else { @@ -72,7 +72,8 @@ extension ARTreeImpl._Iterator: IteratorProtocol { return result } - path.append((next.toInternalNode(), node.index())) + let nextNode: any InternalNode = next.toInternalNode() + path.append((nextNode, nextNode.index())) } } diff --git a/Sources/ARTreeModule/ARTree/ARTree+delete.swift b/Sources/ARTreeModule/ARTree/ARTree+delete.swift index 0e8587862..e153bc489 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+delete.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+delete.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree { +extension ARTreeImpl { public mutating func delete(key: Key) { if _root == nil { return diff --git a/Sources/ARTreeModule/ARTree/ARTree+insert.swift b/Sources/ARTreeModule/ARTree/ARTree+insert.swift index f139e527b..c8c4d54d6 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+insert.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension ARTree { +extension ARTreeImpl { fileprivate enum InsertAction { case replace(NodeLeaf) case splitLeaf(NodeLeaf, depth: Int) diff --git a/Sources/ARTreeModule/ARTree/ARTree+utils.swift b/Sources/ARTreeModule/ARTree/ARTree+utils.swift index 8ab2b6de3..d70cd9523 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+utils.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+utils.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -extension ARTree { +extension ARTreeImpl { static func allocateLeaf(key: Key, value: Value) -> NodeStorage> { return NodeLeaf.allocate(key: key, value: value) } diff --git a/Sources/ARTreeModule/ARTree/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift index aa991a9f7..4e2769a83 100644 --- a/Sources/ARTreeModule/ARTree/ARTree.swift +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -48,10 +48,10 @@ /// implements the same APIs. However, `ARTree` is optimized specifically for use cases /// where underlying keys share common prefixes. The underlying data-structure is a /// _persistent variant of _Adaptive Radix Tree (ART)_. -public typealias ARTree = ARTreeImpl> +typealias ARTree = ARTreeImpl> /// Implements a persistent Adaptive Radix Tree (ART). -public struct ARTreeImpl { +struct ARTreeImpl { public typealias Spec = Spec public typealias Value = Spec.Value diff --git a/Sources/ARTreeModule/ARTree/Node+StringConverter.swift b/Sources/ARTreeModule/ARTree/Node+StringConverter.swift index c11cc4208..e8c079187 100644 --- a/Sources/ARTreeModule/ARTree/Node+StringConverter.swift +++ b/Sources/ARTreeModule/ARTree/Node+StringConverter.swift @@ -164,7 +164,7 @@ extension Node48: NodePrettyPrinter { let last = total == count output += indent(depth, last: last) output += String(key) + ": " - output += child(at: Int(slot))!.prettyPrint(depth: depth + 1, with: Spec.self) + output += child(at: Int(key))!.prettyPrint(depth: depth + 1, with: Spec.self) if !last { output += "\n" } diff --git a/Sources/ARTreeModule/ARTree/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift index 75347c2ee..df9d6be0e 100644 --- a/Sources/ARTreeModule/ARTree/Node16.swift +++ b/Sources/ARTreeModule/ARTree/Node16.swift @@ -98,7 +98,7 @@ extension Node16: InternalNode { } func index(forKey k: KeyPart) -> Index? { - for (index, key) in keys.enumerated() { + for (index, key) in keys[.. NodeStorage { let storage = NodeStorage.allocate() - storage.update { newNode in + _ = storage.update { newNode in UnsafeMutableRawPointer(newNode.childs.baseAddress!) .bindMemory(to: RawNode?.self, capacity: Self.numKeys) } @@ -83,7 +83,7 @@ extension Node256: InternalNode { return nil } - func child(at: Int) -> RawNode? { + func child(at: Index) -> RawNode? { assert(at < 256, "maximum 256 childs allowed") return childs[at] } @@ -108,7 +108,6 @@ extension Node256: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { - assert(index < count, "not enough childs in node") let ref = childs.baseAddress! + index return body(ref) } diff --git a/Sources/ARTreeModule/ARTree/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift index ac7434846..2b04d3a02 100644 --- a/Sources/ARTreeModule/ARTree/Node4.swift +++ b/Sources/ARTreeModule/ARTree/Node4.swift @@ -77,7 +77,7 @@ extension Node4: InternalNode { } func index(forKey k: KeyPart) -> Index? { - for (index, key) in keys.enumerated() { + for (index, key) in keys[.. Index? { let childIndex = Int(keys[Int(k)]) - return childIndex == 0xFF ? nil : childIndex + return childIndex == 0xFF ? nil : Int(k) } func index() -> Index? { @@ -113,16 +113,17 @@ extension Node48: InternalNode { func next(index: Index) -> Index? { for idx: Int in index + 1..<256 { if keys[idx] != 0xFF { - return Int(keys[idx]) + return Int(idx) } } return nil } - func child(at: Int) -> RawNode? { - assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return childs[at] + func child(at: Index) -> RawNode? { + let slot = Int(keys[at]) + assert(slot != 0xFF, "no child at given slot") + return childs[slot] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { @@ -191,8 +192,8 @@ extension Node48: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { - assert(index < count, "not enough childs in node") - let ref = childs.baseAddress! + index + assert(keys[index] != 0xFF, "child doesn't exist in given slot") + let ref = childs.baseAddress! + Int(keys[index]) return body(ref) } } diff --git a/Sources/ARTreeModule/KeyConversions.swift b/Sources/ARTreeModule/KeyConversions.swift new file mode 100644 index 000000000..7058f5871 --- /dev/null +++ b/Sources/ARTreeModule/KeyConversions.swift @@ -0,0 +1,81 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 protocol ConvertibleToOrderedBytes { + func toOrderedBytes() -> [UInt8] + static func fromOrderedBytes(_ bytes: [UInt8]) -> Self +} + +///-- Unsigned Integers ------------------------------------------------------// + +extension UInt: ConvertibleToOrderedBytes { + public func toOrderedBytes() -> [UInt8] { + return withUnsafeBytes(of: self.bigEndian, Array.init) + } + + public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt16: ConvertibleToOrderedBytes { + public func toOrderedBytes() -> [UInt8] { + return withUnsafeBytes(of: self.bigEndian, Array.init) + } + + public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt32: ConvertibleToOrderedBytes { + public func toOrderedBytes() -> [UInt8] { + return withUnsafeBytes(of: self.bigEndian, Array.init) + } + + public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt64: ConvertibleToOrderedBytes { + public func toOrderedBytes() -> [UInt8] { + return withUnsafeBytes(of: self.bigEndian, Array.init) + } + + public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +///-- Bytes ------------------------------------------------------------------// + +extension [UInt8]: ConvertibleToOrderedBytes { + public func toOrderedBytes() -> [UInt8] { + return self + } + + public static func fromOrderedBytes(_ bytes: [UInt8]) -> Key { + return bytes + } +} diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift new file mode 100644 index 000000000..730390123 --- /dev/null +++ b/Sources/ARTreeModule/RadixTree.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 RadixTree { + var _tree: ARTree + + public init() { + self._tree = ARTree() + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree { + public mutating func insert(_ key: Key, _ value: Value) -> Bool { + let k = key.toOrderedBytes() + return _tree.insert(key: k, value: value) + } + + public mutating func getValue(_ key: Key) -> Value? { + let k = key.toOrderedBytes() + return _tree.getValue(key: k) + } + + public mutating func delete(_ key: Key) { + let k = key.toOrderedBytes() + _tree.delete(key: k) + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree: Sequence { + public struct Iterator: IteratorProtocol { + public typealias Element = (Key, Value) + + var _iter: ARTree.Iterator + + mutating public func next() -> Element? { + guard let (k, v) = _iter.next() else { return nil } + return (Key.fromOrderedBytes(k), v) + } + } + + public func makeIterator() -> Iterator { + return Iterator(_iter: _tree.makeIterator()) + } +} diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index 48057f6a5..ffebcb13a 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -13,7 +13,10 @@ import XCTest @testable import ARTreeModule -func randomByteArray(minSize: Int, maxSize: Int, minByte: UInt8, maxByte: UInt8) -> [UInt8] { +fileprivate func randomByteArray(minSize: Int, + maxSize: Int, + minByte: UInt8, + maxByte: UInt8) -> [UInt8] { let size = Int.random(in: minSize...maxSize) var result: [UInt8] = (0..(size: Int, + unique: Bool, + min: T, + max: T) -> [T] { + + if unique { + return (0..(size: Int, + unique: Bool, + min: T, + max: T, + debug: Bool = false) throws { + let testCase: [(T, Int)] = Array( + randomInts(size: size, + unique: unique, + min: min, + max: max) + .enumerated()) + .map { (v, k) in (k, v) } + + var t = RadixTree() + var m: [T: Int] = [:] + for (k, v) in testCase { + if debug { + print("Inserting \(k) --> \(v)") + } + _ = t.insert(k, v) + m[k] = v + } + + var total = 0 + var last = -1 + for (k, v) in t { + total += 1 + if debug { + print("Fetched \(k) --> \(v)") + } + XCTAssertEqual(v, m[k]) + XCTAssertLessThan(last, Int(k), "keys should be ordered") + last = Int(k) + + if total > m.count { + break + } + } + + XCTAssertEqual(total, m.count) + } + + func testUnsignedIntUnique() throws { + try _testCommon(size: 100000, + unique: true, + min: 0 as UInt, + max: 1 << 50 as UInt) + } + + func testUnsignedIntWithDuplicatesSmallSet() throws { + try _testCommon(size: 100, + unique: false, + min: 0 as UInt, + max: 50 as UInt) + } + + func testUnsignedIntWithDuplicatesLargeSet() throws { + try _testCommon(size: 1000000, + unique: false, + min: 0 as UInt, + max: 100000 as UInt) + } +} From f7d27b1a9238631da7107148984a7f52c44b363b Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sun, 3 Sep 2023 18:34:51 -0700 Subject: [PATCH 53/67] Support signed integer --- Sources/ARTreeModule/KeyConversions.swift | 85 +++++++++++++++++------ Sources/ARTreeModule/RadixTree.swift | 10 +-- Tests/ARTreeModuleTests/IntMapTests.swift | 54 +++++++++++--- 3 files changed, 114 insertions(+), 35 deletions(-) diff --git a/Sources/ARTreeModule/KeyConversions.swift b/Sources/ARTreeModule/KeyConversions.swift index 7058f5871..710132d65 100644 --- a/Sources/ARTreeModule/KeyConversions.swift +++ b/Sources/ARTreeModule/KeyConversions.swift @@ -9,19 +9,19 @@ // //===----------------------------------------------------------------------===// -public protocol ConvertibleToOrderedBytes { - func toOrderedBytes() -> [UInt8] - static func fromOrderedBytes(_ bytes: [UInt8]) -> Self +public protocol ConvertibleToBinaryComparableBytes { + func toBinaryComparableBytes() -> [UInt8] + static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self } -///-- Unsigned Integers ------------------------------------------------------// +///-- Unsigned Integers ----------------------------------------------------------------------// -extension UInt: ConvertibleToOrderedBytes { - public func toOrderedBytes() -> [UInt8] { +extension UInt: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { return withUnsafeBytes(of: self.bigEndian, Array.init) } - public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { let ii = bytes.withUnsafeBytes { $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee } @@ -29,12 +29,12 @@ extension UInt: ConvertibleToOrderedBytes { } } -extension UInt16: ConvertibleToOrderedBytes { - public func toOrderedBytes() -> [UInt8] { +extension UInt16: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { return withUnsafeBytes(of: self.bigEndian, Array.init) } - public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { let ii = bytes.withUnsafeBytes { $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee } @@ -42,12 +42,12 @@ extension UInt16: ConvertibleToOrderedBytes { } } -extension UInt32: ConvertibleToOrderedBytes { - public func toOrderedBytes() -> [UInt8] { +extension UInt32: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { return withUnsafeBytes(of: self.bigEndian, Array.init) } - public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { let ii = bytes.withUnsafeBytes { $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee } @@ -55,12 +55,12 @@ extension UInt32: ConvertibleToOrderedBytes { } } -extension UInt64: ConvertibleToOrderedBytes { - public func toOrderedBytes() -> [UInt8] { +extension UInt64: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { return withUnsafeBytes(of: self.bigEndian, Array.init) } - public static func fromOrderedBytes(_ bytes: [UInt8]) -> Self { + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { let ii = bytes.withUnsafeBytes { $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee } @@ -68,14 +68,59 @@ extension UInt64: ConvertibleToOrderedBytes { } } -///-- Bytes ------------------------------------------------------------------// +///-- Signed Integers ------------------------------------------------------------------------// -extension [UInt8]: ConvertibleToOrderedBytes { - public func toOrderedBytes() -> [UInt8] { +fileprivate func _flipSignBit(_ val: T) -> T { + return val ^ (1 << (T.bitWidth - 1)) +} + +extension Int: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { + return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +extension Int32: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { + return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +extension Int64: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { + return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +///-- Bytes ----------------------------------------------------------------------------------// + +extension [UInt8]: ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { return self } - public static func fromOrderedBytes(_ bytes: [UInt8]) -> Key { + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Key { return bytes } } diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift index 730390123..3436fa0d2 100644 --- a/Sources/ARTreeModule/RadixTree.swift +++ b/Sources/ARTreeModule/RadixTree.swift @@ -9,7 +9,7 @@ // //===----------------------------------------------------------------------===// -public struct RadixTree { +public struct RadixTree { var _tree: ARTree public init() { @@ -20,17 +20,17 @@ public struct RadixTree { @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RadixTree { public mutating func insert(_ key: Key, _ value: Value) -> Bool { - let k = key.toOrderedBytes() + let k = key.toBinaryComparableBytes() return _tree.insert(key: k, value: value) } public mutating func getValue(_ key: Key) -> Value? { - let k = key.toOrderedBytes() + let k = key.toBinaryComparableBytes() return _tree.getValue(key: k) } public mutating func delete(_ key: Key) { - let k = key.toOrderedBytes() + let k = key.toBinaryComparableBytes() _tree.delete(key: k) } } @@ -44,7 +44,7 @@ extension RadixTree: Sequence { mutating public func next() -> Element? { guard let (k, v) = _iter.next() else { return nil } - return (Key.fromOrderedBytes(k), v) + return (Key.fromBinaryComparableBytes(k), v) } } diff --git a/Tests/ARTreeModuleTests/IntMapTests.swift b/Tests/ARTreeModuleTests/IntMapTests.swift index 066cc8da0..fe275c2a5 100644 --- a/Tests/ARTreeModuleTests/IntMapTests.swift +++ b/Tests/ARTreeModuleTests/IntMapTests.swift @@ -19,7 +19,8 @@ fileprivate func randomInts(size: Int, max: T) -> [T] { if unique { - return (0..= size, "range not large enough") + return (min..(size: Int, @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) final class IntMapTests: XCTestCase { - func _testCommon(size: Int, + func _testCommon(size: Int, unique: Bool, min: T, max: T, @@ -51,16 +52,20 @@ final class IntMapTests: XCTestCase { } var total = 0 - var last = -1 + var last = T.min for (k, v) in t { - total += 1 if debug { print("Fetched \(k) --> \(v)") } + XCTAssertEqual(v, m[k]) - XCTAssertLessThan(last, Int(k), "keys should be ordered") - last = Int(k) + if total > 1 { + XCTAssertLessThan(last, k, "keys should be ordered") + } + last = k + + total += 1 if total > m.count { break } @@ -69,8 +74,15 @@ final class IntMapTests: XCTestCase { XCTAssertEqual(total, m.count) } - func testUnsignedIntUnique() throws { - try _testCommon(size: 100000, + func testUnsignedIntUniqueSmall() throws { + try _testCommon(size: 100, + unique: true, + min: 0 as UInt, + max: 1_000 as UInt) + } + + func testUnsignedIntUniqueLarge() throws { + try _testCommon(size: 100_000, unique: true, min: 0 as UInt, max: 1 << 50 as UInt) @@ -83,10 +95,32 @@ final class IntMapTests: XCTestCase { max: 50 as UInt) } + func testUnsignedInt32WithDuplicatesSmallSet() throws { + try _testCommon(size: 100, + unique: false, + min: 0 as UInt32, + max: 50 as UInt32) + } + func testUnsignedIntWithDuplicatesLargeSet() throws { - try _testCommon(size: 1000000, + try _testCommon(size: 1_000_000, unique: false, min: 0 as UInt, - max: 100000 as UInt) + max: 100_000 as UInt) + } + + func testSignedIntUniqueSmall() throws { + try _testCommon(size: 100, + unique: true, + min: -100 as Int, + max: 100 as Int, + debug: false) + } + + func testSignedIntUniqueLarge() throws { + try _testCommon(size: 1_000_000, + unique: true, + min: -100_000_000 as Int, + max: 100_000_000 as Int) } } From bd143f9c9e46d9c248ecbe199476632533959b7a Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Sun, 3 Sep 2023 20:49:25 -0700 Subject: [PATCH 54/67] Reorganize some tests --- Tests/ARTreeModuleTests/GetValueTests.swift | 55 ++-- Tests/ARTreeModuleTests/InsertTests.swift | 314 +++++++------------- 2 files changed, 146 insertions(+), 223 deletions(-) diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index ffebcb13a..cb37c6902 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -40,14 +40,13 @@ final class ARTreeGetValueTests: XCTestCase { XCTAssertEqual(t.getValue(key: [12, 22, 32]), [13, 23, 33]) } - func testGetValueRandom() throws { - // (0) Parameters. - let reps = 100 - let numKv = 10000 - let minSize = 10 - let maxSize = 100 - let minByte: UInt8 = 1 - let maxByte: UInt8 = 30 + func _testCommon(reps: Int, + numKv: Int, + minSize: Int, + maxSize: Int, + minByte: UInt8, + maxByte: UInt8, + debug: Bool = false) throws { for iteration in 1...reps { if iteration == (reps / 10) * iteration { @@ -69,18 +68,20 @@ final class ARTreeGetValueTests: XCTestCase { var testSetArray = Array(testSet.values) // (2) Insert into tree. - for (_, (key, value)) in testSetArray.enumerated() { + for (idx, (key, value)) in testSetArray.enumerated() { tree.insert(key: key, value: value) - // print("Inserted: \(idx + 1) \(key) -> \(value)") - // for (k, v) in testSetArray[0...idx] { - // let obs = tree.getValue(key: k) - // if obs ?? [] != v { - // print("Missed After Insert: \(k): \(obs) instead of \(v)") - // print(tree) - // XCTAssert(false) - // return - // } - // } + if debug { + print("Inserted: \(idx + 1) \(key) -> \(value)") + for (k, v) in testSetArray[0...idx] { + let obs = tree.getValue(key: k) + if obs ?? [] != v { + print("Missed After Insert: \(k): \(obs) instead of \(v)") + print(tree) + XCTAssert(false) + return + } + } + } } // (3) Shuffle test-set. @@ -98,10 +99,22 @@ final class ARTreeGetValueTests: XCTestCase { XCTAssertEqual(missed, 0) if missed > 0 { - print("Total = \(numKv), Matched = \(numKv - missed), Missed = \(missed)") - print(tree) + if debug { + print("Total = \(numKv), Matched = \(numKv - missed), Missed = \(missed)") + print(tree) + } return } } } + + func testGetKeyValue() throws { + try _testCommon( + reps: 100, + numKv: 10000, + minSize: 10, + maxSize: 100, + minByte: 1, + maxByte: 30) + } } diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 5b930ad20..86889a244 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -256,88 +256,94 @@ final class ARTreeInsertTests: XCTestCase { "└──○ 70: 1[70] -> [70]") } + func _testCommon(_ items: [[UInt8]], + expectedRootType: NodeType?, + reps: UInt = 1) throws { + for iter in 0..() + for (index, key) in items.enumerated() { + tree.insert(key: key, value: index + 10) + } + + for (index, key) in items.enumerated().shuffled() { + XCTAssertEqual(tree.getValue(key: key), index + 10) + } + } + } + + func _testCommon(_ items: [([UInt8], [UInt8])], + expectedRootType: NodeType?, + reps: UInt = 1) throws { + for iter in 0..() + for (key, value) in items { + tree.insert(key: key, value: value) + } + + for (key, value) in items.shuffled() { + XCTAssertEqual(tree.getValue(key: key), value) + } + } + } + func testInsertPrefixSharedSmall() throws { - let testCase: [[UInt8]] = [ + let items: [[UInt8]] = [ [1, 2, 3, 4, 5], [1, 2, 3, 4, 6], [1, 2, 3, 4, 7] ] - var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertPrefixLongOnNodePrefixFull() throws { - let testCase: [[UInt8]] = [ + let items: [[UInt8]] = [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 12] ] - var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertPrefixLongMultiLayer1() throws { - let testCase: [[UInt8]] = [ + let items: [[UInt8]] = [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 17], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 18] ] - var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertPrefixLongMultiLayer2() throws { - let testCase: [[UInt8]] = [ + let items: [[UInt8]] = [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16], [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 11, 12, 13, 14, 17], [1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 11, 12, 13, 14, 18] ] - var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertPrefixLongMultiLayer3() throws { - var testCase: [[UInt8]] = [ + var items: [[UInt8]] = [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 24] ] var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) + for (index, key) in items.enumerated() { + _ = tree.insert(key: key, value: index + 10) } XCTAssertEqual( @@ -350,114 +356,77 @@ final class ARTreeInsertTests: XCTestCase { "│ │ │ ├──○ 23: 21[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23] -> 11\n" + "│ │ │ └──○ 24: 21[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 24] -> 12") - testCase.append([1, 2, 3, 4, 5, 6, 7, 8, 10, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23]) - tree.insert(key:testCase.last!, value: 3 + 10) - for (val, test) in testCase.enumerated() { + items.append([1, 2, 3, 4, 5, 6, 7, 8, 10, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 23]) + + tree.insert(key:items.last!, value: 3 + 10) + for (val, test) in items.enumerated() { XCTAssertEqual(tree.getValue(key: test), val + 10) } } func testInsertPrefixLongMultiLayer5() throws { - let testCase: [[UInt8]] = [ + let items: [[UInt8]] = [ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99, 13, 14, 15, 17, 18, 19, 66, 21, 22, 77, 24, 25, 26, 27], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 88, 13, 14, 15, 17, 18, 19, 55, 21, 22, 66, 24, 25, 26, 27], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99, 13, 14, 15, 17, 18, 19, 66, 21, 22, 44, 24, 25, 26, 27], ] - var tree = ARTree() - for (index, test) in testCase.enumerated() { - tree.insert(key: test, value: index + 10) - } - - for (val, test) in testCase.enumerated() { - let result = tree.getValue(key: test) - XCTAssertEqual(result, val + 10) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertAndGetSmallSetRepeat48() throws { - for ii in 0...10000 { - if ii % 1000 == 0 { - print("testInsertAndGet: Iteration: \(ii)") - } - - var testCase: [([UInt8], [UInt8])] = [ - ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), - ([45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), - ([132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), - ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), - ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), - ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), - ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), - ] - - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node16) + let items: [([UInt8], [UInt8])] = [ + ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), + ([45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), + ([132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), + ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), + ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), + ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ] - testCase.shuffle() - for (k, v) in testCase { - XCTAssertEqual(tree.getValue(key: k), v) - } - } + try _testCommon(items, expectedRootType: .node16, reps: 100) } func testInsertAndGetSmallSetRepeat256() throws { - for ii in 0...10000 { - if ii % 1000 == 0 { - print("testInsertAndGet: Iteration: \(ii)") - } - - var testCase: [([UInt8], [UInt8])] = [ - ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), - ([15, 45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), - ([15, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([15, 45, 201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), - ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), - ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), - ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), - ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), - ([15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ([99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), - ([118, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([24, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([45, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([12, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([22, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), - ([15, 15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), - ([57, 99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), - ([57, 83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), - ([16, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([17, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([18, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([28, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([88, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([78, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ([19, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), - ] - - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node48) + let items: [([UInt8], [UInt8])] = [ + ([15, 118, 236, 37], [184, 222, 84, 178, 8, 42, 238, 20]), + ([15, 45, 142, 131, 183, 171, 108, 168], [153, 208, 8, 76, 71, 219]), + ([15, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([15, 45, 201, 251, 245, 67, 118, 215, 95], [232, 42, 102, 176, 41, 195, 118, 191]), + ([101, 234, 198, 223, 121, 0], [239, 66, 245, 90, 33, 99, 232, 56, 70, 210]), + ([172, 34, 70, 29, 249, 116, 239, 109], [209, 14, 239, 173, 182, 124, 148]), + ([99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([57, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ([15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([118, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([24, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([45, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([12, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([22, 9, 214, 179, 231, 31, 175, 125, 231, 83], [54, 4, 138, 111, 143, 121, 2]), + ([15, 15, 99, 136, 84, 183, 107], [151, 55, 124, 56, 254, 255, 106]), + ([57, 99, 118, 20, 190, 173, 101, 67, 245], [161, 154, 111, 179, 216, 198, 248, 206, 164, 243]), + ([57, 83, 22, 7, 62, 40, 239], [137, 78, 27, 99, 66]), + ([16, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([17, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([18, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([28, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([88, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([78, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ([19, 132, 29, 2, 67, 152, 3, 180, 115, 216, 202], [85, 13, 131, 117, 120]), + ] - testCase.shuffle() - for (k, v) in testCase { - XCTAssertEqual(tree.getValue(key: k), v) - } - } + try _testCommon(items, expectedRootType: .node48, reps: 100) } func testInsertPrefixSmallLong() throws { - var testCase: [([UInt8], [UInt8])] = [ + let items: [([UInt8], [UInt8])] = [ ([1, 2, 1, 2, 3, 2, 3, 3, 1, 3, 1, 0, 0, 2, 0, 3, 0, 1, 1], [1, 1, 2, 0, 3, 1, 3, 1, 0, 1, 3, 3, 1, 2, 3, 1, 1, 0, 1]), ([1, 3, 2, 2, 1, 0, 0, 2, 3, 2, 0], [0, 0, 2, 0, 3, 3, 0, 3, 3, 0, 2, 3, 3, 1, 2, 3, 2]), ([2, 0, 1, 0, 1, 2, 2, 3], [3, 3, 1, 3, 2, 2, 2, 1, 2, 0, 2, 1, 0, 0, 0, 2, 0, 1, 1, 1]), @@ -466,26 +435,11 @@ final class ARTreeInsertTests: XCTestCase { ([2, 1, 2, 3, 1, 2, 2, 1, 2, 1, 0, 2, 2, 1], [3, 2, 2, 3, 0, 1, 0, 3, 3, 0, 1, 2]), ] - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - tree.insert(key: k, value: v) - } - - var total = 0 - for (_, _)in tree { - total += 1 - } - XCTAssertEqual(total, testCase.count) - XCTAssertEqual(tree._root?.type, .node4) - - testCase.shuffle() - for (k, v) in testCase { - XCTAssertEqual(tree.getValue(key: k), v) - } + try _testCommon(items, expectedRootType: .node16) } func testInsertPrefixSmall2() throws { - var testCase: [([UInt8], [UInt8])] = [ + let items: [([UInt8], [UInt8])] = [ ([3, 1, 3, 1, 1, 2, 0], [2, 4, 0]), ([2, 4, 4, 0], [4, 1, 0]), ([3, 2, 1, 3, 1, 1, 0], [4, 3, 0]), @@ -497,100 +451,56 @@ final class ARTreeInsertTests: XCTestCase { ([3, 1, 0], [2, 2, 3, 3, 4, 0]), ] - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - print("Inserting \(k) \(v)") - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node4) - - testCase.reverse() - for (k, v) in testCase { - print("Checking \(k) \(v)") - XCTAssertEqual(tree.getValue(key: k), v) - } + try _testCommon(items, expectedRootType: .node16) } func testInsertLongSharedPrefix1() throws { - var testCase: [([UInt8], [UInt8])] = [ + let items: [([UInt8], [UInt8])] = [ ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), ] - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - print("Inserting \(k) \(v)") - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node4) - - testCase.reverse() - for (k, v) in testCase { - print("Checking \(k) \(v)") - XCTAssertEqual(tree.getValue(key: k), v) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertLongSharedPrefix2() throws { - var testCase: [([UInt8], [UInt8])] = [ + let items: [([UInt8], [UInt8])] = [ ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), ([1, 4, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [4]), ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20], [3]), ] - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - print("Inserting \(k) \(v)") - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node4) - - testCase.reverse() - for (k, v) in testCase { - print("Checking \(k) \(v)") - XCTAssertEqual(tree.getValue(key: k), v) - } + try _testCommon(items, expectedRootType: .node4) } func testInsertLongSharedPrefix3() throws { - var testCase: [([UInt8], [UInt8])] = [ + let items: [([UInt8], [UInt8])] = [ ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], [1]), ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19], [2]), ([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 20], [3]), ] - var tree = ARTree<[UInt8]>() - for (k, v) in testCase { - print("Inserting \(k) \(v)") - tree.insert(key: k, value: v) - } - XCTAssertEqual(tree._root?.type, .node4) - - testCase.reverse() - for (k, v) in testCase { - print("Checking \(k) \(v)") - XCTAssertEqual(tree.getValue(key: k), v) - } + try _testCommon(items, expectedRootType: .node4) } func testReplace() throws { var t = ARTree() - let testCases: [[UInt8]] = [ + let items: [[UInt8]] = [ [11, 21, 31], [12, 22, 32], [10, 20, 32] ] - for (idx, test) in testCases.enumerated() { + for (idx, test) in items.enumerated() { t.insert(key: test, value: idx) } - for (idx, test) in testCases.enumerated() { + for (idx, test) in items.enumerated() { t.insert(key: test, value: idx + 10) } - for (idx, test) in testCases.enumerated() { + for (idx, test) in items.enumerated() { XCTAssertEqual(t.getValue(key: test), idx + 10) } } From a67393a1518c16ec38745eeb3cf75cb72fc282aa Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 07:09:01 -0700 Subject: [PATCH 55/67] Reorganize some files --- Sources/ARTreeModule/ARTree/ARTree+insert.swift | 6 ++++++ Sources/ARTreeModule/ARTree/ARTree+utils.swift | 16 ---------------- ...verter.swift => Node+StringConvertible.swift} | 0 .../{Node+Storage.swift => NodeStorage.swift} | 0 ...dStorage.swift => UnmanagedNodeStorage.swift} | 0 5 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 Sources/ARTreeModule/ARTree/ARTree+utils.swift rename Sources/ARTreeModule/ARTree/{Node+StringConverter.swift => Node+StringConvertible.swift} (100%) rename Sources/ARTreeModule/ARTree/{Node+Storage.swift => NodeStorage.swift} (100%) rename Sources/ARTreeModule/ARTree/{Node+UnmanagedStorage.swift => UnmanagedNodeStorage.swift} (100%) diff --git a/Sources/ARTreeModule/ARTree/ARTree+insert.swift b/Sources/ARTreeModule/ARTree/ARTree+insert.swift index c8c4d54d6..2be1b7e84 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+insert.swift @@ -168,3 +168,9 @@ extension ARTreeImpl { fatalError("unexpected state") } } + +extension ARTreeImpl { + static func allocateLeaf(key: Key, value: Value) -> NodeStorage> { + return NodeLeaf.allocate(key: key, value: value) + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree+utils.swift b/Sources/ARTreeModule/ARTree/ARTree+utils.swift deleted file mode 100644 index d70cd9523..000000000 --- a/Sources/ARTreeModule/ARTree/ARTree+utils.swift +++ /dev/null @@ -1,16 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift Collections open source project -// -// Copyright (c) 2023 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 ARTreeImpl { - static func allocateLeaf(key: Key, value: Value) -> NodeStorage> { - return NodeLeaf.allocate(key: key, value: value) - } -} diff --git a/Sources/ARTreeModule/ARTree/Node+StringConverter.swift b/Sources/ARTreeModule/ARTree/Node+StringConvertible.swift similarity index 100% rename from Sources/ARTreeModule/ARTree/Node+StringConverter.swift rename to Sources/ARTreeModule/ARTree/Node+StringConvertible.swift diff --git a/Sources/ARTreeModule/ARTree/Node+Storage.swift b/Sources/ARTreeModule/ARTree/NodeStorage.swift similarity index 100% rename from Sources/ARTreeModule/ARTree/Node+Storage.swift rename to Sources/ARTreeModule/ARTree/NodeStorage.swift diff --git a/Sources/ARTreeModule/ARTree/Node+UnmanagedStorage.swift b/Sources/ARTreeModule/ARTree/UnmanagedNodeStorage.swift similarity index 100% rename from Sources/ARTreeModule/ARTree/Node+UnmanagedStorage.swift rename to Sources/ARTreeModule/ARTree/UnmanagedNodeStorage.swift From c9578a27f48c03145f607f18559878f27a5a281b Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 07:16:58 -0700 Subject: [PATCH 56/67] Remove old FixedArray implementation --- .../ARTree/FixedArray+Utils.swift | 45 +++++++++++ .../ARTreeModule/ARTree/FixedSizedArray.swift | 75 ------------------- Sources/ARTreeModule/ARTree/NodeHeader.swift | 4 +- Sources/ARTreeModule/CMakeLists.txt | 35 +++++---- 4 files changed, 64 insertions(+), 95 deletions(-) create mode 100644 Sources/ARTreeModule/ARTree/FixedArray+Utils.swift delete mode 100644 Sources/ARTreeModule/ARTree/FixedSizedArray.swift diff --git a/Sources/ARTreeModule/ARTree/FixedArray+Utils.swift b/Sources/ARTreeModule/ARTree/FixedArray+Utils.swift new file mode 100644 index 000000000..c121f9058 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/FixedArray+Utils.swift @@ -0,0 +1,45 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 FixedArray { + @inlinable + @inline(__always) + mutating func copy(src: ArraySlice, start: Int, count: Int) { + // TODO: memcpy? + for ii in 0.., start: Int, count: Int) { + for ii in 0.. { - var storage: (Elem, Elem, Elem, Elem, Elem, Elem, Elem, Elem) - - init(val: Elem) { - self.storage = (val, val, val, val, val, val, val, val) - } - - mutating func copy(src: ArraySlice, start: Int, count: Int) { - // TODO: memcpy? - for ii in 0.., start: Int, count: Int) { - for ii in 0.. Elem { - get { - precondition(0 <= position && position < FixedArray8Count, "\(position)") - return self[unchecked: position] - } - - set { - precondition(0 <= position && position < 8) - self[unchecked: position] = newValue - } - } - - subscript(unchecked position: Int) -> Elem { - get { - return withUnsafeBytes(of: storage) { (ptr) -> Elem in - let offset = MemoryLayout.stride &* position - return ptr.load(fromByteOffset: offset, as: Elem.self) - } - } - set { - let offset = MemoryLayout.stride &* position - withUnsafeMutableBytes(of: &storage) { (ptr) -> Void in - ptr.storeBytes( - of: newValue, - toByteOffset: offset, - as: Elem.self) - } - } - } -} diff --git a/Sources/ARTreeModule/ARTree/NodeHeader.swift b/Sources/ARTreeModule/ARTree/NodeHeader.swift index 6e0197ea5..24727fcfd 100644 --- a/Sources/ARTreeModule/ARTree/NodeHeader.swift +++ b/Sources/ARTreeModule/ARTree/NodeHeader.swift @@ -9,10 +9,10 @@ // //===----------------------------------------------------------------------===// -typealias PartialBytes = FixedSizedArray8 +typealias PartialBytes = FixedArray8 struct InternalNodeHeader { var count: UInt16 = 0 var partialLength: UInt8 = 0 - var partialBytes: PartialBytes = PartialBytes(val: 0) + var partialBytes: PartialBytes = PartialBytes(repeating: 0) } diff --git a/Sources/ARTreeModule/CMakeLists.txt b/Sources/ARTreeModule/CMakeLists.txt index 41f027e27..905158cac 100644 --- a/Sources/ARTreeModule/CMakeLists.txt +++ b/Sources/ARTreeModule/CMakeLists.txt @@ -8,32 +8,31 @@ See https://swift.org/LICENSE.txt for license information #]] add_library(ARTreeModule - "ARTree/ArtNode.swift" + "ARTree/ARTree+Sequence.swift" + "ARTree/ARTree+delete.swift" + "ARTree/ARTree+get.swift" "ARTree/ARTree+insert.swift" "ARTree/ARTree.swift" - "ARTree/FixedArray+impl.swift" - "ARTree/FixedSizedArray.swift" - "ARTree/Node+StringConverter.swift" - "ARTree/Node16.swift" - "ARTree/NodeHeader.swift" - "ARTree/RawNode.swift" - "ARTree/ARTree+delete.swift" - "ARTree/ARTree+Sequence.swift" + "ARTree/ArtNode.swift" "ARTree/CMakeLists.txt" + "ARTree/Consts and Specs.swift" "ARTree/FixedArray+Storage.swift" + "ARTree/FixedArray+Utils.swift" + "ARTree/FixedArray+impl.swift" + "ARTree/FixedArray.swift" "ARTree/InternalNode.swift" - "ARTree/Node+UnmanagedStorage.swift" + "ARTree/Node+StringConverter.swift" + "ARTree/Node16.swift" + "ARTree/Node256.swift" + "ARTree/Node4.swift" "ARTree/Node48.swift" + "ARTree/NodeHeader.swift" "ARTree/NodeLeaf.swift" - "ARTree/Utils.swift" - "ARTree/ARTree+get.swift" - "ARTree/ARTree+utils.swift" - "ARTree/Consts and Specs.swift" - "ARTree/FixedArray.swift" - "ARTree/Node+Storage.swif" - "ARTree/Node4.swift" - "ARTree/Node256.swift" + "ARTree/NodeStorage.swif" "ARTree/NodeType.swift" + "ARTree/RawNode.swift" + "ARTree/UnmanagedNodeStorage.swift" + "ARTree/Utils.swift" ) target_link_libraries(ARTreeModule PRIVATE _CollectionsUtilities) From 27bfed11685ddc8dafd9eaed76267dd210df6dff Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 09:11:13 -0700 Subject: [PATCH 57/67] Add README --- Sources/ARTreeModule/ARTree/ARTree.swift | 33 ++------------------ Sources/ARTreeModule/README.md | 38 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 Sources/ARTreeModule/README.md diff --git a/Sources/ARTreeModule/ARTree/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift index 4e2769a83..b5094ffd3 100644 --- a/Sources/ARTreeModule/ARTree/ARTree.swift +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -9,37 +9,6 @@ // //===----------------------------------------------------------------------===// -// TODO: -// -// - Ranged Operations -// - Subsequence Delete -// - Subsequence Iteration -// - Bulk insert -// - Merge operation -// - Disk-backed storage -// - Confirm to Swift Dictionary protocol -// - Optimizations -// - SIMD for Node4 -// - Binary Search for Node16 -// - Replace for loops with memcpy -// - Reduce refcount traffic -// - Leaf shouldn’t store entire key -// - Testing -// - Tests around edge cases in general -// - Tests around prefixes greater than maxPrefixLimit -// - Fuzz testing -// - Instrumentation and Profiling -// - Track number of allocations and deallocations -// - Write some performance benchmarks to compare against other data-structures -// - Use some well known datasets -// - Cost of dynamic dispatch -// - Refactoring and Maintenance -// - Documentation -// - Add assert to ensure invariants -// - Safer use of unmanaged objects -// - Potentially refactor to use FixedSizeArray and hence classes -// - ArtNode* is unmanaged and make its use contained within closures. - /// An ordered collection of unique keys and associated values, optimized for space, /// mutating shared copies, and efficient range operations, particularly read /// operations. @@ -48,6 +17,8 @@ /// implements the same APIs. However, `ARTree` is optimized specifically for use cases /// where underlying keys share common prefixes. The underlying data-structure is a /// _persistent variant of _Adaptive Radix Tree (ART)_. + +/// Alias for `ARTreeImpl` using the default specification. typealias ARTree = ARTreeImpl> /// Implements a persistent Adaptive Radix Tree (ART). diff --git a/Sources/ARTreeModule/README.md b/Sources/ARTreeModule/README.md new file mode 100644 index 000000000..b7ca774e1 --- /dev/null +++ b/Sources/ARTreeModule/README.md @@ -0,0 +1,38 @@ +Adaptive Radix Tree +=================== + +Todo +---- + +- Ranged Operations + - Subsequence Delete + - Subsequence Iteration +- Bulk insert +- Merge operation +- Disk-backed storage +- Confirm to Swift Collections protocol + - Implement non-owning Index/UnsafePath for ARTree + - Use Index/UnsafePath for sequence implementation +- Optimizations + - SIMD for Node4 + - Binary Search for Node16 + - Replace for loops with memcpy + - Reduce refcount traffic + - Leaf shouldn’t store entire key +- Testing + - Tests around edge cases in general + - Tests around prefixes greater than maxPrefixLimit + - Reuse some existing tests from other collections + - Use test assertion library in swift-collections + - Fuzz testing +- Instrumentation and Profiling + - Track number of allocations and deallocations + - Write some performance benchmarks to compare against other data-structures + - Use some well known datasets + - Cost of dynamic dispatch +- Refactoring and Maintenance + - Documentation + - Add assert to ensure invariants + - Safer use of unmanaged objects. Be more specific about ownership + - Potentially refactor to use FixedSizeArray and hence classes + - ArtNode* is unmanaged and make its use contained within closures From 6ecc661a4c78a5da7c55609aab3ceee369139303 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 09:15:50 -0700 Subject: [PATCH 58/67] Rename FixedStorage to FixedArrayStorage --- Sources/ARTreeModule/ARTree/FixedArray+Storage.swift | 12 ++++++------ Sources/ARTreeModule/ARTree/FixedArray.swift | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift b/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift index 9b87772d9..d9bfbae6e 100644 --- a/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift +++ b/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift @@ -9,14 +9,14 @@ // //===----------------------------------------------------------------------===// -protocol FixedStorage { +protocol FixedArrayStorage { associatedtype Element static var capacity: Int { get } init(repeating: Element) } -struct FixedStorage4: FixedStorage { +struct FixedArrayStorage4: FixedArrayStorage { internal var items: (T, T, T, T) @inline(__always) @@ -29,7 +29,7 @@ struct FixedStorage4: FixedStorage { } } -struct FixedStorage8: FixedStorage { +struct FixedArrayStorage8: FixedArrayStorage { internal var items: (T, T, T, T, T, T, T, T) @inline(__always) @@ -42,7 +42,7 @@ struct FixedStorage8: FixedStorage { } } -struct FixedStorage16: FixedStorage { +struct FixedArrayStorage16: FixedArrayStorage { internal var items: ( T, T, T, T, T, T, T, T, @@ -62,7 +62,7 @@ struct FixedStorage16: FixedStorage { } } -struct FixedStorage48: FixedStorage { +struct FixedArrayStorage48: FixedArrayStorage { internal var items: ( T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, @@ -84,7 +84,7 @@ struct FixedStorage48: FixedStorage { } } -struct FixedStorage256: FixedStorage { +struct FixedArrayStorage256: FixedArrayStorage { internal var items: ( T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, diff --git a/Sources/ARTreeModule/ARTree/FixedArray.swift b/Sources/ARTreeModule/ARTree/FixedArray.swift index b2568e993..10c40ab76 100644 --- a/Sources/ARTreeModule/ARTree/FixedArray.swift +++ b/Sources/ARTreeModule/ARTree/FixedArray.swift @@ -13,13 +13,13 @@ // //===----------------------------------------------------------------------===// -typealias FixedArray4 = FixedArray> -typealias FixedArray8 = FixedArray> -typealias FixedArray16 = FixedArray> -typealias FixedArray48 = FixedArray> -typealias FixedArray256 = FixedArray> +typealias FixedArray4 = FixedArray> +typealias FixedArray8 = FixedArray> +typealias FixedArray16 = FixedArray> +typealias FixedArray48 = FixedArray> +typealias FixedArray256 = FixedArray> -internal struct FixedArray { +internal struct FixedArray { typealias Element = Storage.Element internal var storage: Storage } From 1b74fbcacac26998c40b8f261012b785b8d3fbe5 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 09:29:23 -0700 Subject: [PATCH 59/67] Supress some warnings --- Sources/ARTreeModule/ARTree/ARTree+insert.swift | 2 +- Tests/ARTreeModuleTests/GetValueTests.swift | 16 ++++++++-------- Tests/ARTreeModuleTests/TreeRefCountTests.swift | 4 ++++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Sources/ARTreeModule/ARTree/ARTree+insert.swift b/Sources/ARTreeModule/ARTree/ARTree+insert.swift index 2be1b7e84..c07fec137 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+insert.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+insert.swift @@ -20,7 +20,7 @@ extension ARTreeImpl { @discardableResult public mutating func insert(key: Key, value: Value) -> Bool { - guard var (action, ref) = _findInsertNode(key: key) else { return false } + guard case (let action, var ref)? = _findInsertNode(key: key) else { return false } switch action { case .replace(let leaf): diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index cb37c6902..829a31ed4 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -73,13 +73,13 @@ final class ARTreeGetValueTests: XCTestCase { if debug { print("Inserted: \(idx + 1) \(key) -> \(value)") for (k, v) in testSetArray[0...idx] { - let obs = tree.getValue(key: k) - if obs ?? [] != v { - print("Missed After Insert: \(k): \(obs) instead of \(v)") - print(tree) - XCTAssert(false) - return - } + let obs = tree.getValue(key: k) + if obs ?? [] != v { + print("Missed After Insert: \(k): \(String(describing: obs)) instead of \(v)") + print(tree) + XCTAssert(false) + return + } } } } @@ -92,7 +92,7 @@ final class ARTreeGetValueTests: XCTestCase { for (key, value) in testSetArray { let obs = tree.getValue(key: key) ?? [] if obs != value { - print("Missed: \(key): \(value) got \(obs)") + print("Missed: \(key): \(value) got \(String(describing: obs))") missed += 1 } } diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 95d9a5ede..71aa05ceb 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -87,6 +87,8 @@ final class ARTreeRefCountTest: XCTestCase { let c = node.node.rawNode let count3 = _getRetainCount(ref) XCTAssertEqual(count3, count2 + 1) + + _ = (a, b, c) // FIXME: to suppress warning } func testRefCountReplace() throws { @@ -122,5 +124,7 @@ final class ARTreeRefCountTest: XCTestCase { _ = node.deleteChild(at: 0) XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref), "newNode can't be unique as it is should be referenced by clone as well") + + _ = (cloneNode) // FIXME: to suppress warning. } } From 6ef8f0851eb8400b211643540666a055381cf877 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 09:55:51 -0700 Subject: [PATCH 60/67] Use collection test library --- .../ARTreeModuleTests/CopyOnWriteTests.swift | 49 +++++++++-------- Tests/ARTreeModuleTests/DeleteTests.swift | 45 ++++++++------- Tests/ARTreeModuleTests/GetValueTests.swift | 17 +++--- Tests/ARTreeModuleTests/InsertTests.swift | 53 +++++++++--------- Tests/ARTreeModuleTests/IntMapTests.swift | 11 ++-- Tests/ARTreeModuleTests/Node16Tests.swift | 31 +++++------ Tests/ARTreeModuleTests/Node256Tests.swift | 15 +++-- Tests/ARTreeModuleTests/Node48Tests.swift | 15 +++-- Tests/ARTreeModuleTests/Node4Tests.swift | 39 +++++++------ Tests/ARTreeModuleTests/NodeBasicTests.swift | 15 +++-- Tests/ARTreeModuleTests/NodeLeafTests.swift | 47 ++++++++-------- Tests/ARTreeModuleTests/SequenceTests.swift | 13 ++--- .../ARTreeModuleTests/TreeRefCountTests.swift | 55 +++++++++---------- 13 files changed, 199 insertions(+), 206 deletions(-) diff --git a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift index ada855e92..37c40206a 100644 --- a/Tests/ARTreeModuleTests/CopyOnWriteTests.swift +++ b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift @@ -9,12 +9,17 @@ // //===----------------------------------------------------------------------===// -import XCTest +#if COLLECTIONS_SINGLE_MODULE +import Collections +#else +import ARTreeModule +import _CollectionsTestSupport +#endif @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeCopyOnWriteTests: XCTestCase { +final class ARTreeCopyOnWriteTests: CollectionTestCase { func testCopyOnWriteBasicInsert() throws { var t1 = ARTree() _ = t1.insert(key: [10, 20], value: 10) @@ -23,12 +28,12 @@ final class ARTreeCopyOnWriteTests: XCTestCase { _ = t2.insert(key: [30, 40], value: 30) _ = t2.insert(key: [40, 50], value: 40) - XCTAssertEqual( + expectEqual( t1.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 2[10, 20] -> 10\n" + "└──○ 20: 2[20, 30] -> 20") - XCTAssertEqual( + expectEqual( t2.description, "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 2[10, 20] -> 10\n" + @@ -49,19 +54,19 @@ final class ARTreeCopyOnWriteTests: XCTestCase { t3.delete(key: [10, 20]) t3.delete(key: [20]) - XCTAssertEqual( + expectEqual( t1.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 2[10, 20] -> 10\n" + "└──○ 20: 2[20, 30] -> 20") - XCTAssertEqual( + expectEqual( t2.description, "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 2[10, 20] -> 10\n" + "├──○ 20: 2[20, 30] -> 20\n" + "├──○ 30: 2[30, 40] -> 30\n" + "└──○ 40: 2[40, 50] -> 40") - XCTAssertEqual( + expectEqual( t3.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 20: 2[20, 30] -> 20\n" + @@ -94,7 +99,7 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" - XCTAssertEqual(t1_descp, t1.description) + expectEqual(t1_descp, t1.description) var t2 = t1 t2.insert(key: [5, 6, 7], value: 11) @@ -113,8 +118,8 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + "├──○ 5: 3[5, 6, 7] -> 11\n" + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" - XCTAssertEqual(t1.description, t1_descp) - XCTAssertEqual(t2.description, t2_descp) + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp) t2.delete(key: [2, 3, 4, 5, 6]) let t2_descp_2 = "○ Node16 {childs=6, partial=[]}\n" + "├──○ 1: Node4 {childs=3, partial=[2, 3]}\n" + @@ -129,13 +134,13 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + "├──○ 5: 3[5, 6, 7] -> 11\n" + "└──○ 8: 5[8, 9, 10, 12, 12] -> 5" - XCTAssertEqual(t1.description, t1_descp) - XCTAssertEqual(t2.description, t2_descp_2) + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp_2) var t3 = t2 t3.insert(key: [3, 4, 7], value: 11) - XCTAssertEqual(t1.description, t1_descp) - XCTAssertEqual(t2.description, t2_descp_2) + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp_2) t3.delete(key: [1, 2, 3, 4, 5]) t3.delete(key: [1, 2, 3, 6, 7]) t3.insert(key: [5, 6, 8], value: 14) @@ -156,9 +161,9 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "└──○ 8: Node4 {childs=2, partial=[9, 10]}\n" + "│ ├──○ 12: 5[8, 9, 10, 12, 12] -> 5\n" + "│ └──○ 13: 5[8, 9, 10, 13, 14] -> 15" - XCTAssertEqual(t1.description, t1_descp) - XCTAssertEqual(t2.description, t2_descp_2) - XCTAssertEqual(t3.description, t3_descp) + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp_2) + expectEqual(t3.description, t3_descp) t1.delete(key: [1, 2, 3, 4, 5]) t1.delete(key: [2, 3, 4, 5, 6]) t1.delete(key: [3, 4, 5, 6, 7]) @@ -169,9 +174,9 @@ final class ARTreeCopyOnWriteTests: XCTestCase { t1.delete(key: [2, 3, 5, 5, 6]) t1.delete(key: [4, 5, 6, 8, 8]) t1.delete(key: [4, 5, 6, 9, 9]) - XCTAssertEqual(t1.description, "<>") - XCTAssertEqual(t2.description, t2_descp_2) - XCTAssertEqual(t3.description, t3_descp) + expectEqual(t1.description, "<>") + expectEqual(t2.description, t2_descp_2) + expectEqual(t3.description, t3_descp) } func testCopyOnWriteReplaceValue() throws { @@ -225,7 +230,7 @@ final class ARTreeCopyOnWriteTests: XCTestCase { "│ ├──○ 8: 5[4, 5, 6, 8, 8] -> 9\n" + "│ └──○ 9: 5[4, 5, 6, 9, 9] -> 10\n" + "└──○ 8: 5[8, 9, 10, 12, 12] -> 12" - XCTAssertEqual(t1.description, t1_descp) - XCTAssertEqual(t2.description, t2_descp) + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp) } } diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift index 6fd47df9e..089f72650 100644 --- a/Tests/ARTreeModuleTests/DeleteTests.swift +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeDeleteTests: XCTestCase { +final class ARTreeDeleteTests: CollectionTestCase { override func setUp() { Const.testCheckUnique = true } @@ -29,7 +28,7 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [11, 21, 31], value: [12, 22, 32]) t.insert(key: [12, 22, 32], value: [13, 23, 33]) t.delete(key: [11, 21, 31]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + @@ -44,7 +43,7 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [10, 20, 30]) t.delete(key: [11, 21, 31]) t.delete(key: [12, 22, 32]) - XCTAssertEqual(t.description, "<>") + expectEqual(t.description, "<>") } func testDeleteNested1() throws { @@ -53,15 +52,15 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [4, 5, 6], value: [2]) t.insert(key: [1, 2, 4], value: [3]) t.delete(key: [1, 2, 4]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 3[1, 2, 3] -> [1]\n" + "└──○ 4: 3[4, 5, 6] -> [2]") t.delete(key: [1, 2, 3]) - XCTAssertEqual(t.description, "○ 3[4, 5, 6] -> [2]") + expectEqual(t.description, "○ 3[4, 5, 6] -> [2]") t.delete(key: [4, 5, 6]) - XCTAssertEqual(t.description, "<>") + expectEqual(t.description, "<>") } func testDeleteNested2() throws { @@ -72,7 +71,7 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [1, 2, 3, 4, 8, 9], value: [4]) t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) t.delete(key: [1, 2, 3, 4, 5, 6]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + @@ -88,7 +87,7 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) t.insert(key: [4, 5, 6, 7, 8, 9], value: [2]) t.delete(key: [1, 2, 3]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]\n" + @@ -104,7 +103,7 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [1, 2, 3, 4, 9, 9], value: [5]) t.delete(key: [1, 2, 3, 4, 5, 6]) t.delete(key: [1, 2, 3, 4, 8, 9]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 1: Node4 {childs=2, partial=[2]}\n" + @@ -114,9 +113,9 @@ final class ARTreeDeleteTests: XCTestCase { t.delete(key: [1, 2, 4, 5, 6, 7]) t.delete(key: [4, 5, 6, 7, 8, 9]) t.delete(key: [1, 2, 3, 4, 9, 9]) - XCTAssertEqual(t.description, "<>") + expectEqual(t.description, "<>") t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) - XCTAssertEqual(t.description, "○ Node4 {childs=1, partial=[]}\n" + + expectEqual(t.description, "○ Node4 {childs=1, partial=[]}\n" + "└──○ 1: 6[1, 2, 3, 4, 5, 6] -> [1]") } @@ -126,12 +125,12 @@ final class ARTreeDeleteTests: XCTestCase { _ = t1.insert(key: [4, 5, 6, 7, 8], value: 4) _ = t1.insert(key: [4, 5, 6, 8, 8], value: 9) t1.delete(key: [2, 3, 5, 5, 6]) - XCTAssertEqual(t1.description, "○ Node4 {childs=2, partial=[4, 5, 6]}\n" + + expectEqual(t1.description, "○ Node4 {childs=2, partial=[4, 5, 6]}\n" + "├──○ 7: 5[4, 5, 6, 7, 8] -> 4\n" + "└──○ 8: 5[4, 5, 6, 8, 8] -> 9") t1.delete(key: [4, 5, 6, 7, 8]) t1.delete(key: [4, 5, 6, 8, 8]) - XCTAssertEqual(t1.description, "<>") + expectEqual(t1.description, "<>") } func testDeleteCompressToNode4() throws { @@ -141,7 +140,7 @@ final class ARTreeDeleteTests: XCTestCase { t.insert(key: [3, 4, 5, 6, 7], value: [3]) t.insert(key: [4, 5, 6, 7, 8], value: [4]) t.insert(key: [5, 6, 7, 8, 9], value: [5]) - XCTAssertEqual( + expectEqual( t.description, "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + @@ -151,7 +150,7 @@ final class ARTreeDeleteTests: XCTestCase { "└──○ 5: 5[5, 6, 7, 8, 9] -> [5]") t.delete(key: [3, 4, 5, 6, 7]) t.delete(key: [4, 5, 6, 7, 8]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=3, partial=[]}\n" + "├──○ 1: 5[1, 2, 3, 4, 5] -> [1]\n" + @@ -164,10 +163,10 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...16 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t._root?.type, .node48) + expectEqual(t._root?.type, .node48) t.delete(key: [3, 4]) t.delete(key: [4, 5]) - XCTAssertEqual( + expectEqual( t.description, "○ Node48 {childs=15, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + @@ -187,7 +186,7 @@ final class ARTreeDeleteTests: XCTestCase { "└──○ 16: 2[16, 17] -> [16]") t.delete(key: [5, 6]) t.delete(key: [6, 7]) - XCTAssertEqual( + expectEqual( t.description, "○ Node16 {childs=13, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + @@ -210,14 +209,14 @@ final class ARTreeDeleteTests: XCTestCase { for i: UInt8 in 0...48 { t.insert(key: [i, i + 1], value: [i]) } - XCTAssertEqual(t._root?.type, .node256) + expectEqual(t._root?.type, .node256) for i: UInt8 in 24...40 { if i % 2 == 0 { t.delete(key: [i, i + 1]) } } - XCTAssertEqual(t._root?.type, .node48) - XCTAssertEqual( + expectEqual(t._root?.type, .node48) + expectEqual( t.description, "○ Node48 {childs=40, partial=[]}\n" + "├──○ 0: 2[0, 1] -> [0]\n" + diff --git a/Tests/ARTreeModuleTests/GetValueTests.swift b/Tests/ARTreeModuleTests/GetValueTests.swift index 829a31ed4..417b8060c 100644 --- a/Tests/ARTreeModuleTests/GetValueTests.swift +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -9,8 +9,7 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule fileprivate func randomByteArray(minSize: Int, @@ -24,10 +23,10 @@ fileprivate func randomByteArray(minSize: Int, } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeGetValueTests: XCTestCase { +final class ARTreeGetValueTests: CollectionTestCase { func testGetValueNil() throws { let t = ARTree<[UInt8]>() - XCTAssertEqual(t.getValue(key: [10, 20, 30]), nil) + expectEqual(t.getValue(key: [10, 20, 30]), nil) } func testGetValueBasic() throws { @@ -35,9 +34,9 @@ final class ARTreeGetValueTests: XCTestCase { t.insert(key: [10, 20, 30], value: [11, 21, 31]) t.insert(key: [11, 21, 31], value: [12, 22, 32]) t.insert(key: [12, 22, 32], value: [13, 23, 33]) - XCTAssertEqual(t.getValue(key: [10, 20, 30]), [11, 21, 31]) - XCTAssertEqual(t.getValue(key: [11, 21, 31]), [12, 22, 32]) - XCTAssertEqual(t.getValue(key: [12, 22, 32]), [13, 23, 33]) + expectEqual(t.getValue(key: [10, 20, 30]), [11, 21, 31]) + expectEqual(t.getValue(key: [11, 21, 31]), [12, 22, 32]) + expectEqual(t.getValue(key: [12, 22, 32]), [13, 23, 33]) } func _testCommon(reps: Int, @@ -77,7 +76,7 @@ final class ARTreeGetValueTests: XCTestCase { if obs ?? [] != v { print("Missed After Insert: \(k): \(String(describing: obs)) instead of \(v)") print(tree) - XCTAssert(false) + expectTrue(false) return } } @@ -97,7 +96,7 @@ final class ARTreeGetValueTests: XCTestCase { } } - XCTAssertEqual(missed, 0) + expectEqual(missed, 0) if missed > 0 { if debug { print("Total = \(numKv), Matched = \(numKv - missed), Missed = \(missed)") diff --git a/Tests/ARTreeModuleTests/InsertTests.swift b/Tests/ARTreeModuleTests/InsertTests.swift index 86889a244..d98c09b8a 100644 --- a/Tests/ARTreeModuleTests/InsertTests.swift +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeInsertTests: XCTestCase { +final class ARTreeInsertTests: CollectionTestCase { override func setUp() { Const.testCheckUnique = true } @@ -28,7 +27,7 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [10, 20, 30], value: [11, 21, 31]) t.insert(key: [11, 21, 31], value: [12, 22, 32]) t.insert(key: [12, 22, 32], value: [13, 23, 33]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [11, 21, 31]\n" + @@ -40,20 +39,20 @@ final class ARTreeInsertTests: XCTestCase { var t = ARTree<[UInt8]>() t.insert(key: [10, 20, 30], value: [1]) t.delete(key: [10, 20, 30]) - XCTAssertEqual(t.description, "<>") + expectEqual(t.description, "<>") t.insert(key: [11, 21, 31], value: [2]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=1, partial=[]}\n" + "└──○ 11: 3[11, 21, 31] -> [2]") t.insert(key: [10, 20, 30], value: [3]) t.delete(key: [10, 20, 30]) - XCTAssertEqual(t._root?.type, .leaf, "root should shrink to leaf") + expectEqual(t._root?.type, .leaf, "root should shrink to leaf") t.insert(key: [10, 20, 30], value: [4]) - XCTAssertEqual(t._root?.type, .node4, "should be able to insert into root leaf node") - XCTAssertEqual( + expectEqual(t._root?.type, .node4, "should be able to insert into root leaf node") + expectEqual( t.description, "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 3[10, 20, 30] -> [4]\n" + @@ -66,7 +65,7 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [11, 21, 31], value: [12, 22, 32]) t.insert(key: [12, 22, 32], value: [13, 23, 33]) t.insert(key: [10, 20, 32], value: [1]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: Node4 {childs=2, partial=[20]}\n" + @@ -82,7 +81,7 @@ final class ARTreeInsertTests: XCTestCase { t.insert(key: [2], value: [2]) t.insert(key: [3], value: [3]) t.insert(key: [4], value: [4]) - XCTAssertEqual( + expectEqual( t.description, "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -90,7 +89,7 @@ final class ARTreeInsertTests: XCTestCase { "├──○ 3: 1[3] -> [3]\n" + "└──○ 4: 1[4] -> [4]") t.insert(key: [5], value: [5]) - XCTAssertEqual( + expectEqual( t.description, "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -108,17 +107,17 @@ final class ARTreeInsertTests: XCTestCase { + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t._root?.type, .node4) + expectEqual(t._root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t._root?.type, .node16) + expectEqual(t._root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t._root?.type, .node48) + expectEqual(t._root?.type, .node48) } } let root: any InternalNode = t._root!.toInternalNode() - XCTAssertEqual(root.count, 40) - XCTAssertEqual( + expectEqual(root.count, 40) + expectEqual( t.description, "○ Node48 {childs=40, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -171,17 +170,17 @@ final class ARTreeInsertTests: XCTestCase { + 1], value: [ii + 1]) if ii < 4 { - XCTAssertEqual(t._root?.type, .node4) + expectEqual(t._root?.type, .node4) } else if ii < 16 { - XCTAssertEqual(t._root?.type, .node16) + expectEqual(t._root?.type, .node16) } else if ii < 48 { - XCTAssertEqual(t._root?.type, .node48) + expectEqual(t._root?.type, .node48) } } let root: any InternalNode = t._root!.toInternalNode() - XCTAssertEqual(root.count, 70) - XCTAssertEqual( + expectEqual(root.count, 70) + expectEqual( t.description, "○ Node256 {childs=70, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -270,7 +269,7 @@ final class ARTreeInsertTests: XCTestCase { } for (index, key) in items.enumerated().shuffled() { - XCTAssertEqual(tree.getValue(key: key), index + 10) + expectEqual(tree.getValue(key: key), index + 10) } } } @@ -289,7 +288,7 @@ final class ARTreeInsertTests: XCTestCase { } for (key, value) in items.shuffled() { - XCTAssertEqual(tree.getValue(key: key), value) + expectEqual(tree.getValue(key: key), value) } } } @@ -346,7 +345,7 @@ final class ARTreeInsertTests: XCTestCase { _ = tree.insert(key: key, value: index + 10) } - XCTAssertEqual( + expectEqual( tree.description, "○ Node4 {childs=1, partial=[]}\n" + "└──○ 1: Node4 {childs=1, partial=[2]}\n" + @@ -360,7 +359,7 @@ final class ARTreeInsertTests: XCTestCase { tree.insert(key:items.last!, value: 3 + 10) for (val, test) in items.enumerated() { - XCTAssertEqual(tree.getValue(key: test), val + 10) + expectEqual(tree.getValue(key: test), val + 10) } } @@ -501,7 +500,7 @@ final class ARTreeInsertTests: XCTestCase { } for (idx, test) in items.enumerated() { - XCTAssertEqual(t.getValue(key: test), idx + 10) + expectEqual(t.getValue(key: test), idx + 10) } } } diff --git a/Tests/ARTreeModuleTests/IntMapTests.swift b/Tests/ARTreeModuleTests/IntMapTests.swift index fe275c2a5..efaee1637 100644 --- a/Tests/ARTreeModuleTests/IntMapTests.swift +++ b/Tests/ARTreeModuleTests/IntMapTests.swift @@ -9,8 +9,7 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule fileprivate func randomInts(size: Int, @@ -27,7 +26,7 @@ fileprivate func randomInts(size: Int, } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class IntMapTests: XCTestCase { +final class IntMapTests: CollectionTestCase { func _testCommon(size: Int, unique: Bool, min: T, @@ -58,10 +57,10 @@ final class IntMapTests: XCTestCase { print("Fetched \(k) --> \(v)") } - XCTAssertEqual(v, m[k]) + expectEqual(v, m[k]) if total > 1 { - XCTAssertLessThan(last, k, "keys should be ordered") + expectLessThanOrEqual(last, k, "keys should be ordered") } last = k @@ -71,7 +70,7 @@ final class IntMapTests: XCTestCase { } } - XCTAssertEqual(total, m.count) + expectEqual(total, m.count) } func testUnsignedIntUniqueSmall() throws { diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index e5f8cac60..ccb14acc0 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -9,18 +9,17 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeNode16Tests: XCTestCase { +final class ARTreeNode16Tests: CollectionTestCase { func test16Basic() throws { typealias T = Tree<[UInt8]> var node = T.N16.allocate() _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [0])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + @@ -34,7 +33,7 @@ final class ARTreeNode16Tests: XCTestCase { _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [2])) _ = node.addChild(forKey: 30, node: T.Leaf.allocate(key: [30], value: [3])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [4])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + @@ -49,25 +48,25 @@ final class ARTreeNode16Tests: XCTestCase { _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 0) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 1) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") _ = node.deleteChild(at: 0) - XCTAssertEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") + expectEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } func test16DeleteKey() throws { @@ -76,25 +75,25 @@ final class ARTreeNode16Tests: XCTestCase { _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual( + expectEqual( node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") + expectEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } func test16ExpandTo48AndThenShrinkTo4() throws { @@ -103,7 +102,7 @@ final class ARTreeNode16Tests: XCTestCase { for ii: UInt8 in 0...15 { switch node.addChild(forKey: ii, node: T.Leaf.allocate(key: [ii], value: Int(ii) + 10)) { case .noop: break - case .replaceWith(_): XCTAssert(false, "node16 shouldn't expand just yet") + case .replaceWith(_): expectTrue(false, "node16 shouldn't expand just yet") } } @@ -115,7 +114,7 @@ final class ARTreeNode16Tests: XCTestCase { count -= 1 } } - XCTAssertEqual(newNode?.type, .node16) + expectEqual(newNode?.type, .node16) do { var count = 16 @@ -124,6 +123,6 @@ final class ARTreeNode16Tests: XCTestCase { count -= 1 } } - XCTAssertEqual(newNode?.type, .node4) + expectEqual(newNode?.type, .node4) } } diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index 17f49cd0f..9c29b0d89 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeNode256Tests: XCTestCase { +final class ARTreeNode256Tests: CollectionTestCase { typealias Leaf = NodeLeaf> typealias N256 = Node256> @@ -22,7 +21,7 @@ final class ARTreeNode256Tests: XCTestCase { var node = N256.allocate() _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [0])) _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [0]\n" + @@ -34,24 +33,24 @@ final class ARTreeNode256Tests: XCTestCase { _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node256 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 10) - XCTAssertEqual( + expectEqual( node.print(), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 15) - XCTAssertEqual( + expectEqual( node.print(), "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 20) - XCTAssertEqual(node.print(), "○ Node256 {childs=0, partial=[]}\n") + expectEqual(node.print(), "○ Node256 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index ddce99541..d7e15adb5 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -9,12 +9,11 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeNode48Tests: XCTestCase { +final class ARTreeNode48Tests: CollectionTestCase { typealias Leaf = NodeLeaf> typealias N48 = Node48> @@ -22,7 +21,7 @@ final class ARTreeNode48Tests: XCTestCase { var node = N48.allocate() _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [2])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + @@ -34,24 +33,24 @@ final class ARTreeNode48Tests: XCTestCase { _ = node.addChild(forKey: 10, node: Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node48 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 10) - XCTAssertEqual( + expectEqual( node.print(), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 15) - XCTAssertEqual( + expectEqual( node.print(), "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 20) - XCTAssertEqual(node.print(), "○ Node48 {childs=0, partial=[]}\n") + expectEqual(node.print(), "○ Node48 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 9117111c0..45ac92301 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -9,8 +9,7 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule struct Tree { @@ -46,13 +45,13 @@ extension NodeStorage where Mn: InternalNode { } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeNode4Tests: XCTestCase { +final class ARTreeNode4Tests: CollectionTestCase { func test4Basic() throws { typealias T = Tree<[UInt8]> var node = T.N4.allocate() _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [11])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [22])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> [11]\n" + @@ -64,7 +63,7 @@ final class ARTreeNode4Tests: XCTestCase { var node = T.N4.allocate() _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: 11)) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: 22)) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 10: 1[10] -> 11\n" + @@ -78,7 +77,7 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [2])) _ = node.addChild(forKey: 30, node: T.Leaf.allocate(key: [30], value: [3])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [4])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + @@ -101,21 +100,21 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.deleteChild(at: 0) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") let newNode = node.deleteChildReturn(at: 1) - XCTAssertEqual(newNode?.type, .leaf) + expectEqual(newNode?.type, .leaf) } func test4DeleteFromFull() throws { @@ -125,8 +124,8 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.addChild(forKey: 2, node: T.Leaf.allocate(key: [2], value: 2)) _ = node.addChild(forKey: 3, node: T.Leaf.allocate(key: [3], value: 3)) _ = node.addChild(forKey: 4, node: T.Leaf.allocate(key: [4], value: 4)) - XCTAssertEqual(node.type, .node4) - XCTAssertEqual( + expectEqual(node.type, .node4) + expectEqual( node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> 1\n" + @@ -134,7 +133,7 @@ final class ARTreeNode4Tests: XCTestCase { "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") _ = node.deleteChild(at: 1) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 1: 1[1] -> 1\n" + @@ -143,7 +142,7 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.deleteChild(at: 1) let newNode = node.deleteChildReturn(at: 1) - XCTAssertEqual(newNode?.type, .leaf) + expectEqual(newNode?.type, .leaf) } func test4ExpandTo16ThenShrinkTo4() throws { @@ -153,7 +152,7 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.addChild(forKey: 2, node: T.Leaf.allocate(key: [2], value: [2])) _ = node.addChild(forKey: 3, node: T.Leaf.allocate(key: [3], value: [3])) _ = node.addChild(forKey: 4, node: T.Leaf.allocate(key: [4], value: [4])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=4, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -162,7 +161,7 @@ final class ARTreeNode4Tests: XCTestCase { "└──○ 4: 1[4] -> [4]") let newNode = node.addChildReturn(forKey: 5, node: T.Leaf.allocate(key: [5], value: [5])) - XCTAssertEqual( + expectEqual( newNode!.print(with: T.Spec.self), "○ Node16 {childs=5, partial=[]}\n" + "├──○ 1: 1[1] -> [1]\n" + @@ -175,8 +174,8 @@ final class ARTreeNode4Tests: XCTestCase { var node: any InternalNode = newNode!.toInternalNode() _ = node.deleteChild(at: 4) switch node.deleteChild(at: 3) { - case .noop, .replaceWith(nil): XCTAssert(false, "node should shrink") - case .replaceWith(let newValue?): XCTAssertEqual(newValue.type, .node4) + case .noop, .replaceWith(nil): expectTrue(false, "node should shrink") + case .replaceWith(let newValue?): expectEqual(newValue.type, .node4) } } } @@ -187,20 +186,20 @@ final class ARTreeNode4Tests: XCTestCase { _ = node.addChild(forKey: 10, node: T.Leaf.allocate(key: [10], value: [1])) _ = node.addChild(forKey: 15, node: T.Leaf.allocate(key: [15], value: [2])) _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=3, partial=[]}\n" + "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } - XCTAssertEqual( + expectEqual( node.print(), "○ Node4 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift index de8ac2059..5f0b34f67 100644 --- a/Tests/ARTreeModuleTests/NodeBasicTests.swift +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -9,14 +9,13 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule -final class ARTreeNodeBasicTests: XCTestCase { +final class ARTreeNodeBasicTests: CollectionTestCase { func testNodeSizes() throws { let header = MemoryLayout.stride - XCTAssertEqual(header, 12) + expectEqual(header, 12) typealias Spec = DefaultSpec let childSlotSize = MemoryLayout.stride @@ -36,9 +35,9 @@ final class ARTreeNodeBasicTests: XCTestCase { print("sizeOf(.node48) = \(size48)") print("sizeOf(.node256) = \(size256)") - XCTAssertEqual(size4, header + 4 + 4 * ptrSize) - XCTAssertEqual(size16, header + 16 + 16 * ptrSize) - XCTAssertEqual(size48, header + 256 + 48 * ptrSize) - XCTAssertEqual(size256, header + 256 * ptrSize) + expectEqual(size4, header + 4 + 4 * ptrSize) + expectEqual(size16, header + 16 + 16 * ptrSize) + expectEqual(size48, header + 256 + 48 * ptrSize) + expectEqual(size256, header + 256 * ptrSize) } } diff --git a/Tests/ARTreeModuleTests/NodeLeafTests.swift b/Tests/ARTreeModuleTests/NodeLeafTests.swift index fd28a87e3..0edfea678 100644 --- a/Tests/ARTreeModuleTests/NodeLeafTests.swift +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -9,45 +9,44 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeNodeLeafTests: XCTestCase { +final class ARTreeNodeLeafTests: CollectionTestCase { func testLeafBasic() throws { typealias L = NodeLeaf> let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) - XCTAssertEqual(leaf1.print(), "○ 4[10, 20, 30, 40] -> [0]") + expectEqual(leaf1.print(), "○ 4[10, 20, 30, 40] -> [0]") let leaf2 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) - XCTAssertEqual(leaf2.print(), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") + expectEqual(leaf2.print(), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") let leaf3 = L.allocate(key: [], value: []) - XCTAssertEqual(leaf3.print(), "○ 0[] -> []") + expectEqual(leaf3.print(), "○ 0[] -> []") } func testLeafKeyEquals() throws { typealias L = NodeLeaf> let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) - XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30, 50])) - XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30])) - XCTAssertFalse(leaf1.node.keyEquals(with: [10, 20, 30, 40, 50])) - XCTAssertTrue(leaf1.node.keyEquals(with: [10, 20, 30, 40])) + expectFalse(leaf1.node.keyEquals(with: [10, 20, 30, 50])) + expectFalse(leaf1.node.keyEquals(with: [10, 20, 30])) + expectFalse(leaf1.node.keyEquals(with: [10, 20, 30, 40, 50])) + expectTrue(leaf1.node.keyEquals(with: [10, 20, 30, 40])) } func testCasts() throws { typealias L = NodeLeaf> let leaf = L.allocate(key: [10, 20, 30, 40], value: [0]) - XCTAssertEqual(leaf.node.key, [10, 20, 30, 40]) - XCTAssertEqual(leaf.node.value, [0]) + expectEqual(leaf.node.key, [10, 20, 30, 40]) + expectEqual(leaf.node.value, [0]) } func testLeafLcp() throws { typealias L = NodeLeaf> var leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) L.allocate(key: [0, 1, 2, 3], value: [0]).read { other in - XCTAssertEqual( + expectEqual( leaf1.node.longestCommonPrefix( with: other, fromIndex: 0), @@ -55,54 +54,54 @@ final class ARTreeNodeLeafTests: XCTestCase { } L.allocate(key: [0], value: [0]).read { other in - XCTAssertEqual( + expectEqual( leaf1.node.longestCommonPrefix( with:other, fromIndex: 0), 0) } L.allocate(key: [0, 1], value: [0]).read { other in - XCTAssertEqual( + expectEqual( leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 0) } L.allocate(key: [10, 1], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 1) } L.allocate(key: [10, 20], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 2) } L.allocate(key: [10, 20], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 1), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 1), 1) } L.allocate(key: [10, 20], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 2), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 2), 0) } leaf1 = L.allocate(key: [1, 2, 3, 4], value: [0]) L.allocate(key: [1, 2, 3, 4, 5, 6], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 4) } L.allocate(key: [1, 2, 3, 5, 5, 6], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 3) } L.allocate(key: [1, 2, 3, 4], value: [0]).read { other in - XCTAssertEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), 4) } // // Breaks the contract, so its OK that these fail. - // // XCTAssertEqual( + // // expectEqual( // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [], value: [0]), // // fromIndex: 0), // // 0) - // // XCTAssertEqual( + // // expectEqual( // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [10], value: [0]), // // fromIndex: 2), // // 0) diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift index ca19103b7..591b77256 100644 --- a/Tests/ARTreeModuleTests/SequenceTests.swift +++ b/Tests/ARTreeModuleTests/SequenceTests.swift @@ -9,19 +9,18 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeSequenceTests: XCTestCase { +final class ARTreeSequenceTests: CollectionTestCase { func testSequenceEmpty() throws { let t = ARTree<[UInt8]>() var total = 0 for (_, _) in t { total += 1 } - XCTAssertEqual(total, 0) + expectEqual(total, 0) } func testSequenceBasic() throws { @@ -44,10 +43,10 @@ final class ARTreeSequenceTests: XCTestCase { newPairs.append((k, v)) } - XCTAssertEqual(pairs.count, newPairs.count) + expectEqual(pairs.count, newPairs.count) for ((k1, v1), (k2, v2)) in zip(pairs, newPairs) { - XCTAssertEqual(k1, k2) - XCTAssertEqual(v1, v2) + expectEqual(k1, k2) + expectEqual(v1, v2) } } } diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 71aa05ceb..6c8a11809 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -9,8 +9,7 @@ // //===----------------------------------------------------------------------===// -import XCTest - +import _CollectionsTestSupport @testable import ARTreeModule private class TestBox { @@ -22,21 +21,21 @@ private class TestBox { } @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -final class ARTreeRefCountTest: XCTestCase { +final class ARTreeRefCountTest: CollectionTestCase { func testRefCountBasic() throws { // TODO: Why is it 2? var x = TestBox("foo") - XCTAssertEqual(_getRetainCount(x), 2) + expectEqual(_getRetainCount(x), 2) var t = ARTree() - XCTAssertEqual(_getRetainCount(x), 2) + expectEqual(_getRetainCount(x), 2) t.insert(key: [10, 20, 30], value: x) - XCTAssertEqual(_getRetainCount(x), 3) + expectEqual(_getRetainCount(x), 3) x = TestBox("bar") - XCTAssertEqual(_getRetainCount(x), 2) + expectEqual(_getRetainCount(x), 2) x = t.getValue(key: [10, 20, 30])! - XCTAssertEqual(_getRetainCount(x), 3) + expectEqual(_getRetainCount(x), 3) t.delete(key: [10, 20, 30]) - XCTAssertEqual(_getRetainCount(x), 2) + expectEqual(_getRetainCount(x), 2) } func testRefCountNode4() throws { @@ -45,11 +44,11 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [1, 2, 3], value: 10) t!.insert(key: [2, 4, 4], value: 20) - XCTAssertEqual(_getRetainCount(t!._root!.buf), 2) + expectEqual(_getRetainCount(t!._root!.buf), 2) var n4 = t!._root - XCTAssertEqual(_getRetainCount(n4!.buf), 3) + expectEqual(_getRetainCount(n4!.buf), 3) t = nil - XCTAssertEqual(_getRetainCount(n4!.buf), 2) + expectEqual(_getRetainCount(n4!.buf), 2) n4 = nil } @@ -62,11 +61,11 @@ final class ARTreeRefCountTest: XCTestCase { t!.insert(key: [4, 4, 4], value: 40) t!.insert(key: [5, 4, 4], value: 50) - XCTAssertEqual(_getRetainCount(t!._root!.buf), 2) + expectEqual(_getRetainCount(t!._root!.buf), 2) var n4 = t!._root - XCTAssertEqual(_getRetainCount(n4!.buf), 3) + expectEqual(_getRetainCount(n4!.buf), 3) t = nil - XCTAssertEqual(_getRetainCount(n4!.buf), 2) + expectEqual(_getRetainCount(n4!.buf), 2) n4 = nil } @@ -78,15 +77,15 @@ final class ARTreeRefCountTest: XCTestCase { let a = node.node let count1 = _getRetainCount(ref) - XCTAssertEqual(count1, count0) + expectEqual(count1, count0) let b = node.node let count2 = _getRetainCount(ref) - XCTAssertEqual(count2, count1) + expectEqual(count2, count1) let c = node.node.rawNode let count3 = _getRetainCount(ref) - XCTAssertEqual(count3, count2 + 1) + expectEqual(count3, count2 + 1) _ = (a, b, c) // FIXME: to suppress warning } @@ -95,34 +94,34 @@ final class ARTreeRefCountTest: XCTestCase { typealias Tree = ARTree var t = Tree() var v = TestBox("val1") - XCTAssertTrue(isKnownUniquelyReferenced(&v)) + expectTrue(isKnownUniquelyReferenced(&v)) let count0 = _getRetainCount(v) t.insert(key: [1, 2, 3], value: v) - XCTAssertFalse(isKnownUniquelyReferenced(&v)) - XCTAssertEqual(_getRetainCount(v), count0 + 1) + expectFalse(isKnownUniquelyReferenced(&v)) + expectEqual(_getRetainCount(v), count0 + 1) t.insert(key: [1, 2, 3], value: TestBox("val2")) - XCTAssertEqual(_getRetainCount(v), count0) - XCTAssertTrue(isKnownUniquelyReferenced(&v)) + expectEqual(_getRetainCount(v), count0) + expectTrue(isKnownUniquelyReferenced(&v)) } func testRefCountNode4ChildAndClone() throws { typealias Tree = ARTree var node = Node4.allocate() var newNode = Node4.allocate() - XCTAssertTrue(isKnownUniquelyReferenced(&newNode.ref)) + expectTrue(isKnownUniquelyReferenced(&newNode.ref)) _ = node.addChild(forKey: 10, node: newNode) - XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref)) + expectFalse(isKnownUniquelyReferenced(&newNode.ref)) _ = node.deleteChild(at: 0) - XCTAssertTrue(isKnownUniquelyReferenced(&newNode.ref)) + expectTrue(isKnownUniquelyReferenced(&newNode.ref)) // Now do same after cloning. _ = node.addChild(forKey: 10, node: newNode) - XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref)) + expectFalse(isKnownUniquelyReferenced(&newNode.ref)) let cloneNode = node.clone() _ = node.deleteChild(at: 0) - XCTAssertFalse(isKnownUniquelyReferenced(&newNode.ref), + expectFalse(isKnownUniquelyReferenced(&newNode.ref), "newNode can't be unique as it is should be referenced by clone as well") _ = (cloneNode) // FIXME: to suppress warning. From 5b183909808c6a869e433c6337c96ba843e2c6a4 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 10:07:35 -0700 Subject: [PATCH 61/67] change testRefCountBasic --- .../ARTreeModuleTests/TreeRefCountTests.swift | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index 6c8a11809..d00d1cee8 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -24,18 +24,20 @@ private class TestBox { final class ARTreeRefCountTest: CollectionTestCase { func testRefCountBasic() throws { // TODO: Why is it 2? - var x = TestBox("foo") - expectEqual(_getRetainCount(x), 2) + let x = TestBox("foo") + let rc1 = _getRetainCount(x) var t = ARTree() - expectEqual(_getRetainCount(x), 2) t.insert(key: [10, 20, 30], value: x) - expectEqual(_getRetainCount(x), 3) - x = TestBox("bar") - expectEqual(_getRetainCount(x), 2) - x = t.getValue(key: [10, 20, 30])! - expectEqual(_getRetainCount(x), 3) + expectEqual(_getRetainCount(x), rc1 + 1) + t.insert(key: [10, 20, 40], value: x) + t.insert(key: [10, 20, 50], value: x) + expectEqual(_getRetainCount(x), rc1 + 3) + t.delete(key: [10, 20, 40]) + expectEqual(_getRetainCount(x), rc1 + 2) + t.delete(key: [10, 20, 50]) + expectEqual(_getRetainCount(x), rc1 + 1) t.delete(key: [10, 20, 30]) - expectEqual(_getRetainCount(x), 2) + expectEqual(_getRetainCount(x), rc1) } func testRefCountNode4() throws { From 79abfd976793d8697561fcd1bbb2635d26be5889 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Mon, 4 Sep 2023 22:44:49 -0700 Subject: [PATCH 62/67] Fix IntMapTest unique number generation --- Sources/ARTreeModule/KeyConversions.swift | 90 +++++++++++++++++------ Tests/ARTreeModuleTests/IntMapTests.swift | 6 +- 2 files changed, 72 insertions(+), 24 deletions(-) diff --git a/Sources/ARTreeModule/KeyConversions.swift b/Sources/ARTreeModule/KeyConversions.swift index 710132d65..5f7458930 100644 --- a/Sources/ARTreeModule/KeyConversions.swift +++ b/Sources/ARTreeModule/KeyConversions.swift @@ -10,15 +10,25 @@ //===----------------------------------------------------------------------===// public protocol ConvertibleToBinaryComparableBytes { - func toBinaryComparableBytes() -> [UInt8] + func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self } +extension ConvertibleToBinaryComparableBytes { + public func toBinaryComparableBytes() -> [UInt8] { + self.withUnsafeBinaryComparableBytes { Array($0) } + } +} + ///-- Unsigned Integers ----------------------------------------------------------------------// extension UInt: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: self.bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -30,8 +40,11 @@ extension UInt: ConvertibleToBinaryComparableBytes { } extension UInt16: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: self.bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -43,8 +56,11 @@ extension UInt16: ConvertibleToBinaryComparableBytes { } extension UInt32: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: self.bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -56,8 +72,11 @@ extension UInt32: ConvertibleToBinaryComparableBytes { } extension UInt64: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: self.bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -75,8 +94,12 @@ fileprivate func _flipSignBit(_ val: T) -> } extension Int: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + let ii = _flipSignBit(self).bigEndian + return try withUnsafeBytes(of: ii) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -88,8 +111,12 @@ extension Int: ConvertibleToBinaryComparableBytes { } extension Int32: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + let ii = _flipSignBit(self).bigEndian + return try withUnsafeBytes(of: ii) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -101,8 +128,12 @@ extension Int32: ConvertibleToBinaryComparableBytes { } extension Int64: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return withUnsafeBytes(of: _flipSignBit(self).bigEndian, Array.init) + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + let ii = _flipSignBit(self).bigEndian + return try withUnsafeBytes(of: ii) { + try body($0) + } } public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { @@ -113,14 +144,27 @@ extension Int64: ConvertibleToBinaryComparableBytes { } } +///-- String ---------------------------------------------------------------------------------// + +// extension String: ConvertibleToBinaryComparableBytes { +// public func toBinaryComparableBytes() -> [UInt8] { +// return self.withUnsafe +// } + +// public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Key { +// return bytes +// } +// } + ///-- Bytes ----------------------------------------------------------------------------------// -extension [UInt8]: ConvertibleToBinaryComparableBytes { - public func toBinaryComparableBytes() -> [UInt8] { - return self - } +// TODO: Disable until, we support storing bytes with shared prefixes. +// extension [UInt8]: ConvertibleToBinaryComparableBytes { +// public func toBinaryComparableBytes() -> [UInt8] { +// return self +// } - public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Key { - return bytes - } -} +// public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Key { +// return bytes +// } +// } diff --git a/Tests/ARTreeModuleTests/IntMapTests.swift b/Tests/ARTreeModuleTests/IntMapTests.swift index efaee1637..84dc204c1 100644 --- a/Tests/ARTreeModuleTests/IntMapTests.swift +++ b/Tests/ARTreeModuleTests/IntMapTests.swift @@ -19,7 +19,11 @@ fileprivate func randomInts(size: Int, if unique { assert(max - min + 1 >= size, "range not large enough") - return (min..() + while uniques.count < size { + uniques.insert(.random(in: min...max)) + } + return Array(uniques) } else { return (0.. Date: Tue, 5 Sep 2023 12:28:05 -0700 Subject: [PATCH 63/67] Add String as BinaryComparableBytes --- Sources/ARTreeModule/KeyConversions.swift | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Sources/ARTreeModule/KeyConversions.swift b/Sources/ARTreeModule/KeyConversions.swift index 5f7458930..7c5467df5 100644 --- a/Sources/ARTreeModule/KeyConversions.swift +++ b/Sources/ARTreeModule/KeyConversions.swift @@ -146,15 +146,19 @@ extension Int64: ConvertibleToBinaryComparableBytes { ///-- String ---------------------------------------------------------------------------------// -// extension String: ConvertibleToBinaryComparableBytes { -// public func toBinaryComparableBytes() -> [UInt8] { -// return self.withUnsafe -// } +extension String: ConvertibleToBinaryComparableBytes { + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { -// public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Key { -// return bytes -// } -// } + try self.utf8CString.withUnsafeBytes { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + String(cString: bytes) + } +} ///-- Bytes ----------------------------------------------------------------------------------// From cf652bce1e3f221a92ba0d530c3ce07a0d0d7757 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Tue, 5 Sep 2023 13:42:39 -0700 Subject: [PATCH 64/67] Add subscript and dictionary literal --- Sources/ARTreeModule/CMakeLists.txt | 4 +- ...xTree+ExpressibleByDictionaryLiteral.swift | 34 +++++++++++ .../ARTreeModule/RadixTree+Subscripts.swift | 41 +++++++++++++ Sources/ARTreeModule/RadixTree.swift | 25 +++++++- Tests/ARTreeModuleTests/RadixTree.swift | 57 +++++++++++++++++++ 5 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 Sources/ARTreeModule/RadixTree+ExpressibleByDictionaryLiteral.swift create mode 100644 Sources/ARTreeModule/RadixTree+Subscripts.swift create mode 100644 Tests/ARTreeModuleTests/RadixTree.swift diff --git a/Sources/ARTreeModule/CMakeLists.txt b/Sources/ARTreeModule/CMakeLists.txt index 905158cac..01a74b8ea 100644 --- a/Sources/ARTreeModule/CMakeLists.txt +++ b/Sources/ARTreeModule/CMakeLists.txt @@ -32,7 +32,9 @@ add_library(ARTreeModule "ARTree/NodeType.swift" "ARTree/RawNode.swift" "ARTree/UnmanagedNodeStorage.swift" - "ARTree/Utils.swift" + "ARTree/Utils.swift", + "RadixTree.swift", + "KeyConversions.swift" ) target_link_libraries(ARTreeModule PRIVATE _CollectionsUtilities) diff --git a/Sources/ARTreeModule/RadixTree+ExpressibleByDictionaryLiteral.swift b/Sources/ARTreeModule/RadixTree+ExpressibleByDictionaryLiteral.swift new file mode 100644 index 000000000..7fb31c1d6 --- /dev/null +++ b/Sources/ARTreeModule/RadixTree+ExpressibleByDictionaryLiteral.swift @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree: ExpressibleByDictionaryLiteral { + /// Creates a new Radix Tree from the contents of a dictionary + /// literal. + /// + /// Duplicate elements in the literal are allowed, but the resulting + /// set will only contain the last occurrence of each. + /// + /// Do not call this initializer directly. It is used by the compiler when you + /// use a dictionary literal. Instead, create a new ordered dictionary using a + /// dictionary literal as its value by enclosing a comma-separated list of + /// values in square brackets. You can use an array literal anywhere a set is + /// expected by the type context. + /// + /// - Parameter elements: A variadic list of key-value pairs for the new + /// dictionary. + /// + /// - Complexity: O(?) + @inlinable + public init(dictionaryLiteral elements: (Key, Value)...) { + self.init(keysWithValues: elements) + } +} diff --git a/Sources/ARTreeModule/RadixTree+Subscripts.swift b/Sources/ARTreeModule/RadixTree+Subscripts.swift new file mode 100644 index 000000000..eed9cbfdf --- /dev/null +++ b/Sources/ARTreeModule/RadixTree+Subscripts.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree { + /// Accesses the value associated with the key for both read and write operations + /// + /// This key-based subscript returns the value for the given key if the key is found in + /// the tree, or nil if the key is not found. + /// + /// When you assign a value for a key and that key already exists, the tree overwrites + /// the existing value. If the tree doesn’t contain the key, the key and value are added + /// as a new key-value pair. + /// + /// - Parameter key: The key to find in the tree + /// - Returns: The value associated with key if key is in the tree; otherwise, nil. + /// - Complexity: O(?) + @inlinable + @inline(__always) + public subscript(key: Key) -> Value? { + get { + return self.getValue(key) + } + + set { + if let newValue = newValue { + _ = self.insert(key, newValue) + } else { + self.delete(key) + } + } + } +} diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift index 3436fa0d2..e417f9f96 100644 --- a/Sources/ARTreeModule/RadixTree.swift +++ b/Sources/ARTreeModule/RadixTree.swift @@ -17,6 +17,29 @@ public struct RadixTree { } } +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree { + /// Creates a Radix Tree collection from a sequence of key-value pairs. + /// + /// If duplicates are encountered the last instance of the key-value pair is the one + /// that is kept. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use + /// for the new Radix Tree. + /// - Complexity: O(?) + @inlinable + @inline(__always) + public init( + keysWithValues keysAndValues: S + ) where S: Sequence, S.Element == (key: Key, value: Value) { + self.init() + + for (key, value) in keysAndValues { + _ = self.insert(key, value) + } + } +} + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RadixTree { public mutating func insert(_ key: Key, _ value: Value) -> Bool { @@ -24,7 +47,7 @@ extension RadixTree { return _tree.insert(key: k, value: value) } - public mutating func getValue(_ key: Key) -> Value? { + public func getValue(_ key: Key) -> Value? { let k = key.toBinaryComparableBytes() return _tree.getValue(key: k) } diff --git a/Tests/ARTreeModuleTests/RadixTree.swift b/Tests/ARTreeModuleTests/RadixTree.swift new file mode 100644 index 000000000..9f75c4ba4 --- /dev/null +++ b/Tests/ARTreeModuleTests/RadixTree.swift @@ -0,0 +1,57 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +@testable import ARTreeModule + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +final class RadixTreeCollectionTests: CollectionTestCase { + func testDictionaryLiteral() throws { + let d: RadixTree = [ + "a": 0, + "b": 1, + "c": 2 + ] + + var items: [(String, Int)] = [] + for (key, value) in d { + items.append((key, value)) + } + + var count = 0 + for (lhs, rhs) in zip([("a", 0), ("b", 1), ("c", 2)], items) { + count += 1 + expectEqual(lhs, rhs) + } + + expectEqual(count, 3) + } + + func testSubscript() throws { + var d: RadixTree = [:] + d["a"] = 0 + d["b"] = 1 + d["c"] = 2 + + var items: [(String, Int)] = [] + for (key, value) in d { + items.append((key, value)) + } + + var count = 0 + for (lhs, rhs) in zip([("a", 0), ("b", 1), ("c", 2)], items) { + count += 1 + expectEqual(lhs, rhs) + } + + expectEqual(count, 3) + } +} From 562a42c05d4c5416739d07b532827ff9c3f17048 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Tue, 5 Sep 2023 14:37:15 -0700 Subject: [PATCH 65/67] Some documentation --- .../ARTreeModule/RadixTree+Subscripts.swift | 1 + Sources/ARTreeModule/RadixTree.swift | 24 +++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Sources/ARTreeModule/RadixTree+Subscripts.swift b/Sources/ARTreeModule/RadixTree+Subscripts.swift index eed9cbfdf..c769df7e3 100644 --- a/Sources/ARTreeModule/RadixTree+Subscripts.swift +++ b/Sources/ARTreeModule/RadixTree+Subscripts.swift @@ -9,6 +9,7 @@ // //===----------------------------------------------------------------------===// +// TODO: Subscripts with default value, ranges and index. @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RadixTree { /// Accesses the value associated with the key for both read and write operations diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift index e417f9f96..bb9ee1a03 100644 --- a/Sources/ARTreeModule/RadixTree.swift +++ b/Sources/ARTreeModule/RadixTree.swift @@ -9,9 +9,29 @@ // //===----------------------------------------------------------------------===// +/// A collection which maintains key-value pairs in compact and sorted order. The keys must confirm +/// to `ConvertibleToBinaryComparableBytes`, i.e. the keys can be converted to bytes, and the order +/// of key is defined by order of these bytes. +/// +/// `RadixTree` is optimized for space, mutating shared copies, and efficient range operations. +/// Compared to `SortedDictionary` in collection library, `RadixTree` is ideal for datasets where +/// keys often have common prefixes, or key comparison is expensive as `RadixTree` operations are +/// often O(k) instead of O(log(n)) where `k` is key length and `n` is number of items in +/// collection. +/// +/// `RadixTree` has the same functionality as a standard `Dictionary`, and it largely +/// implements the same APIs. However, `RadixTree` is optimized specifically for use cases +/// where underlying keys share common prefixes. The underlying data-structure is a +/// _persistent variant of _Adaptive Radix Tree (ART)_. public struct RadixTree { - var _tree: ARTree + @usableFromInline + internal var _tree: ARTree + /// Creates an empty tree. + /// + /// - Complexity: O(1) + @inlinable + @inline(__always) public init() { self._tree = ARTree() } @@ -26,7 +46,7 @@ extension RadixTree { /// /// - Parameter keysAndValues: A sequence of key-value pairs to use /// for the new Radix Tree. - /// - Complexity: O(?) + /// - Complexity: O(n*k) @inlinable @inline(__always) public init( From 2613cb2d44cf3d0001954ecdcebc1fd3b56c1445 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Tue, 5 Sep 2023 17:32:01 -0700 Subject: [PATCH 66/67] More documentation --- Sources/ARTreeModule/ARTree/ARTree.swift | 2 +- .../ARTreeModule/ARTree/InternalNode.swift | 6 +- Sources/ARTreeModule/ARTree/Node16.swift | 2 +- Sources/ARTreeModule/ARTree/Node256.swift | 2 +- Sources/ARTreeModule/ARTree/Node4.swift | 2 +- Sources/ARTreeModule/ARTree/Node48.swift | 2 +- Sources/ARTreeModule/ARTree/NodeStorage.swift | 4 +- .../ARTreeModule/RadixTree+Initializers.swift | 33 +++++++ Sources/ARTreeModule/RadixTree+Sequence.swift | 28 ++++++ .../ARTreeModule/RadixTree+Subscripts.swift | 8 +- Sources/ARTreeModule/RadixTree.swift | 92 +++++++++---------- Tests/ARTreeModuleTests/IntMapTests.swift | 2 +- Tests/ARTreeModuleTests/Node16Tests.swift | 16 ++-- Tests/ARTreeModuleTests/Node256Tests.swift | 6 +- Tests/ARTreeModuleTests/Node48Tests.swift | 6 +- Tests/ARTreeModuleTests/Node4Tests.swift | 22 ++--- .../ARTreeModuleTests/TreeRefCountTests.swift | 4 +- 17 files changed, 144 insertions(+), 93 deletions(-) create mode 100644 Sources/ARTreeModule/RadixTree+Initializers.swift create mode 100644 Sources/ARTreeModule/RadixTree+Sequence.swift diff --git a/Sources/ARTreeModule/ARTree/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift index b5094ffd3..e7dedec8a 100644 --- a/Sources/ARTreeModule/ARTree/ARTree.swift +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -22,7 +22,7 @@ typealias ARTree = ARTreeImpl> /// Implements a persistent Adaptive Radix Tree (ART). -struct ARTreeImpl { +internal struct ARTreeImpl { public typealias Spec = Spec public typealias Value = Spec.Value diff --git a/Sources/ARTreeModule/ARTree/InternalNode.swift b/Sources/ARTreeModule/ARTree/InternalNode.swift index d37f9a26c..5fd239504 100644 --- a/Sources/ARTreeModule/ARTree/InternalNode.swift +++ b/Sources/ARTreeModule/ARTree/InternalNode.swift @@ -31,7 +31,7 @@ protocol InternalNode: ArtNode { mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult - mutating func deleteChild(at index: Index) -> UpdateResult + mutating func removeChild(at index: Index) -> UpdateResult mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R } @@ -207,7 +207,7 @@ extension InternalNode { // - Remove successful, but we can shrink ourselves now with newValue. return withSelfOrClone(isUnique: isUnique) { var selfRef = $0 - switch selfRef.deleteChild(at: childPosition) { + switch selfRef.removeChild(at: childPosition) { case .noop: // Child removed successfully, nothing to do. Keep ourselves. return .replaceWith(selfRef.rawNode) @@ -224,7 +224,7 @@ extension InternalNode { return .replaceWith(newValue) case .replaceWith(nil): - fatalError("unexpected state: deleteChild should not be called with count == 1") + fatalError("unexpected state: removeChild should not be called with count == 1") } } diff --git a/Sources/ARTreeModule/ARTree/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift index df9d6be0e..739b91ddf 100644 --- a/Sources/ARTreeModule/ARTree/Node16.swift +++ b/Sources/ARTreeModule/ARTree/Node16.swift @@ -153,7 +153,7 @@ extension Node16: InternalNode { } } - mutating func deleteChild(at index: Index) -> UpdateResult { + mutating func removeChild(at index: Index) -> UpdateResult { assert(index < Self.numKeys, "index can't >= 16 in Node16") assert(index < count, "not enough childs in node") diff --git a/Sources/ARTreeModule/ARTree/Node256.swift b/Sources/ARTreeModule/ARTree/Node256.swift index 92a49d61b..4d13a9f05 100644 --- a/Sources/ARTreeModule/ARTree/Node256.swift +++ b/Sources/ARTreeModule/ARTree/Node256.swift @@ -95,7 +95,7 @@ extension Node256: InternalNode { return .noop } - mutating func deleteChild(at index: Index) -> UpdateResult { + mutating func removeChild(at index: Index) -> UpdateResult { childs[index] = nil count -= 1 diff --git a/Sources/ARTreeModule/ARTree/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift index 2b04d3a02..3587f06b2 100644 --- a/Sources/ARTreeModule/ARTree/Node4.swift +++ b/Sources/ARTreeModule/ARTree/Node4.swift @@ -130,7 +130,7 @@ extension Node4: InternalNode { } } - mutating func deleteChild(at index: Index) -> UpdateResult { + mutating func removeChild(at index: Index) -> UpdateResult { assert(index < 4, "index can't >= 4 in Node4") assert(index < count, "not enough childs in node") diff --git a/Sources/ARTreeModule/ARTree/Node48.swift b/Sources/ARTreeModule/ARTree/Node48.swift index 417b817fa..ee1c6ebb4 100644 --- a/Sources/ARTreeModule/ARTree/Node48.swift +++ b/Sources/ARTreeModule/ARTree/Node48.swift @@ -147,7 +147,7 @@ extension Node48: InternalNode { } } - public mutating func deleteChild(at index: Index) -> UpdateResult { + public mutating func removeChild(at index: Index) -> UpdateResult { let targetSlot = Int(keys[index]) assert(targetSlot != 0xFF, "slot is empty already") // 1. Find out who has the last slot. diff --git a/Sources/ARTreeModule/ARTree/NodeStorage.swift b/Sources/ARTreeModule/ARTree/NodeStorage.swift index f8a03a76c..5d58d15f0 100644 --- a/Sources/ARTreeModule/ARTree/NodeStorage.swift +++ b/Sources/ARTreeModule/ARTree/NodeStorage.swift @@ -102,9 +102,9 @@ extension NodeStorage where Mn: InternalNode { } } - mutating func deleteChild(at index: Index) -> UpdateResult { + mutating func removeChild(at index: Index) -> UpdateResult { update { - $0.deleteChild(at: index) + $0.removeChild(at: index) } } } diff --git a/Sources/ARTreeModule/RadixTree+Initializers.swift b/Sources/ARTreeModule/RadixTree+Initializers.swift new file mode 100644 index 000000000..8b2d624d3 --- /dev/null +++ b/Sources/ARTreeModule/RadixTree+Initializers.swift @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree { + /// Creates a Radix Tree collection from a sequence of key-value pairs. + /// + /// If duplicates are encountered the last instance of the key-value pair is the one + /// that is kept. + /// + /// - Parameter keysAndValues: A sequence of key-value pairs to use + /// for the new Radix Tree. + /// - Complexity: O(n*k) + @inlinable + @inline(__always) + public init( + keysWithValues keysAndValues: S + ) where S: Sequence, S.Element == (key: Key, value: Value) { + self.init() + + for (key, value) in keysAndValues { + _ = self.updateValue(value, forKey: key) + } + } +} diff --git a/Sources/ARTreeModule/RadixTree+Sequence.swift b/Sources/ARTreeModule/RadixTree+Sequence.swift new file mode 100644 index 000000000..ab5f5742c --- /dev/null +++ b/Sources/ARTreeModule/RadixTree+Sequence.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension RadixTree: Sequence { + public struct Iterator: IteratorProtocol { + public typealias Element = (Key, Value) + + var _iter: ARTree.Iterator + + mutating public func next() -> Element? { + guard let (k, v) = _iter.next() else { return nil } + return (Key.fromBinaryComparableBytes(k), v) + } + } + + public func makeIterator() -> Iterator { + return Iterator(_iter: _tree.makeIterator()) + } +} diff --git a/Sources/ARTreeModule/RadixTree+Subscripts.swift b/Sources/ARTreeModule/RadixTree+Subscripts.swift index c769df7e3..83ff29985 100644 --- a/Sources/ARTreeModule/RadixTree+Subscripts.swift +++ b/Sources/ARTreeModule/RadixTree+Subscripts.swift @@ -28,14 +28,14 @@ extension RadixTree { @inline(__always) public subscript(key: Key) -> Value? { get { - return self.getValue(key) + return self.getValue(forKey: key) } - + set { if let newValue = newValue { - _ = self.insert(key, newValue) + _ = self.updateValue(newValue, forKey: key) } else { - self.delete(key) + self.removeValue(forKey: key) } } } diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift index bb9ee1a03..81450a209 100644 --- a/Sources/ARTreeModule/RadixTree.swift +++ b/Sources/ARTreeModule/RadixTree.swift @@ -19,79 +19,69 @@ /// often O(k) instead of O(log(n)) where `k` is key length and `n` is number of items in /// collection. /// -/// `RadixTree` has the same functionality as a standard `Dictionary`, and it largely -/// implements the same APIs. However, `RadixTree` is optimized specifically for use cases -/// where underlying keys share common prefixes. The underlying data-structure is a -/// _persistent variant of _Adaptive Radix Tree (ART)_. +/// `RadixTree` has the same functionality as a standard `Dictionary`, and it largely implements the +/// same APIs. However, `RadixTree` is optimized specifically for use cases where underlying keys +/// share common prefixes. The underlying data-structure is a _persistent variant of _Adaptive Radix +/// Tree (ART)_. public struct RadixTree { - @usableFromInline internal var _tree: ARTree /// Creates an empty tree. /// /// - Complexity: O(1) - @inlinable - @inline(__always) public init() { self._tree = ARTree() } } +// MARK: Accessing Keys and Values @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RadixTree { - /// Creates a Radix Tree collection from a sequence of key-value pairs. + /// Returns the value associated with the key. /// - /// If duplicates are encountered the last instance of the key-value pair is the one - /// that is kept. - /// - /// - Parameter keysAndValues: A sequence of key-value pairs to use - /// for the new Radix Tree. - /// - Complexity: O(n*k) - @inlinable - @inline(__always) - public init( - keysWithValues keysAndValues: S - ) where S: Sequence, S.Element == (key: Key, value: Value) { - self.init() - - for (key, value) in keysAndValues { - _ = self.insert(key, value) - } + /// - Parameter key: The key to search for + /// - Returns: `nil` if the key was not present. Otherwise, the previous value. + /// - Complexity: O(`k`) where `k` is size of the key. + public func getValue(forKey key: Key) -> Value? { + let kb = key.toBinaryComparableBytes() + return _tree.getValue(key: kb) } } +// MARK: Mutations @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RadixTree { - public mutating func insert(_ key: Key, _ value: Value) -> Bool { - let k = key.toBinaryComparableBytes() - return _tree.insert(key: k, value: value) - } - - public func getValue(_ key: Key) -> Value? { - let k = key.toBinaryComparableBytes() - return _tree.getValue(key: k) - } - - public mutating func delete(_ key: Key) { - let k = key.toBinaryComparableBytes() - _tree.delete(key: k) + /// Updates the value stored in the tree for the given key, or adds a new key-value pair if the + /// key does not exist. + /// + /// Use this method instead of key-based subscripting when you need to know whether the new value + /// supplants the value of an existing key. If the value of an existing key is updated, + /// `updateValue(_:forKey:)` returns the original value. + /// + /// - Parameters: + /// - value: The new value to add to the tree. + /// - key: The key to associate with value. If key already exists in the tree, value replaces + /// the existing associated value. Inserts `(key, value)` otherwise. + /// - Returns: The value that was replaced, or nil if a new key-value pair was added. + /// - Complexity: O(`log n`) where `n` is the number of key-value pairs in the dictionary. + public mutating func updateValue(_ value: Value, forKey key: Key) -> Bool { + let kb = key.toBinaryComparableBytes() + return _tree.insert(key: kb, value: value) } } +// MARK: Removing Keys and Values @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) -extension RadixTree: Sequence { - public struct Iterator: IteratorProtocol { - public typealias Element = (Key, Value) - - var _iter: ARTree.Iterator - - mutating public func next() -> Element? { - guard let (k, v) = _iter.next() else { return nil } - return (Key.fromBinaryComparableBytes(k), v) - } - } - - public func makeIterator() -> Iterator { - return Iterator(_iter: _tree.makeIterator()) +extension RadixTree { + /// Removes the given key and its associated value from the tree. + /// + /// Calling this method invalidates any existing indices for use with this tree. + /// + /// - Parameter key: The key to remove along with its associated value. + /// - Returns: The value that was removed, or `nil` if the key was not present. + /// - Complexity: O(`log n`) where `n` is the number of key-value pairs in the collection. + public mutating func removeValue(forKey key: Key) { + let kb = key.toBinaryComparableBytes() + _tree.delete(key: kb) } } diff --git a/Tests/ARTreeModuleTests/IntMapTests.swift b/Tests/ARTreeModuleTests/IntMapTests.swift index 84dc204c1..01c5ca26c 100644 --- a/Tests/ARTreeModuleTests/IntMapTests.swift +++ b/Tests/ARTreeModuleTests/IntMapTests.swift @@ -50,7 +50,7 @@ final class IntMapTests: CollectionTestCase { if debug { print("Inserting \(k) --> \(v)") } - _ = t.insert(k, v) + _ = t.updateValue(v, forKey: k) m[k] = v } diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift index ccb14acc0..c6ead4a3a 100644 --- a/Tests/ARTreeModuleTests/Node16Tests.swift +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -54,18 +54,18 @@ final class ARTreeNode16Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 0) + _ = node.removeChild(at: 0) expectEqual( node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 1) + _ = node.removeChild(at: 1) expectEqual( node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 15: 1[15] -> [2]") - _ = node.deleteChild(at: 0) + _ = node.removeChild(at: 0) expectEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } @@ -81,18 +81,18 @@ final class ARTreeNode16Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } + _ = node.index(forKey: 10).flatMap { node.removeChild(at: $0) } expectEqual( node.print(), "○ Node16 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } + _ = node.index(forKey: 15).flatMap { node.removeChild(at: $0) } expectEqual( node.print(), "○ Node16 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 20).flatMap { node.deleteChild(at: $0) } + _ = node.index(forKey: 20).flatMap { node.removeChild(at: $0) } expectEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") } @@ -110,7 +110,7 @@ final class ARTreeNode16Tests: CollectionTestCase { do { var count = 48 while newNode?.type != .node16 && count > 0 { - newNode = node.deleteChildReturn(at: 4) + newNode = node.removeChildReturn(at: 4) count -= 1 } } @@ -119,7 +119,7 @@ final class ARTreeNode16Tests: CollectionTestCase { do { var count = 16 while newNode?.type != .node4 && count > 0 { - newNode = node.deleteChildReturn(at: 2) + newNode = node.removeChildReturn(at: 2) count -= 1 } } diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift index 9c29b0d89..e48abdd20 100644 --- a/Tests/ARTreeModuleTests/Node256Tests.swift +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -39,18 +39,18 @@ final class ARTreeNode256Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 10) + _ = node.removeChild(at: 10) expectEqual( node.print(), "○ Node256 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 15) + _ = node.removeChild(at: 15) expectEqual( node.print(), "○ Node256 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 20) + _ = node.removeChild(at: 20) expectEqual(node.print(), "○ Node256 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift index d7e15adb5..80370a299 100644 --- a/Tests/ARTreeModuleTests/Node48Tests.swift +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -39,18 +39,18 @@ final class ARTreeNode48Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 10) + _ = node.removeChild(at: 10) expectEqual( node.print(), "○ Node48 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 15) + _ = node.removeChild(at: 15) expectEqual( node.print(), "○ Node48 {childs=1, partial=[]}\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 20) + _ = node.removeChild(at: 20) expectEqual(node.print(), "○ Node48 {childs=0, partial=[]}\n") } } diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift index 45ac92301..aac092a94 100644 --- a/Tests/ARTreeModuleTests/Node4Tests.swift +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -34,8 +34,8 @@ extension NodeStorage where Mn: InternalNode { } } - mutating func deleteChildReturn(at idx: Index) -> RawNode? { - switch deleteChild(at: idx) { + mutating func removeChildReturn(at idx: Index) -> RawNode? { + switch removeChild(at: idx) { case .noop: return self.rawNode case .replaceWith(let newValue): @@ -106,14 +106,14 @@ final class ARTreeNode4Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.deleteChild(at: 0) + _ = node.removeChild(at: 0) expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - let newNode = node.deleteChildReturn(at: 1) + let newNode = node.removeChildReturn(at: 1) expectEqual(newNode?.type, .leaf) } @@ -132,7 +132,7 @@ final class ARTreeNode4Tests: CollectionTestCase { "├──○ 2: 1[2] -> 2\n" + "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") - _ = node.deleteChild(at: 1) + _ = node.removeChild(at: 1) expectEqual( node.print(), "○ Node4 {childs=3, partial=[]}\n" + @@ -140,8 +140,8 @@ final class ARTreeNode4Tests: CollectionTestCase { "├──○ 3: 1[3] -> 3\n" + "└──○ 4: 1[4] -> 4") - _ = node.deleteChild(at: 1) - let newNode = node.deleteChildReturn(at: 1) + _ = node.removeChild(at: 1) + let newNode = node.removeChildReturn(at: 1) expectEqual(newNode?.type, .leaf) } @@ -172,8 +172,8 @@ final class ARTreeNode4Tests: CollectionTestCase { do { var node: any InternalNode = newNode!.toInternalNode() - _ = node.deleteChild(at: 4) - switch node.deleteChild(at: 3) { + _ = node.removeChild(at: 4) + switch node.removeChild(at: 3) { case .noop, .replaceWith(nil): expectTrue(false, "node should shrink") case .replaceWith(let newValue?): expectEqual(newValue.type, .node4) } @@ -192,13 +192,13 @@ final class ARTreeNode4Tests: CollectionTestCase { "├──○ 10: 1[10] -> [1]\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 10).flatMap { node.deleteChild(at: $0) } + _ = node.index(forKey: 10).flatMap { node.removeChild(at: $0) } expectEqual( node.print(), "○ Node4 {childs=2, partial=[]}\n" + "├──○ 15: 1[15] -> [2]\n" + "└──○ 20: 1[20] -> [3]") - _ = node.index(forKey: 15).flatMap { node.deleteChild(at: $0) } + _ = node.index(forKey: 15).flatMap { node.removeChild(at: $0) } expectEqual( node.print(), "○ Node4 {childs=1, partial=[]}\n" + diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift index d00d1cee8..798516868 100644 --- a/Tests/ARTreeModuleTests/TreeRefCountTests.swift +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -115,14 +115,14 @@ final class ARTreeRefCountTest: CollectionTestCase { expectTrue(isKnownUniquelyReferenced(&newNode.ref)) _ = node.addChild(forKey: 10, node: newNode) expectFalse(isKnownUniquelyReferenced(&newNode.ref)) - _ = node.deleteChild(at: 0) + _ = node.removeChild(at: 0) expectTrue(isKnownUniquelyReferenced(&newNode.ref)) // Now do same after cloning. _ = node.addChild(forKey: 10, node: newNode) expectFalse(isKnownUniquelyReferenced(&newNode.ref)) let cloneNode = node.clone() - _ = node.deleteChild(at: 0) + _ = node.removeChild(at: 0) expectFalse(isKnownUniquelyReferenced(&newNode.ref), "newNode can't be unique as it is should be referenced by clone as well") From 1cf3b88cb3d237183cacd6482e4adaec48b17bb5 Mon Sep 17 00:00:00 2001 From: Vishesh Yadav Date: Wed, 6 Sep 2023 20:16:47 -0700 Subject: [PATCH 67/67] WIP on Collection protocol --- .../ARTree/ARTree+Collection.swift | 23 ++++ .../ARTreeModule/ARTree/ARTree+Sequence.swift | 12 +-- .../ARTreeModule/ARTree/ARTree.Index.swift | 100 ++++++++++++++++++ Sources/ARTreeModule/ARTree/ARTree.swift | 2 + Sources/ARTreeModule/ARTree/ArtNode.swift | 6 ++ .../ARTreeModule/ARTree/InternalNode.swift | 20 ++-- Sources/ARTreeModule/ARTree/Node16.swift | 22 ++-- Sources/ARTreeModule/ARTree/Node256.swift | 28 +++-- Sources/ARTreeModule/ARTree/Node4.swift | 22 ++-- Sources/ARTreeModule/ARTree/Node48.swift | 27 +++-- Sources/ARTreeModule/ARTree/RawNode.swift | 7 ++ .../{RadixTree.swift => RadixTreeTests.swift} | 22 ++++ 12 files changed, 239 insertions(+), 52 deletions(-) create mode 100644 Sources/ARTreeModule/ARTree/ARTree+Collection.swift create mode 100644 Sources/ARTreeModule/ARTree/ARTree.Index.swift rename Tests/ARTreeModuleTests/{RadixTree.swift => RadixTreeTests.swift} (80%) diff --git a/Sources/ARTreeModule/ARTree/ARTree+Collection.swift b/Sources/ARTreeModule/ARTree/ARTree+Collection.swift new file mode 100644 index 000000000..94d82c366 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+Collection.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl { + var startIndex: Index { + var idx = Index(forTree: self) + idx.descentToLeftMostChild() + return idx + } + + var endIndex: Index { + return Index(forTree: self) + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift index 355aedfce..39e3ee436 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift @@ -17,7 +17,7 @@ extension ARTreeImpl: Sequence { typealias _ChildIndex = InternalNode.Index private let tree: ARTreeImpl - private var path: [(any InternalNode, _ChildIndex?)] + private var path: [(any InternalNode, _ChildIndex)] init(tree: ARTreeImpl) { self.tree = tree @@ -27,7 +27,7 @@ extension ARTreeImpl: Sequence { assert(node.type != .leaf, "root can't be leaf") let n: any InternalNode = node.toInternalNode() if n.count > 0 { - self.path = [(n, n.index())] + self.path = [(n, n.startIndex)] } } } @@ -53,13 +53,13 @@ extension ARTreeImpl._Iterator: IteratorProtocol { return } - path.append((node, node.next(index: index!))) + path.append((node, node.index(after: index))) } mutating func next() -> Element? { while !path.isEmpty { - while let (node, _index) = path.last { - guard let index = _index else { + while let (node, index) = path.last { + if index == node.endIndex { advanceToSibling() break } @@ -73,7 +73,7 @@ extension ARTreeImpl._Iterator: IteratorProtocol { } let nextNode: any InternalNode = next.toInternalNode() - path.append((nextNode, nextNode.index())) + path.append((nextNode, nextNode.startIndex)) } } diff --git a/Sources/ARTreeModule/ARTree/ARTree.Index.swift b/Sources/ARTreeModule/ARTree/ARTree.Index.swift new file mode 100644 index 000000000..5cef84ba3 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree.Index.swift @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 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 +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl { + public struct Index { + internal typealias _ChildIndex = InternalNode.Index + + internal weak var root: RawNodeBuffer? = nil + internal var current: (any ArtNode)? = nil + internal var path: [(any InternalNode, _ChildIndex)] = [] + internal let version: Int + + internal init(forTree tree: ARTreeImpl) { + self.version = tree.version + + if let root = tree._root { + assert(root.type != .leaf, "root can't be leaf") + self.root = tree._root?.buf + self.current = tree._root?.toArtNode() + } + } + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index { + internal var isOnLeaf: Bool { + if let current = self.current { + return current.type == .leaf + } + + return false + } + + internal mutating func descentToLeftMostChild() { + while !isOnLeaf { + descend { $0.startIndex } + } + } + + internal mutating func descend(_ to: (any InternalNode) + -> (any InternalNode).Index) { + assert(!isOnLeaf, "can't descent on a leaf node") + assert(current != nil, "current node can't be nil") + + let currentNode: any InternalNode = current!.rawNode.toInternalNode() + let index = to(currentNode) + self.path.append((currentNode, index)) + self.current = currentNode.child(at: index)?.toArtNode() + } + + mutating private func advanceToSibling() { + let _ = path.popLast() + advanceToNextChild() + } + + mutating private func advanceToNextChild() { + guard let (node, index) = path.popLast() else { + return + } + + path.append((node, node.index(after: index))) + } +} + + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index: Equatable { + static func == (lhs: Self, rhs: Self) -> Bool { + if case (let lhs?, let rhs?) = (lhs.current, rhs.current) { + return lhs.equals(rhs) + } + + return false + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index: Comparable { + static func < (lhs: Self, rhs: Self) -> Bool { + for ((_, idxL), (_, idxR)) in zip(lhs.path, rhs.path) { + if idxL < idxR { + return true + } else if idxL > idxR { + return false + } + } + + return false + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift index e7dedec8a..230bca624 100644 --- a/Sources/ARTreeModule/ARTree/ARTree.swift +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -28,9 +28,11 @@ internal struct ARTreeImpl { @usableFromInline internal var _root: RawNode? + internal var version: Int @inlinable public init() { self._root = nil + self.version = -1 } } diff --git a/Sources/ARTreeModule/ARTree/ArtNode.swift b/Sources/ARTreeModule/ARTree/ArtNode.swift index 8e1619efa..016967f45 100644 --- a/Sources/ARTreeModule/ARTree/ArtNode.swift +++ b/Sources/ARTreeModule/ARTree/ArtNode.swift @@ -40,3 +40,9 @@ extension ArtNode { var rawNode: RawNode { RawNode(buf: self.storage.ref.takeUnretainedValue()) } var type: NodeType { Self.type } } + +extension ArtNode { + func equals(_ other: any ArtNode) -> Bool { + return self.rawNode == other.rawNode + } +} diff --git a/Sources/ARTreeModule/ARTree/InternalNode.swift b/Sources/ARTreeModule/ARTree/InternalNode.swift index 5fd239504..71cc357b1 100644 --- a/Sources/ARTreeModule/ARTree/InternalNode.swift +++ b/Sources/ARTreeModule/ARTree/InternalNode.swift @@ -17,23 +17,25 @@ protocol InternalNode: ArtNode { static var size: Int { get } - var count: Int { get set } var partialLength: Int { get } var partialBytes: PartialBytes { get set } - func index(forKey k: KeyPart) -> Index? - func index() -> Index? - func next(index: Index) -> Index? + var count: Int { get set } + var startIndex: Index { get } + var endIndex: Index { get } + + func index(forKey: KeyPart) -> Index? + func index(after: Index) -> Index - func child(forKey k: KeyPart) -> RawNode? // TODO: Remove + func child(forKey: KeyPart) -> RawNode? // TODO: Remove func child(at: Index) -> RawNode? // TODO: Remove - mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult - mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult + mutating func addChild(forKey: KeyPart, node: RawNode) -> UpdateResult + mutating func addChild(forKey: KeyPart, node: some ArtNode) -> UpdateResult - mutating func removeChild(at index: Index) -> UpdateResult + mutating func removeChild(at: Index) -> UpdateResult - mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R + mutating func withChildRef(at: Index, _ body: (RawNode.SlotRef) -> R) -> R } struct NodeReference { diff --git a/Sources/ARTreeModule/ARTree/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift index 739b91ddf..5da85eea7 100644 --- a/Sources/ARTreeModule/ARTree/Node16.swift +++ b/Sources/ARTreeModule/ARTree/Node16.swift @@ -97,6 +97,9 @@ extension Node16: InternalNode { * (MemoryLayout.stride + MemoryLayout.stride) } + var startIndex: Index { 0 } + var endIndex: Index { count } + func index(forKey k: KeyPart) -> Index? { for (index, key) in keys[.. Index? { - return 0 - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { let next = index + 1 - return next < count ? next : nil + if next >= count { + return count + } else { + return next + } } func _insertSlot(forKey k: KeyPart) -> Int? { @@ -131,9 +134,10 @@ extension Node16: InternalNode { return count } - func child(at: Index) -> RawNode? { - assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") + assert(index < count, "not enough childs in node") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { diff --git a/Sources/ARTreeModule/ARTree/Node256.swift b/Sources/ARTreeModule/ARTree/Node256.swift index 4d13a9f05..73e98eb65 100644 --- a/Sources/ARTreeModule/ARTree/Node256.swift +++ b/Sources/ARTreeModule/ARTree/Node256.swift @@ -65,27 +65,33 @@ extension Node256: InternalNode { MemoryLayout.stride + 256 * MemoryLayout.stride } - func index(forKey k: KeyPart) -> Index? { - return childs[Int(k)] != nil ? Int(k) : nil + var startIndex: Index { + if count == 0 { + return endIndex + } else { + return index(after: -1) + } } - func index() -> Index? { - return next(index: -1) + var endIndex: Index { 256 } + + func index(forKey k: KeyPart) -> Index? { + return childs[Int(k)] != nil ? Int(k) : nil } - func next(index: Index) -> Index? { - for idx in index + 1..<256 { + func index(after idx: Index) -> Index { + for idx in idx + 1..<256 { if childs[idx] != nil { return idx } } - return nil + return 256 } - func child(at: Index) -> RawNode? { - assert(at < 256, "maximum 256 childs allowed") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < 256, "maximum 256 childs allowed") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { @@ -96,6 +102,7 @@ extension Node256: InternalNode { } mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 256, "invalid index") childs[index] = nil count -= 1 @@ -108,6 +115,7 @@ extension Node256: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { + assert(index < 256, "invalid index") let ref = childs.baseAddress! + index return body(ref) } diff --git a/Sources/ARTreeModule/ARTree/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift index 3587f06b2..273d15bfa 100644 --- a/Sources/ARTreeModule/ARTree/Node4.swift +++ b/Sources/ARTreeModule/ARTree/Node4.swift @@ -76,6 +76,9 @@ extension Node4: InternalNode { * (MemoryLayout.stride + MemoryLayout.stride) } + var startIndex: Index { 0 } + var endIndex: Index { count } + func index(forKey k: KeyPart) -> Index? { for (index, key) in keys[.. Index? { - return 0 - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { let next = index + 1 - return next < count ? next : nil + if next >= count { + return count + } else { + return next + } } func _insertSlot(forKey k: KeyPart) -> Int? { @@ -109,9 +112,10 @@ extension Node4: InternalNode { return count } - func child(at: Index) -> RawNode? { - assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") + assert(index < count, "not enough childs in node") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { diff --git a/Sources/ARTreeModule/ARTree/Node48.swift b/Sources/ARTreeModule/ARTree/Node48.swift index ee1c6ebb4..7c9ce0785 100644 --- a/Sources/ARTreeModule/ARTree/Node48.swift +++ b/Sources/ARTreeModule/ARTree/Node48.swift @@ -101,27 +101,34 @@ extension Node48: InternalNode { * MemoryLayout.stride } + var startIndex: Index { + if count == 0 { + return endIndex + } else { + return index(after: -1) + } + } + + var endIndex: Index { 256 } + func index(forKey k: KeyPart) -> Index? { let childIndex = Int(keys[Int(k)]) return childIndex == 0xFF ? nil : Int(k) } - func index() -> Index? { - return next(index: -1) - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { for idx: Int in index + 1..<256 { if keys[idx] != 0xFF { - return Int(idx) + return idx } } - return nil + return 256 } - func child(at: Index) -> RawNode? { - let slot = Int(keys[at]) + func child(at index: Index) -> RawNode? { + assert(index < 256, "invalid index") + let slot = Int(keys[index]) assert(slot != 0xFF, "no child at given slot") return childs[slot] } @@ -148,6 +155,7 @@ extension Node48: InternalNode { } public mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 256, "invalid index") let targetSlot = Int(keys[index]) assert(targetSlot != 0xFF, "slot is empty already") // 1. Find out who has the last slot. @@ -192,6 +200,7 @@ extension Node48: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { + assert(index < 256, "invalid index") assert(keys[index] != 0xFF, "child doesn't exist in given slot") let ref = childs.baseAddress! + Int(keys[index]) return body(ref) diff --git a/Sources/ARTreeModule/ARTree/RawNode.swift b/Sources/ARTreeModule/ARTree/RawNode.swift index 6a7d4e62f..def3a453c 100644 --- a/Sources/ARTreeModule/ARTree/RawNode.swift +++ b/Sources/ARTreeModule/ARTree/RawNode.swift @@ -30,6 +30,13 @@ extension RawNode { } } +extension RawNode: Equatable { + @usableFromInline + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.buf === rhs.buf + } +} + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { func clone(spec: Spec.Type) -> RawNode { diff --git a/Tests/ARTreeModuleTests/RadixTree.swift b/Tests/ARTreeModuleTests/RadixTreeTests.swift similarity index 80% rename from Tests/ARTreeModuleTests/RadixTree.swift rename to Tests/ARTreeModuleTests/RadixTreeTests.swift index 9f75c4ba4..25cdb0dcd 100644 --- a/Tests/ARTreeModuleTests/RadixTree.swift +++ b/Tests/ARTreeModuleTests/RadixTreeTests.swift @@ -54,4 +54,26 @@ final class RadixTreeCollectionTests: CollectionTestCase { expectEqual(count, 3) } + + func testEmptyIteration() throws { + var d: RadixTree = [:] + var count = 0 + for (k, v) in d { + count += 1 + } + expectEqual(count, 0) + + d["a"] = 0 + for _ in d { + count += 1 + } + expectEqual(count, 1) + + d["a"] = nil + count = 0 + for _ in d { + count += 1 + } + expectEqual(count, 0) + } }