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/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 new file mode 100644 index 000000000..39e3ee436 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift @@ -0,0 +1,82 @@ +//===----------------------------------------------------------------------===// +// +// 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: Sequence { + public typealias Iterator = _Iterator + + public struct _Iterator { + typealias _ChildIndex = InternalNode.Index + + private let tree: ARTreeImpl + private var path: [(any InternalNode, _ChildIndex)] + + init(tree: ARTreeImpl) { + self.tree = tree + self.path = [] + guard let node = tree._root else { return } + + assert(node.type != .leaf, "root can't be leaf") + let n: any InternalNode = node.toInternalNode() + if n.count > 0 { + self.path = [(n, n.startIndex)] + } + } + } + + public func makeIterator() -> Iterator { + return Iterator(tree: self) + } +} + +// 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? + + // 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.index(after: index))) + } + + mutating func next() -> Element? { + while !path.isEmpty { + while let (node, index) = path.last { + if index == node.endIndex { + advanceToSibling() + break + } + + let next = node.child(at: index)! + if next.type == .leaf { + let leaf: NodeLeaf = next.toLeafNode() + let result = (leaf.key, leaf.value) + advanceToNextChild() + return result + } + + let nextNode: any InternalNode = next.toInternalNode() + path.append((nextNode, nextNode.startIndex)) + } + } + + return nil + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree+delete.swift b/Sources/ARTreeModule/ARTree/ARTree+delete.swift new file mode 100644 index 000000000..e153bc489 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+delete.swift @@ -0,0 +1,64 @@ +//===----------------------------------------------------------------------===// +// +// 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 mutating func delete(key: Key) { + 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): + _root = newValue + } + } + + public mutating func deleteRange(start: Key, end: Key) { + // TODO + fatalError("not implemented") + } + + 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) { + return .noop + } + + return .replaceWith(nil) + } + + assert(!Const.testCheckUnique || isUniquePath, "unique path is expected in this test") + var node: any InternalNode = child!.toInternalNode() + var newDepth = depth + + if node.partialLength > 0 { + let matchedBytes = node.prefixMismatch(withKey: key, fromIndex: depth) + assert(matchedBytes <= node.partialLength) + newDepth += matchedBytes + } + + 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/ARTree/ARTree+get.swift b/Sources/ARTreeModule/ARTree/ARTree+get.swift new file mode 100644 index 000000000..aff9ca38c --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+get.swift @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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 func getValue(key: Key) -> Value? { + var current = _root + var depth = 0 + while depth <= key.count { + guard let _rawNode = current else { + return nil + } + + if _rawNode.type == .leaf { + let leaf: NodeLeaf = _rawNode.toLeafNode() + return leaf.keyEquals(with: key) + ? leaf.value + : nil + } + + 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") + 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/ARTree+insert.swift b/Sources/ARTreeModule/ARTree/ARTree+insert.swift new file mode 100644 index 000000000..c07fec137 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+insert.swift @@ -0,0 +1,176 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + 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 { + guard case (let action, var ref)? = _findInsertNode(key: key) else { return false } + + switch action { + case .replace(let leaf): + leaf.withValue { + $0.pointee = value + } + + case .splitLeaf(let leaf, let depth): + let newLeaf = Self.allocateLeaf(key: key, value: value) + 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: 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 + + if longestPrefix <= 0 { + break + } + + 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. + + 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) + + 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 + + case .insertInto(var node, let depth): + 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)? { + 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) + var ref = NodeReference(&_root) + + while current.type != .leaf && depth < key.count { + assert( + !Const.testCheckUnique || isUnique, + "unique path is expected in this test, depth=\(depth)") + + if !isUnique { + // 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.rawNode.toInternalNode() + 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. + return (.splitNode(node, depth: depth, prefixDiff: prefixDiff), ref) + } + } + + // Find next child to continue. + guard + let (next, _isUnique) = + (node.maybeReadChild(forKey: key[depth], ref: &ref) { ($0, $1) }) + else { + return (.insertInto(node, depth: depth), ref) + } + + 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) { + if isUnique { + return (.replace(leaf), ref) + } else { + let clone = leaf.clone() + ref.pointee = clone.node.rawNode + return (.replace(clone.node), 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) + } + } + + 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.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 new file mode 100644 index 000000000..230bca624 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// 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)_. + +/// Alias for `ARTreeImpl` using the default specification. +typealias ARTree = ARTreeImpl> + +/// Implements a persistent Adaptive Radix Tree (ART). +internal struct ARTreeImpl { + public typealias Spec = Spec + public typealias Value = Spec.Value + + @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 new file mode 100644 index 000000000..016967f45 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ArtNode.swift @@ -0,0 +1,48 @@ +//===----------------------------------------------------------------------===// +// +// 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 } +} + +extension ArtNode { + func equals(_ other: any ArtNode) -> Bool { + return self.rawNode == other.rawNode + } +} diff --git a/Sources/ARTreeModule/ARTree/Consts and Specs.swift b/Sources/ARTreeModule/ARTree/Consts and Specs.swift new file mode 100644 index 000000000..33c7a81db --- /dev/null +++ b/Sources/ARTreeModule/ARTree/Consts and Specs.swift @@ -0,0 +1,25 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +internal struct Const { + static let maxPartialLength = 8 + static var testCheckUnique = false + static var testPrintRc = false + static var testPrintAddr = false +} + +public protocol ARTreeSpec { + associatedtype Value +} + +public struct DefaultSpec<_Value>: ARTreeSpec { + public typealias Value = _Value +} diff --git a/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift b/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift new file mode 100644 index 000000000..d9bfbae6e --- /dev/null +++ b/Sources/ARTreeModule/ARTree/FixedArray+Storage.swift @@ -0,0 +1,133 @@ +//===----------------------------------------------------------------------===// +// +// 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 FixedArrayStorage { + associatedtype Element + + static var capacity: Int { get } + init(repeating: Element) +} + +struct FixedArrayStorage4: FixedArrayStorage { + 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 FixedArrayStorage8: FixedArrayStorage { + 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 FixedArrayStorage16: FixedArrayStorage { + 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 FixedArrayStorage48: FixedArrayStorage { + 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 FixedArrayStorage256: FixedArrayStorage { + 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/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.. 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/ARTree/FixedArray.swift b/Sources/ARTreeModule/ARTree/FixedArray.swift new file mode 100644 index 000000000..10c40ab76 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/FixedArray.swift @@ -0,0 +1,42 @@ +//===----------------------------------------------------------------------===// +// +// 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 } + } +} diff --git a/Sources/ARTreeModule/ARTree/InternalNode.swift b/Sources/ARTreeModule/ARTree/InternalNode.swift new file mode 100644 index 000000000..71cc357b1 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/InternalNode.swift @@ -0,0 +1,245 @@ +//===----------------------------------------------------------------------===// +// +// 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 partialLength: Int { get } + var partialBytes: PartialBytes { get set } + + 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: KeyPart) -> RawNode? // TODO: Remove + func child(at: Index) -> RawNode? // TODO: Remove + + mutating func addChild(forKey: KeyPart, node: RawNode) -> UpdateResult + mutating func addChild(forKey: KeyPart, node: some ArtNode) -> UpdateResult + + mutating func removeChild(at: Index) -> UpdateResult + + mutating func withChildRef(at: 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, + isUniquePath: Bool, + body: (inout RawNode?, Bool) -> UpdateResult + ) + -> UpdateResult + { + + guard let childPosition = index(forKey: k) else { + return .noop + } + + 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 { + 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.removeChild(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.keys[0] + node.partialLength += 1 + } + return .replaceWith(newValue) + + case .replaceWith(nil): + fatalError("unexpected state: removeChild 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/ARTree/Node+StringConvertible.swift b/Sources/ARTreeModule/ARTree/Node+StringConvertible.swift new file mode 100644 index 000000000..e8c079187 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/Node+StringConvertible.swift @@ -0,0 +1,201 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if !COLLECTIONS_SINGLE_MODULE + import _CollectionsUtilities +#endif + +protocol NodePrettyPrinter { + func print() -> String + func prettyPrint(depth: Int) -> String +} + +extension NodePrettyPrinter { + func print() -> String { + return "○ " + prettyPrint(depth: 0) + } +} + +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 + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl: CustomStringConvertible { + public var description: String { + if let node = _root { + return "○ " + node.prettyPrint(depth: 0, with: Spec.self) + } else { + return "<>" + } + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ArtNode { + 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 print(with: Spec.Type) -> String { + return "○ " + prettyPrint(depth: 0, with: Spec.self) + } + + func prettyPrint(depth: Int, with: Spec.Type) -> String { + 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] { + var arr = [UInt8](repeating: 0, count: partialLength) + let bytes = partialBytes + for idx in 0.. String { + 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 { + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node4\(addr){childs=\(count), partial=\(partial)}\n" + + for idx in 0.. String { + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node16\(addr){childs=\(count), partial=\(partial)}\n" + + for idx in 0.. String { + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node48\(addr){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(key))!.prettyPrint(depth: depth + 1, with: Spec.self) + if !last { + output += "\n" + } + } + + return output + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension Node256: NodePrettyPrinter { + func prettyPrint(depth: Int) -> String { + let addr = Const.testPrintAddr ? " \(_addressString(for: self.rawNode.buf))" : " " + var output = "Node256\(addr){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!.prettyPrint(depth: depth + 1, with: Spec.self) + if !last { + output += "\n" + } + } + + return output + } +} diff --git a/Sources/ARTreeModule/ARTree/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift new file mode 100644 index 000000000..5da85eea7 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/Node16.swift @@ -0,0 +1,212 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + var storage: Storage +} + +extension Node16 { + static var type: NodeType { .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 + UnsafeMutableRawPointer(node.keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(node.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } + + return storage + } + + static func allocate(copyFrom: Node4) -> NodeStorage { + let storage = Self.allocate() + + 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 + } + + static func allocate(copyFrom: Node48) -> NodeStorage { + let storage = NodeStorage.allocate() + + 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: InternalNode { + static var size: Int { + MemoryLayout.stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout.stride) + } + + var startIndex: Index { 0 } + var endIndex: Index { count } + + func index(forKey k: KeyPart) -> Index? { + for (index, key) in keys[.. Index { + let next = index + 1 + if next >= count { + return count + } else { + return next + } + } + + 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(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 { + 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 + return .noop + } else { + return Node48.allocate(copyFrom: self).update { newNode in + _ = newNode.addChild(forKey: k, node: node) + return .replaceWith(newNode.rawNode) + } + } + } + + 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") + + 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 == 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") + let ref = childs.baseAddress! + index + return body(ref) + } +} + +extension Node16: ArtNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node16(buffer: self) + for idx in 0..<16 { + node.childs[idx] = nil + } + node.count = 0 + } + } + + func clone() -> NodeStorage { + let storage = Self.allocate() + + storage.update { newNode in + newNode.copyHeader(from: self) + for idx in 0.. { + var storage: Storage +} + +extension Node256 { + static var type: NodeType { .node256 } + static var numKeys: Int { 256 } +} + +extension Node256 { + var childs: UnsafeMutableBufferPointer { + storage.withBodyPointer { + UnsafeMutableBufferPointer( + start: $0.assumingMemoryBound(to: RawNode?.self), + count: 256) + } + } +} + +extension Node256 { + static func allocate() -> NodeStorage { + let storage = NodeStorage.allocate() + + _ = storage.update { newNode in + UnsafeMutableRawPointer(newNode.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } + + return storage + } + + static func allocate(copyFrom: Node48) -> NodeStorage { + let storage = Self.allocate() + + 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] + } + } + + Self.retainChildren(newNode.childs, count: Self.numKeys) + assert(newNode.count == 48, "should have exactly 48 childs") + } + + return storage + } +} + +extension Node256: InternalNode { + static var size: Int { + MemoryLayout.stride + 256 * 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? { + return childs[Int(k)] != nil ? Int(k) : nil + } + + func index(after idx: Index) -> Index { + for idx in idx + 1..<256 { + if childs[idx] != nil { + return idx + } + } + + return 256 + } + + 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 { + assert(childs[Int(k)] == nil, "node for key \(k) already exists") + childs[Int(k)] = node + count += 1 + return .noop + } + + mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 256, "invalid index") + childs[index] = nil + count -= 1 + + 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 < 256, "invalid index") + let ref = childs.baseAddress! + index + return body(ref) + } +} + +extension Node256: ArtNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node256(buffer: self) + for idx in 0..<256 { + node.childs[idx] = nil + } + node.count = 0 + } + } + + func clone() -> NodeStorage { + let storage = Self.allocate() + + storage.update { newNode in + newNode.copyHeader(from: self) + for idx in 0..<256 { + newNode.childs[idx] = childs[idx] + } + } + + return storage + } +} diff --git a/Sources/ARTreeModule/ARTree/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift new file mode 100644 index 000000000..273d15bfa --- /dev/null +++ b/Sources/ARTreeModule/ARTree/Node4.swift @@ -0,0 +1,188 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + var storage: Storage +} + +extension Node4 { + static var type: NodeType { .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 + UnsafeMutableRawPointer(node.keys.baseAddress!) + .bindMemory(to: UInt8.self, capacity: Self.numKeys) + UnsafeMutableRawPointer(node.childs.baseAddress!) + .bindMemory(to: RawNode?.self, capacity: Self.numKeys) + } + + return storage + } + + static func allocate(copyFrom: Node16) -> NodeStorage { + let storage = Self.allocate() + + storage.update { newNode in + newNode.copyHeader(from: copyFrom) + + UnsafeMutableRawBufferPointer(newNode.keys).copyBytes( + from: UnsafeBufferPointer(rebasing: copyFrom.keys[0...stride + Self.numKeys + * (MemoryLayout.stride + MemoryLayout.stride) + } + + var startIndex: Index { 0 } + var endIndex: Index { count } + + func index(forKey k: KeyPart) -> Index? { + for (index, key) in keys[.. Index { + let next = index + 1 + if next >= count { + return count + } else { + return next + } + } + + func _insertSlot(forKey k: KeyPart) -> Int? { + if count >= Self.numKeys { + return nil + } + + for idx in 0..= Int(k) { + return idx + } + } + + return count + } + + 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 { + 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 + return .noop + } else { + var newNode = Node16.allocate(copyFrom: self) + _ = newNode.addChild(forKey: k, node: node) + return .replaceWith(newNode.rawNode) + } + } + + mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 4, "index can't >= 4 in Node4") + assert(index < count, "not enough childs in node") + + 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]) + } + + return .noop + } + + mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { + assert(index < count, "index=\(index) less than count=\(count)") + let ref = childs.baseAddress! + index + return body(ref) + } +} + +extension Node4: ArtNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node4(buffer: self) + for idx in 0..<4 { + node.childs[idx] = nil + } + node.count = 0 + } + } + + func clone() -> NodeStorage { + let storage = Self.allocate() + + storage.update { newNode in + newNode.copyHeader(from: self) + for idx in 0.. { + var storage: Storage +} + +extension Node48 { + static var type: NodeType { .node48 } + static var numKeys: Int { 48 } +} + +extension Node48 { + var keys: UnsafeMutableBufferPointer { + 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 { 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 { + newNode.keys[idx] = 0xFF + } + } + + return storage + } + + static func allocate(copyFrom: Node16) -> NodeStorage { + let storage = Self.allocate() + + 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 + } + + static func allocate(copyFrom: Node256) -> NodeStorage { + let storage = Self.allocate() + + 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: InternalNode { + static var size: Int { + MemoryLayout.stride + 256 * MemoryLayout.stride + Self.numKeys + * 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(after index: Index) -> Index { + for idx: Int in index + 1..<256 { + if keys[idx] != 0xFF { + return idx + } + } + + return 256 + } + + 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] + } + + mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { + if count < Self.numKeys { + assert(keys[Int(k)] == 0xFF, "node for key \(k) already exists") + + 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 { + return Node256.allocate(copyFrom: self).update { newNode in + _ = newNode.addChild(forKey: k, node: node) + return .replaceWith(newNode.rawNode) + } + } + } + + 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. + 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 { + let newNode = Node16.allocate(copyFrom: self) + return .replaceWith(newNode.node.rawNode) + } + + return .noop + } + + private func findFreeSlot() -> Int? { + for (index, child) in childs.enumerated() { + if child == nil { + return index + } + } + + return nil + } + + 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) + } +} + +extension Node48: ArtNode { + final class Buffer: RawNodeBuffer { + deinit { + var node = Node48(buffer: self) + for idx in 0..<48 { + node.childs[idx] = nil + } + node.count = 0 + } + } + + func clone() -> NodeStorage { + let storage = Self.allocate() + + 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)] + } + } + } + + return storage + } +} diff --git a/Sources/ARTreeModule/ARTree/NodeHeader.swift b/Sources/ARTreeModule/ARTree/NodeHeader.swift new file mode 100644 index 000000000..24727fcfd --- /dev/null +++ b/Sources/ARTreeModule/ARTree/NodeHeader.swift @@ -0,0 +1,18 @@ +//===----------------------------------------------------------------------===// +// +// 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 = FixedArray8 + +struct InternalNodeHeader { + var count: UInt16 = 0 + var partialLength: UInt8 = 0 + var partialBytes: PartialBytes = PartialBytes(repeating: 0) +} diff --git a/Sources/ARTreeModule/ARTree/NodeLeaf.swift b/Sources/ARTreeModule/ARTree/NodeLeaf.swift new file mode 100644 index 000000000..9d6a1e1d3 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/NodeLeaf.swift @@ -0,0 +1,145 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + typealias Value = Spec.Value + var storage: Storage +} + +extension NodeLeaf { + static var type: NodeType { .leaf } +} + +extension NodeLeaf { + static func allocate(key: Key, value: Value) -> NodeStorage { + let size = MemoryLayout.stride + key.count + MemoryLayout.stride + let storage = NodeStorage.create(type: .leaf, size: size) + + storage.update { leaf in + leaf.keyLength = key.count + leaf.withKeyValue { keyPtr, valuePtr in + key.withUnsafeBytes { + UnsafeMutableRawBufferPointer(keyPtr).copyBytes(from: $0) + } + valuePtr.pointee = value + } + } + + return storage + } +} + +extension NodeLeaf { + typealias KeyPtr = UnsafeMutableBufferPointer + + 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) + } + } + + func withValue(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, UnsafeMutablePointer) throws -> R) rethrows -> R { + return try storage.withUnsafePointer { + let base = $0.advanced(by: MemoryLayout.stride) + let keyPtr = UnsafeMutableBufferPointer( + start: base.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 key: Key { + withKey { k in Array(k) } + } + + var keyLength: Int { + get { + storage.withUnsafePointer { + Int($0.assumingMemoryBound(to: UInt32.self).pointee) + } + } + set { + storage.withUnsafePointer { + $0.assumingMemoryBound(to: UInt32.self).pointee = UInt32(newValue) + } + } + } + + var value: Value { + withValue { $0.pointee } + } +} + +extension NodeLeaf { + func keyEquals(with key: Key, depth: Int = 0) -> Bool { + if key.count != keyLength { + return false + } + + return withKey { keyPtr in + for ii in depth.. Int { + let maxComp = Int(min(keyLength, other.keyLength) - fromIndex) + + return withKey { keyPtr in + return other.withKey { otherKeyPtr in + for index in 0.. NodeStorage { + return Self.allocate(key: key, value: value) + } +} diff --git a/Sources/ARTreeModule/ARTree/NodeStorage.swift b/Sources/ARTreeModule/ARTree/NodeStorage.swift new file mode 100644 index 000000000..5d58d15f0 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/NodeStorage.swift @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +struct NodeStorage { + var ref: Mn.Buffer + + var node: Mn { Mn(buffer: ref) } + var rawNode: RawNode { RawNode(buf: ref) } + + init(raw: RawNodeBuffer) { + self.ref = unsafeDowncast(raw, to: Mn.Buffer.self) + } +} + +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 NodeStorage(raw: buf) + } + + 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 = Self.create(type: Mn.type, size: size) + _ = buf.ref.withUnsafeMutablePointerToElements { + UnsafeMutableRawPointer($0).bindMemory(to: Header.self, capacity: 1) + } + return buf + } + + var type: NodeType { + read { $0.type } + } + var partialBytes: PartialBytes { + get { + read { $0.partialBytes } + } + set { + update { $0.partialBytes = newValue } + } + } + + 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 removeChild(at index: Index) -> UpdateResult { + update { + $0.removeChild(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/ARTree/NodeType.swift b/Sources/ARTreeModule/ARTree/NodeType.swift new file mode 100644 index 000000000..c3c8367fb --- /dev/null +++ b/Sources/ARTreeModule/ARTree/NodeType.swift @@ -0,0 +1,18 @@ +//===----------------------------------------------------------------------===// +// +// 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 + case node16 + case node48 + case node256 +} diff --git a/Sources/ARTreeModule/ARTree/RawNode.swift b/Sources/ARTreeModule/ARTree/RawNode.swift new file mode 100644 index 000000000..def3a453c --- /dev/null +++ b/Sources/ARTreeModule/ARTree/RawNode.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@usableFromInline +struct RawNode { + var buf: RawNodeBuffer + + init(buf: RawNodeBuffer) { + self.buf = buf + } +} + +extension RawNode { + typealias SlotRef = UnsafeMutablePointer + + var type: NodeType { + @inline(__always) get { return buf.header } + } + + var isUnique: Bool { + mutating get { isKnownUniquelyReferenced(&buf) } + } +} + +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 { + 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: buf) + case .node16: + return Node16(buffer: buf) + case .node48: + return Node48(buffer: buf) + case .node256: + return Node256(buffer: buf) + default: + assert(false, "leaf nodes are not internal nodes") + } + } + + func toInternalNode(of: Spec.Type) -> any InternalNode { + let n: any InternalNode = toInternalNode() + return n + } + + func toLeafNode() -> NodeLeaf { + assert(type == .leaf) + return NodeLeaf(buffer: buf) + } + + func toArtNode() -> any ArtNode { + switch type { + case .leaf: + return toLeafNode() + default: + return toInternalNode() + } + } +} diff --git a/Sources/ARTreeModule/ARTree/UnmanagedNodeStorage.swift b/Sources/ARTreeModule/ARTree/UnmanagedNodeStorage.swift new file mode 100644 index 000000000..f10bb3160 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/UnmanagedNodeStorage.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/ARTree/Utils.swift b/Sources/ARTreeModule/ARTree/Utils.swift new file mode 100644 index 000000000..114efd23a --- /dev/null +++ b/Sources/ARTreeModule/ARTree/Utils.swift @@ -0,0 +1,29 @@ +//===----------------------------------------------------------------------===// +// +// 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) { + 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/Sources/ARTreeModule/CMakeLists.txt b/Sources/ARTreeModule/CMakeLists.txt new file mode 100644 index 000000000..01a74b8ea --- /dev/null +++ b/Sources/ARTreeModule/CMakeLists.txt @@ -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 +#]] + +add_library(ARTreeModule + "ARTree/ARTree+Sequence.swift" + "ARTree/ARTree+delete.swift" + "ARTree/ARTree+get.swift" + "ARTree/ARTree+insert.swift" + "ARTree/ARTree.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+StringConverter.swift" + "ARTree/Node16.swift" + "ARTree/Node256.swift" + "ARTree/Node4.swift" + "ARTree/Node48.swift" + "ARTree/NodeHeader.swift" + "ARTree/NodeLeaf.swift" + "ARTree/NodeStorage.swif" + "ARTree/NodeType.swift" + "ARTree/RawNode.swift" + "ARTree/UnmanagedNodeStorage.swift" + "ARTree/Utils.swift", + "RadixTree.swift", + "KeyConversions.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/ARTreeModule/KeyConversions.swift b/Sources/ARTreeModule/KeyConversions.swift new file mode 100644 index 000000000..7c5467df5 --- /dev/null +++ b/Sources/ARTreeModule/KeyConversions.swift @@ -0,0 +1,174 @@ +//===----------------------------------------------------------------------===// +// +// 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 ConvertibleToBinaryComparableBytes { + 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 withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt16: ConvertibleToBinaryComparableBytes { + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt32: ConvertibleToBinaryComparableBytes { + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +extension UInt64: ConvertibleToBinaryComparableBytes { + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try withUnsafeBytes(of: self.bigEndian) { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return Self(bigEndian: ii) + } +} + +///-- Signed Integers ------------------------------------------------------------------------// + +fileprivate func _flipSignBit(_ val: T) -> T { + return val ^ (1 << (T.bitWidth - 1)) +} + +extension Int: ConvertibleToBinaryComparableBytes { + 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 { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +extension Int32: ConvertibleToBinaryComparableBytes { + 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 { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +extension Int64: ConvertibleToBinaryComparableBytes { + 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 { + let ii = bytes.withUnsafeBytes { + $0.assumingMemoryBound(to: Self.self).baseAddress!.pointee + } + return _flipSignBit(Self(bigEndian: ii)) + } +} + +///-- String ---------------------------------------------------------------------------------// + +extension String: ConvertibleToBinaryComparableBytes { + public func withUnsafeBinaryComparableBytes( + _ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + + try self.utf8CString.withUnsafeBytes { + try body($0) + } + } + + public static func fromBinaryComparableBytes(_ bytes: [UInt8]) -> Self { + String(cString: bytes) + } +} + +///-- Bytes ----------------------------------------------------------------------------------// + +// 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 +// } +// } 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 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+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 new file mode 100644 index 000000000..83ff29985 --- /dev/null +++ b/Sources/ARTreeModule/RadixTree+Subscripts.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 +// +//===----------------------------------------------------------------------===// + +// 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 + /// + /// 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(forKey: key) + } + + set { + if let newValue = newValue { + _ = self.updateValue(newValue, forKey: key) + } else { + self.removeValue(forKey: key) + } + } + } +} diff --git a/Sources/ARTreeModule/RadixTree.swift b/Sources/ARTreeModule/RadixTree.swift new file mode 100644 index 000000000..81450a209 --- /dev/null +++ b/Sources/ARTreeModule/RadixTree.swift @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// 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 { + internal var _tree: ARTree + + /// Creates an empty tree. + /// + /// - Complexity: O(1) + 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 { + /// Returns the value associated with the key. + /// + /// - 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 { + /// 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 { + /// 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/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) 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/CopyOnWriteTests.swift b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift new file mode 100644 index 000000000..37c40206a --- /dev/null +++ b/Tests/ARTreeModuleTests/CopyOnWriteTests.swift @@ -0,0 +1,236 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#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: CollectionTestCase { + 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) + + expectEqual( + t1.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "└──○ 20: 2[20, 30] -> 20") + 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") + } + + 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]) + + expectEqual( + t1.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 2[10, 20] -> 10\n" + + "└──○ 20: 2[20, 30] -> 20") + 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") + expectEqual( + t3.description, + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 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" + expectEqual(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" + 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" + + "│ ├──○ 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" + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp_2) + + var t3 = t2 + t3.insert(key: [3, 4, 7], value: 11) + 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) + 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" + 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]) + 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]) + expectEqual(t1.description, "<>") + expectEqual(t2.description, t2_descp_2) + expectEqual(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" + expectEqual(t1.description, t1_descp) + expectEqual(t2.description, t2_descp) + } +} diff --git a/Tests/ARTreeModuleTests/DeleteTests.swift b/Tests/ARTreeModuleTests/DeleteTests.swift new file mode 100644 index 000000000..089f72650 --- /dev/null +++ b/Tests/ARTreeModuleTests/DeleteTests.swift @@ -0,0 +1,263 @@ +//===----------------------------------------------------------------------===// +// +// 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 ARTreeDeleteTests: CollectionTestCase { + 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]) + 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]) + expectEqual( + 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]) + expectEqual(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]) + 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]) + expectEqual(t.description, "○ 3[4, 5, 6] -> [2]") + t.delete(key: [4, 5, 6]) + expectEqual(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]) + expectEqual( + 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]) + expectEqual( + 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 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]) + 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]) + expectEqual( + 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]") + 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]) + expectEqual(t.description, "<>") + t.insert(key: [1, 2, 3, 4, 5, 6], value: [1]) + expectEqual(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) + t1.delete(key: [2, 3, 5, 5, 6]) + 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]) + expectEqual(t1.description, "<>") + } + + 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]) + expectEqual( + 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]) + expectEqual( + 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]) + } + expectEqual(t._root?.type, .node48) + t.delete(key: [3, 4]) + t.delete(key: [4, 5]) + expectEqual( + 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]) + expectEqual( + 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]) + } + expectEqual(t._root?.type, .node256) + for i: UInt8 in 24...40 { + if i % 2 == 0 { + t.delete(key: [i, i + 1]) + } + } + expectEqual(t._root?.type, .node48) + expectEqual( + 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..417b8060c --- /dev/null +++ b/Tests/ARTreeModuleTests/GetValueTests.swift @@ -0,0 +1,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +fileprivate func randomByteArray(minSize: Int, + maxSize: Int, + minByte: UInt8, + maxByte: UInt8) -> [UInt8] { + let size = Int.random(in: minSize...maxSize) + var result: [UInt8] = (0..() + expectEqual(t.getValue(key: [10, 20, 30]), nil) + } + + func testGetValueBasic() 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]) + 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, + numKv: Int, + minSize: Int, + maxSize: Int, + minByte: UInt8, + maxByte: UInt8, + debug: Bool = false) throws { + + 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): \(String(describing: obs)) instead of \(v)") + print(tree) + expectTrue(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 \(String(describing: obs))") + missed += 1 + } + } + + expectEqual(missed, 0) + if missed > 0 { + 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 new file mode 100644 index 000000000..d98c09b8a --- /dev/null +++ b/Tests/ARTreeModuleTests/InsertTests.swift @@ -0,0 +1,506 @@ +//===----------------------------------------------------------------------===// +// +// 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 ARTreeInsertTests: CollectionTestCase { + 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]) + t.insert(key: [11, 21, 31], value: [12, 22, 32]) + t.insert(key: [12, 22, 32], value: [13, 23, 33]) + expectEqual( + 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 testInsertDeleteInsertOnEmptyTree() throws { + var t = ARTree<[UInt8]>() + t.insert(key: [10, 20, 30], value: [1]) + t.delete(key: [10, 20, 30]) + expectEqual(t.description, "<>") + t.insert(key: [11, 21, 31], value: [2]) + 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]) + expectEqual(t._root?.type, .leaf, "root should shrink to leaf") + + t.insert(key: [10, 20, 30], value: [4]) + 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" + + "└──○ 11: 3[11, 21, 31] -> [2]") + } + + 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]) + expectEqual( + 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]) + expectEqual( + 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]) + expectEqual( + 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 { + typealias T = ARTree<[UInt8]> + var t = T() + for ii: UInt8 in 0..<40 { + t.insert(key: [ii + + 1], value: [ii + + 1]) + if ii < 4 { + expectEqual(t._root?.type, .node4) + } else if ii < 16 { + expectEqual(t._root?.type, .node16) + } else if ii < 48 { + expectEqual(t._root?.type, .node48) + } + } + + let root: any InternalNode = t._root!.toInternalNode() + expectEqual(root.count, 40) + expectEqual( + 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 { + typealias T = ARTree<[UInt8]> + var t = T() + for ii: UInt8 in 0..<70 { + t.insert(key: [ii + + 1], value: [ii + + 1]) + if ii < 4 { + expectEqual(t._root?.type, .node4) + } else if ii < 16 { + expectEqual(t._root?.type, .node16) + } else if ii < 48 { + expectEqual(t._root?.type, .node48) + } + } + + let root: any InternalNode = t._root!.toInternalNode() + expectEqual(root.count, 70) + expectEqual( + 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 _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() { + expectEqual(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() { + expectEqual(tree.getValue(key: key), value) + } + } + } + + func testInsertPrefixSharedSmall() throws { + let items: [[UInt8]] = [ + [1, 2, 3, 4, 5], + [1, 2, 3, 4, 6], + [1, 2, 3, 4, 7] + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertPrefixLongOnNodePrefixFull() throws { + 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] + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertPrefixLongMultiLayer1() throws { + 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] + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertPrefixLongMultiLayer2() throws { + 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] + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertPrefixLongMultiLayer3() throws { + 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, key) in items.enumerated() { + _ = tree.insert(key: key, value: index + 10) + } + + expectEqual( + 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") + + 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() { + expectEqual(tree.getValue(key: test), val + 10) + } + } + + func testInsertPrefixLongMultiLayer5() throws { + 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], + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertAndGetSmallSetRepeat48() throws { + 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]), + ] + + try _testCommon(items, expectedRootType: .node16, reps: 100) + } + + func testInsertAndGetSmallSetRepeat256() throws { + 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]), + ] + + try _testCommon(items, expectedRootType: .node48, reps: 100) + } + + func testInsertPrefixSmallLong() throws { + 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]), + ([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]), + ] + + try _testCommon(items, expectedRootType: .node16) + } + + func testInsertPrefixSmall2() throws { + 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]), + ([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]), + ] + + try _testCommon(items, expectedRootType: .node16) + } + + func testInsertLongSharedPrefix1() throws { + 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]), + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertLongSharedPrefix2() throws { + 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]), + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testInsertLongSharedPrefix3() throws { + 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]), + ] + + try _testCommon(items, expectedRootType: .node4) + } + + func testReplace() throws { + var t = ARTree() + let items: [[UInt8]] = [ + [11, 21, 31], + [12, 22, 32], + [10, 20, 32] + ] + + for (idx, test) in items.enumerated() { + t.insert(key: test, value: idx) + } + + for (idx, test) in items.enumerated() { + t.insert(key: test, value: idx + 10) + } + + for (idx, test) in items.enumerated() { + expectEqual(t.getValue(key: test), idx + 10) + } + } +} diff --git a/Tests/ARTreeModuleTests/IntMapTests.swift b/Tests/ARTreeModuleTests/IntMapTests.swift new file mode 100644 index 000000000..01c5ca26c --- /dev/null +++ b/Tests/ARTreeModuleTests/IntMapTests.swift @@ -0,0 +1,129 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +fileprivate func randomInts(size: Int, + unique: Bool, + min: T, + max: T) -> [T] { + + if unique { + assert(max - min + 1 >= size, "range not large enough") + var uniques = Set() + while uniques.count < size { + uniques.insert(.random(in: min...max)) + } + return Array(uniques) + } else { + 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.updateValue(v, forKey: k) + m[k] = v + } + + var total = 0 + var last = T.min + for (k, v) in t { + if debug { + print("Fetched \(k) --> \(v)") + } + + expectEqual(v, m[k]) + + if total > 1 { + expectLessThanOrEqual(last, k, "keys should be ordered") + } + last = k + + total += 1 + if total > m.count { + break + } + } + + expectEqual(total, m.count) + } + + 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) + } + + func testUnsignedIntWithDuplicatesSmallSet() throws { + try _testCommon(size: 100, + unique: false, + min: 0 as UInt, + 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: 1_000_000, + unique: false, + min: 0 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) + } +} diff --git a/Tests/ARTreeModuleTests/Node16Tests.swift b/Tests/ARTreeModuleTests/Node16Tests.swift new file mode 100644 index 000000000..c6ead4a3a --- /dev/null +++ b/Tests/ARTreeModuleTests/Node16Tests.swift @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// 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 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])) + expectEqual( + node.print(), + "○ Node16 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [0]\n" + + "└──○ 20: 1[20] -> [3]") + } + + func test4AddInMiddle() throws { + 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])) + expectEqual( + node.print(), + "○ 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 { + 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])) + expectEqual( + node.print(), + "○ Node16 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 0) + expectEqual( + node.print(), + "○ Node16 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 1) + expectEqual( + node.print(), + "○ Node16 {childs=1, partial=[]}\n" + + "└──○ 15: 1[15] -> [2]") + _ = node.removeChild(at: 0) + expectEqual(node.print(), "○ Node16 {childs=0, partial=[]}\n") + } + + func test16DeleteKey() throws { + 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])) + 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.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.removeChild(at: $0) } + expectEqual( + node.print(), + "○ Node16 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.index(forKey: 20).flatMap { node.removeChild(at: $0) } + expectEqual(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(_): expectTrue(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.removeChildReturn(at: 4) + count -= 1 + } + } + expectEqual(newNode?.type, .node16) + + do { + var count = 16 + while newNode?.type != .node4 && count > 0 { + newNode = node.removeChildReturn(at: 2) + count -= 1 + } + } + expectEqual(newNode?.type, .node4) + } +} diff --git a/Tests/ARTreeModuleTests/Node256Tests.swift b/Tests/ARTreeModuleTests/Node256Tests.swift new file mode 100644 index 000000000..e48abdd20 --- /dev/null +++ b/Tests/ARTreeModuleTests/Node256Tests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 ARTreeNode256Tests: CollectionTestCase { + typealias Leaf = NodeLeaf> + typealias N256 = Node256> + + func test256Basic() throws { + 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])) + expectEqual( + node.print(), + "○ Node256 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [0]\n" + + "└──○ 20: 1[20] -> [3]") + } + + func test48DeleteAtIndex() throws { + 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])) + expectEqual( + node.print(), + "○ Node256 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 10) + expectEqual( + node.print(), + "○ Node256 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 15) + expectEqual( + node.print(), + "○ Node256 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 20) + expectEqual(node.print(), "○ Node256 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/Node48Tests.swift b/Tests/ARTreeModuleTests/Node48Tests.swift new file mode 100644 index 000000000..80370a299 --- /dev/null +++ b/Tests/ARTreeModuleTests/Node48Tests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// 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 ARTreeNode48Tests: CollectionTestCase { + typealias Leaf = NodeLeaf> + typealias N48 = Node48> + + func test48Basic() throws { + 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])) + expectEqual( + node.print(), + "○ Node48 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "└──○ 20: 1[20] -> [2]") + } + + func test48DeleteAtIndex() throws { + 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])) + expectEqual( + node.print(), + "○ Node48 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 10) + expectEqual( + node.print(), + "○ Node48 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 15) + expectEqual( + node.print(), + "○ Node48 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 20) + expectEqual(node.print(), "○ Node48 {childs=0, partial=[]}\n") + } +} diff --git a/Tests/ARTreeModuleTests/Node4Tests.swift b/Tests/ARTreeModuleTests/Node4Tests.swift new file mode 100644 index 000000000..aac092a94 --- /dev/null +++ b/Tests/ARTreeModuleTests/Node4Tests.swift @@ -0,0 +1,207 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +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 NodeStorage where Mn: InternalNode { + mutating func addChildReturn(forKey k: KeyPart, + node: NodeStorage>) -> RawNode? { + + switch addChild(forKey: k, node: node) { + case .noop: + return self.rawNode + case .replaceWith(let newValue): + return newValue + } + } + + mutating func removeChildReturn(at idx: Index) -> RawNode? { + switch removeChild(at: idx) { + case .noop: + return self.rawNode + case .replaceWith(let newValue): + return newValue + } + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +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])) + expectEqual( + node.print(), + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> [11]\n" + + "└──○ 20: 1[20] -> [22]") + } + + func test4BasicInt() throws { + 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)) + expectEqual( + node.print(), + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 10: 1[10] -> 11\n" + + "└──○ 20: 1[20] -> 22") + } + + func test4AddInMiddle() 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: 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])) + expectEqual( + node.print(), + "○ Node4 {childs=4, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [4]\n" + + "├──○ 20: 1[20] -> [2]\n" + + "└──○ 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() + _ = 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])) + expectEqual( + node.print(), + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 10: 1[10] -> [1]\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + _ = node.removeChild(at: 0) + expectEqual( + node.print(), + "○ Node4 {childs=2, partial=[]}\n" + + "├──○ 15: 1[15] -> [2]\n" + + "└──○ 20: 1[20] -> [3]") + + let newNode = node.removeChildReturn(at: 1) + expectEqual(newNode?.type, .leaf) + } + + func test4DeleteFromFull() throws { + 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)) + expectEqual(node.type, .node4) + expectEqual( + 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") + _ = node.removeChild(at: 1) + expectEqual( + node.print(), + "○ Node4 {childs=3, partial=[]}\n" + + "├──○ 1: 1[1] -> 1\n" + + "├──○ 3: 1[3] -> 3\n" + + "└──○ 4: 1[4] -> 4") + + _ = node.removeChild(at: 1) + let newNode = node.removeChildReturn(at: 1) + expectEqual(newNode?.type, .leaf) + } + + func test4ExpandTo16ThenShrinkTo4() throws { + 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])) + expectEqual( + 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: T.Leaf.allocate(key: [5], value: [5])) + expectEqual( + newNode!.print(with: T.Spec.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]") + + do { + var node: any InternalNode = newNode!.toInternalNode() + _ = 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) + } + } + } + + func test4DeleteKey() 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: 15, node: T.Leaf.allocate(key: [15], value: [2])) + _ = node.addChild(forKey: 20, node: T.Leaf.allocate(key: [20], value: [3])) + 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.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.removeChild(at: $0) } + expectEqual( + node.print(), + "○ Node4 {childs=1, partial=[]}\n" + + "└──○ 20: 1[20] -> [3]") + } +} diff --git a/Tests/ARTreeModuleTests/NodeBasicTests.swift b/Tests/ARTreeModuleTests/NodeBasicTests.swift new file mode 100644 index 000000000..5f0b34f67 --- /dev/null +++ b/Tests/ARTreeModuleTests/NodeBasicTests.swift @@ -0,0 +1,43 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +final class ARTreeNodeBasicTests: CollectionTestCase { + func testNodeSizes() throws { + let header = MemoryLayout.stride + expectEqual(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 + + print("sizeof(RawNode?) = \(refSize)") + 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)") + + 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 new file mode 100644 index 000000000..0edfea678 --- /dev/null +++ b/Tests/ARTreeModuleTests/NodeLeafTests.swift @@ -0,0 +1,109 @@ +//===----------------------------------------------------------------------===// +// +// 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 ARTreeNodeLeafTests: CollectionTestCase { + func testLeafBasic() throws { + typealias L = NodeLeaf> + let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) + expectEqual(leaf1.print(), "○ 4[10, 20, 30, 40] -> [0]") + + let leaf2 = L.allocate(key: [10, 20, 30, 40], value: [0, 1, 2]) + expectEqual(leaf2.print(), "○ 4[10, 20, 30, 40] -> [0, 1, 2]") + + let leaf3 = L.allocate(key: [], value: []) + expectEqual(leaf3.print(), "○ 0[] -> []") + } + + func testLeafKeyEquals() throws { + typealias L = NodeLeaf> + let leaf1 = L.allocate(key: [10, 20, 30, 40], value: [0]) + 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]) + 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 + expectEqual( + leaf1.node.longestCommonPrefix( + with: other, + fromIndex: 0), + 0) + } + + L.allocate(key: [0], value: [0]).read { other in + expectEqual( + leaf1.node.longestCommonPrefix( + with:other, + fromIndex: 0), + 0) + } + L.allocate(key: [0, 1], value: [0]).read { other in + expectEqual( + leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 0) + } + L.allocate(key: [10, 1], value: [0]).read { other in + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 1) + } + L.allocate(key: [10, 20], value: [0]).read { other in + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 2) + } + L.allocate(key: [10, 20], value: [0]).read { other in + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 1), + 1) + } + L.allocate(key: [10, 20], value: [0]).read { other in + 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 + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 4) + } + L.allocate(key: [1, 2, 3, 5, 5, 6], value: [0]).read { other in + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 3) + } + L.allocate(key: [1, 2, 3, 4], value: [0]).read { other in + expectEqual(leaf1.node.longestCommonPrefix(with: other, fromIndex: 0), + 4) + } + + // // Breaks the contract, so its OK that these fail. + // // expectEqual( + // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [], value: [0]), + // // fromIndex: 0), + // // 0) + // // expectEqual( + // // leaf1.node.longestCommonPrefix(with: L.allocate(key: [10], value: [0]), + // // fromIndex: 2), + // // 0) + } +} diff --git a/Tests/ARTreeModuleTests/RadixTreeTests.swift b/Tests/ARTreeModuleTests/RadixTreeTests.swift new file mode 100644 index 000000000..25cdb0dcd --- /dev/null +++ b/Tests/ARTreeModuleTests/RadixTreeTests.swift @@ -0,0 +1,79 @@ +//===----------------------------------------------------------------------===// +// +// 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) + } + + 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) + } +} diff --git a/Tests/ARTreeModuleTests/SequenceTests.swift b/Tests/ARTreeModuleTests/SequenceTests.swift new file mode 100644 index 000000000..591b77256 --- /dev/null +++ b/Tests/ARTreeModuleTests/SequenceTests.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 +// +//===----------------------------------------------------------------------===// + +import _CollectionsTestSupport +@testable import ARTreeModule + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +final class ARTreeSequenceTests: CollectionTestCase { + func testSequenceEmpty() throws { + let t = ARTree<[UInt8]>() + var total = 0 + for (_, _) in t { + total += 1 + } + expectEqual(total, 0) + } + + 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)) + } + + expectEqual(pairs.count, newPairs.count) + for ((k1, v1), (k2, v2)) in zip(pairs, newPairs) { + expectEqual(k1, k2) + expectEqual(v1, v2) + } + } + } +} diff --git a/Tests/ARTreeModuleTests/TreeRefCountTests.swift b/Tests/ARTreeModuleTests/TreeRefCountTests.swift new file mode 100644 index 000000000..798516868 --- /dev/null +++ b/Tests/ARTreeModuleTests/TreeRefCountTests.swift @@ -0,0 +1,131 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +private class TestBox { + var d: String + + init(_ d: String) { + self.d = d + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +final class ARTreeRefCountTest: CollectionTestCase { + func testRefCountBasic() throws { + // TODO: Why is it 2? + let x = TestBox("foo") + let rc1 = _getRetainCount(x) + var t = ARTree() + t.insert(key: [10, 20, 30], value: x) + 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), rc1) + } + + 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) + + expectEqual(_getRetainCount(t!._root!.buf), 2) + var n4 = t!._root + expectEqual(_getRetainCount(n4!.buf), 3) + t = nil + expectEqual(_getRetainCount(n4!.buf), 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) + + expectEqual(_getRetainCount(t!._root!.buf), 2) + var n4 = t!._root + expectEqual(_getRetainCount(n4!.buf), 3) + t = nil + expectEqual(_getRetainCount(n4!.buf), 2) + n4 = nil + } + + func testRefCountStorage() throws { + typealias Tree = ARTree + let node = Node4.allocate() + let ref = node.ref + let count0 = _getRetainCount(ref) + + let a = node.node + let count1 = _getRetainCount(ref) + expectEqual(count1, count0) + + let b = node.node + let count2 = _getRetainCount(ref) + expectEqual(count2, count1) + + let c = node.node.rawNode + let count3 = _getRetainCount(ref) + expectEqual(count3, count2 + 1) + + _ = (a, b, c) // FIXME: to suppress warning + } + + func testRefCountReplace() throws { + typealias Tree = ARTree + var t = Tree() + var v = TestBox("val1") + expectTrue(isKnownUniquelyReferenced(&v)) + + let count0 = _getRetainCount(v) + t.insert(key: [1, 2, 3], value: v) + expectFalse(isKnownUniquelyReferenced(&v)) + expectEqual(_getRetainCount(v), count0 + 1) + + t.insert(key: [1, 2, 3], value: TestBox("val2")) + expectEqual(_getRetainCount(v), count0) + expectTrue(isKnownUniquelyReferenced(&v)) + } + + func testRefCountNode4ChildAndClone() throws { + typealias Tree = ARTree + var node = Node4.allocate() + var newNode = Node4.allocate() + expectTrue(isKnownUniquelyReferenced(&newNode.ref)) + _ = node.addChild(forKey: 10, node: newNode) + expectFalse(isKnownUniquelyReferenced(&newNode.ref)) + _ = 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.removeChild(at: 0) + expectFalse(isKnownUniquelyReferenced(&newNode.ref), + "newNode can't be unique as it is should be referenced by clone as well") + + _ = (cloneNode) // FIXME: to suppress warning. + } +}