diff --git a/Sources/ARTreeModule/ARTree/ARTree+Collection.swift b/Sources/ARTreeModule/ARTree/ARTree+Collection.swift new file mode 100644 index 000000000..94d82c366 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree+Collection.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl { + var startIndex: Index { + var idx = Index(forTree: self) + idx.descentToLeftMostChild() + return idx + } + + var endIndex: Index { + return Index(forTree: self) + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift index 355aedfce..39e3ee436 100644 --- a/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift +++ b/Sources/ARTreeModule/ARTree/ARTree+Sequence.swift @@ -17,7 +17,7 @@ extension ARTreeImpl: Sequence { typealias _ChildIndex = InternalNode.Index private let tree: ARTreeImpl - private var path: [(any InternalNode, _ChildIndex?)] + private var path: [(any InternalNode, _ChildIndex)] init(tree: ARTreeImpl) { self.tree = tree @@ -27,7 +27,7 @@ extension ARTreeImpl: Sequence { assert(node.type != .leaf, "root can't be leaf") let n: any InternalNode = node.toInternalNode() if n.count > 0 { - self.path = [(n, n.index())] + self.path = [(n, n.startIndex)] } } } @@ -53,13 +53,13 @@ extension ARTreeImpl._Iterator: IteratorProtocol { return } - path.append((node, node.next(index: index!))) + path.append((node, node.index(after: index))) } mutating func next() -> Element? { while !path.isEmpty { - while let (node, _index) = path.last { - guard let index = _index else { + while let (node, index) = path.last { + if index == node.endIndex { advanceToSibling() break } @@ -73,7 +73,7 @@ extension ARTreeImpl._Iterator: IteratorProtocol { } let nextNode: any InternalNode = next.toInternalNode() - path.append((nextNode, nextNode.index())) + path.append((nextNode, nextNode.startIndex)) } } diff --git a/Sources/ARTreeModule/ARTree/ARTree.Index.swift b/Sources/ARTreeModule/ARTree/ARTree.Index.swift new file mode 100644 index 000000000..5cef84ba3 --- /dev/null +++ b/Sources/ARTreeModule/ARTree/ARTree.Index.swift @@ -0,0 +1,100 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift Collections open source project +// +// Copyright (c) 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl { + public struct Index { + internal typealias _ChildIndex = InternalNode.Index + + internal weak var root: RawNodeBuffer? = nil + internal var current: (any ArtNode)? = nil + internal var path: [(any InternalNode, _ChildIndex)] = [] + internal let version: Int + + internal init(forTree tree: ARTreeImpl) { + self.version = tree.version + + if let root = tree._root { + assert(root.type != .leaf, "root can't be leaf") + self.root = tree._root?.buf + self.current = tree._root?.toArtNode() + } + } + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index { + internal var isOnLeaf: Bool { + if let current = self.current { + return current.type == .leaf + } + + return false + } + + internal mutating func descentToLeftMostChild() { + while !isOnLeaf { + descend { $0.startIndex } + } + } + + internal mutating func descend(_ to: (any InternalNode) + -> (any InternalNode).Index) { + assert(!isOnLeaf, "can't descent on a leaf node") + assert(current != nil, "current node can't be nil") + + let currentNode: any InternalNode = current!.rawNode.toInternalNode() + let index = to(currentNode) + self.path.append((currentNode, index)) + self.current = currentNode.child(at: index)?.toArtNode() + } + + mutating private func advanceToSibling() { + let _ = path.popLast() + advanceToNextChild() + } + + mutating private func advanceToNextChild() { + guard let (node, index) = path.popLast() else { + return + } + + path.append((node, node.index(after: index))) + } +} + + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index: Equatable { + static func == (lhs: Self, rhs: Self) -> Bool { + if case (let lhs?, let rhs?) = (lhs.current, rhs.current) { + return lhs.equals(rhs) + } + + return false + } +} + +@available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) +extension ARTreeImpl.Index: Comparable { + static func < (lhs: Self, rhs: Self) -> Bool { + for ((_, idxL), (_, idxR)) in zip(lhs.path, rhs.path) { + if idxL < idxR { + return true + } else if idxL > idxR { + return false + } + } + + return false + } +} diff --git a/Sources/ARTreeModule/ARTree/ARTree.swift b/Sources/ARTreeModule/ARTree/ARTree.swift index e7dedec8a..230bca624 100644 --- a/Sources/ARTreeModule/ARTree/ARTree.swift +++ b/Sources/ARTreeModule/ARTree/ARTree.swift @@ -28,9 +28,11 @@ internal struct ARTreeImpl { @usableFromInline internal var _root: RawNode? + internal var version: Int @inlinable public init() { self._root = nil + self.version = -1 } } diff --git a/Sources/ARTreeModule/ARTree/ArtNode.swift b/Sources/ARTreeModule/ARTree/ArtNode.swift index 8e1619efa..016967f45 100644 --- a/Sources/ARTreeModule/ARTree/ArtNode.swift +++ b/Sources/ARTreeModule/ARTree/ArtNode.swift @@ -40,3 +40,9 @@ extension ArtNode { var rawNode: RawNode { RawNode(buf: self.storage.ref.takeUnretainedValue()) } var type: NodeType { Self.type } } + +extension ArtNode { + func equals(_ other: any ArtNode) -> Bool { + return self.rawNode == other.rawNode + } +} diff --git a/Sources/ARTreeModule/ARTree/InternalNode.swift b/Sources/ARTreeModule/ARTree/InternalNode.swift index 5fd239504..71cc357b1 100644 --- a/Sources/ARTreeModule/ARTree/InternalNode.swift +++ b/Sources/ARTreeModule/ARTree/InternalNode.swift @@ -17,23 +17,25 @@ protocol InternalNode: ArtNode { static var size: Int { get } - var count: Int { get set } var partialLength: Int { get } var partialBytes: PartialBytes { get set } - func index(forKey k: KeyPart) -> Index? - func index() -> Index? - func next(index: Index) -> Index? + var count: Int { get set } + var startIndex: Index { get } + var endIndex: Index { get } + + func index(forKey: KeyPart) -> Index? + func index(after: Index) -> Index - func child(forKey k: KeyPart) -> RawNode? // TODO: Remove + func child(forKey: KeyPart) -> RawNode? // TODO: Remove func child(at: Index) -> RawNode? // TODO: Remove - mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult - mutating func addChild(forKey k: KeyPart, node: some ArtNode) -> UpdateResult + mutating func addChild(forKey: KeyPart, node: RawNode) -> UpdateResult + mutating func addChild(forKey: KeyPart, node: some ArtNode) -> UpdateResult - mutating func removeChild(at index: Index) -> UpdateResult + mutating func removeChild(at: Index) -> UpdateResult - mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R + mutating func withChildRef(at: Index, _ body: (RawNode.SlotRef) -> R) -> R } struct NodeReference { diff --git a/Sources/ARTreeModule/ARTree/Node16.swift b/Sources/ARTreeModule/ARTree/Node16.swift index 739b91ddf..5da85eea7 100644 --- a/Sources/ARTreeModule/ARTree/Node16.swift +++ b/Sources/ARTreeModule/ARTree/Node16.swift @@ -97,6 +97,9 @@ extension Node16: InternalNode { * (MemoryLayout.stride + MemoryLayout.stride) } + var startIndex: Index { 0 } + var endIndex: Index { count } + func index(forKey k: KeyPart) -> Index? { for (index, key) in keys[.. Index? { - return 0 - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { let next = index + 1 - return next < count ? next : nil + if next >= count { + return count + } else { + return next + } } func _insertSlot(forKey k: KeyPart) -> Int? { @@ -131,9 +134,10 @@ extension Node16: InternalNode { return count } - func child(at: Index) -> RawNode? { - assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") + assert(index < count, "not enough childs in node") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { diff --git a/Sources/ARTreeModule/ARTree/Node256.swift b/Sources/ARTreeModule/ARTree/Node256.swift index 4d13a9f05..73e98eb65 100644 --- a/Sources/ARTreeModule/ARTree/Node256.swift +++ b/Sources/ARTreeModule/ARTree/Node256.swift @@ -65,27 +65,33 @@ extension Node256: InternalNode { MemoryLayout.stride + 256 * MemoryLayout.stride } - func index(forKey k: KeyPart) -> Index? { - return childs[Int(k)] != nil ? Int(k) : nil + var startIndex: Index { + if count == 0 { + return endIndex + } else { + return index(after: -1) + } } - func index() -> Index? { - return next(index: -1) + var endIndex: Index { 256 } + + func index(forKey k: KeyPart) -> Index? { + return childs[Int(k)] != nil ? Int(k) : nil } - func next(index: Index) -> Index? { - for idx in index + 1..<256 { + func index(after idx: Index) -> Index { + for idx in idx + 1..<256 { if childs[idx] != nil { return idx } } - return nil + return 256 } - func child(at: Index) -> RawNode? { - assert(at < 256, "maximum 256 childs allowed") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < 256, "maximum 256 childs allowed") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { @@ -96,6 +102,7 @@ extension Node256: InternalNode { } mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 256, "invalid index") childs[index] = nil count -= 1 @@ -108,6 +115,7 @@ extension Node256: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { + assert(index < 256, "invalid index") let ref = childs.baseAddress! + index return body(ref) } diff --git a/Sources/ARTreeModule/ARTree/Node4.swift b/Sources/ARTreeModule/ARTree/Node4.swift index 3587f06b2..273d15bfa 100644 --- a/Sources/ARTreeModule/ARTree/Node4.swift +++ b/Sources/ARTreeModule/ARTree/Node4.swift @@ -76,6 +76,9 @@ extension Node4: InternalNode { * (MemoryLayout.stride + MemoryLayout.stride) } + var startIndex: Index { 0 } + var endIndex: Index { count } + func index(forKey k: KeyPart) -> Index? { for (index, key) in keys[.. Index? { - return 0 - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { let next = index + 1 - return next < count ? next : nil + if next >= count { + return count + } else { + return next + } } func _insertSlot(forKey k: KeyPart) -> Int? { @@ -109,9 +112,10 @@ extension Node4: InternalNode { return count } - func child(at: Index) -> RawNode? { - assert(at < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(at)") - return childs[at] + func child(at index: Index) -> RawNode? { + assert(index < Self.numKeys, "maximum \(Self.numKeys) childs allowed, given index = \(index)") + assert(index < count, "not enough childs in node") + return childs[index] } mutating func addChild(forKey k: KeyPart, node: RawNode) -> UpdateResult { diff --git a/Sources/ARTreeModule/ARTree/Node48.swift b/Sources/ARTreeModule/ARTree/Node48.swift index ee1c6ebb4..7c9ce0785 100644 --- a/Sources/ARTreeModule/ARTree/Node48.swift +++ b/Sources/ARTreeModule/ARTree/Node48.swift @@ -101,27 +101,34 @@ extension Node48: InternalNode { * MemoryLayout.stride } + var startIndex: Index { + if count == 0 { + return endIndex + } else { + return index(after: -1) + } + } + + var endIndex: Index { 256 } + func index(forKey k: KeyPart) -> Index? { let childIndex = Int(keys[Int(k)]) return childIndex == 0xFF ? nil : Int(k) } - func index() -> Index? { - return next(index: -1) - } - - func next(index: Index) -> Index? { + func index(after index: Index) -> Index { for idx: Int in index + 1..<256 { if keys[idx] != 0xFF { - return Int(idx) + return idx } } - return nil + return 256 } - func child(at: Index) -> RawNode? { - let slot = Int(keys[at]) + func child(at index: Index) -> RawNode? { + assert(index < 256, "invalid index") + let slot = Int(keys[index]) assert(slot != 0xFF, "no child at given slot") return childs[slot] } @@ -148,6 +155,7 @@ extension Node48: InternalNode { } public mutating func removeChild(at index: Index) -> UpdateResult { + assert(index < 256, "invalid index") let targetSlot = Int(keys[index]) assert(targetSlot != 0xFF, "slot is empty already") // 1. Find out who has the last slot. @@ -192,6 +200,7 @@ extension Node48: InternalNode { } mutating func withChildRef(at index: Index, _ body: (RawNode.SlotRef) -> R) -> R { + assert(index < 256, "invalid index") assert(keys[index] != 0xFF, "child doesn't exist in given slot") let ref = childs.baseAddress! + Int(keys[index]) return body(ref) diff --git a/Sources/ARTreeModule/ARTree/RawNode.swift b/Sources/ARTreeModule/ARTree/RawNode.swift index 6a7d4e62f..def3a453c 100644 --- a/Sources/ARTreeModule/ARTree/RawNode.swift +++ b/Sources/ARTreeModule/ARTree/RawNode.swift @@ -30,6 +30,13 @@ extension RawNode { } } +extension RawNode: Equatable { + @usableFromInline + static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.buf === rhs.buf + } +} + @available(macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4, *) extension RawNode { func clone(spec: Spec.Type) -> RawNode { diff --git a/Tests/ARTreeModuleTests/RadixTree.swift b/Tests/ARTreeModuleTests/RadixTreeTests.swift similarity index 80% rename from Tests/ARTreeModuleTests/RadixTree.swift rename to Tests/ARTreeModuleTests/RadixTreeTests.swift index 9f75c4ba4..25cdb0dcd 100644 --- a/Tests/ARTreeModuleTests/RadixTree.swift +++ b/Tests/ARTreeModuleTests/RadixTreeTests.swift @@ -54,4 +54,26 @@ final class RadixTreeCollectionTests: CollectionTestCase { expectEqual(count, 3) } + + func testEmptyIteration() throws { + var d: RadixTree = [:] + var count = 0 + for (k, v) in d { + count += 1 + } + expectEqual(count, 0) + + d["a"] = 0 + for _ in d { + count += 1 + } + expectEqual(count, 1) + + d["a"] = nil + count = 0 + for _ in d { + count += 1 + } + expectEqual(count, 0) + } }