Add Heap and RBTree (#8)

This commit is contained in:
원형식 2022-09-15 19:31:41 +09:00 committed by GitHub
parent d34fafccf1
commit 3e6345228e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1286 additions and 21 deletions

View File

@ -9,4 +9,4 @@
--patternlet inline
--stripunusedargs unnamed-only
--ifdef no-indent
--commas inline

View File

@ -15,4 +15,8 @@ opt_in_rules:
- contains_over_first_not_nil
disabled_rules:
- line_length
- file_length
# Rewrited rules
cyclomatic_complexity:
warning: 25
function_body_length: 100

View File

@ -9,11 +9,11 @@ let package = Package(
.library(
name: "Yorkie",
targets: ["Yorkie"]
),
)
],
dependencies: [
.package(url: "https://github.com/grpc/grpc-swift.git", .exact("1.9.0")),
.package(url: "https://github.com/apple/swift-protobuf.git", .exact("1.19.0")),
.package(url: "https://github.com/apple/swift-protobuf.git", .exact("1.19.0"))
],
targets: [
.target(
@ -29,6 +29,6 @@ let package = Package(
dependencies: ["Yorkie"],
path: "Tests",
exclude: ["Info.plist"]
),
)
]
)

View File

@ -14,4 +14,237 @@
* limitations under the License.
*/
// Copyright (c) 2016 Matthijs Hollemans and contributors. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import Foundation
struct Heap<T: Comparable> {
/** The array that stores the heap's nodes. */
var nodes = [T]()
/**
* Determines how to compare two nodes in the heap.
* Use '>' for a max-heap or '<' for a min-heap,
* or provide a comparing method if the heap is made
* of custom elements, for example tuples.
*/
private var orderCriteria: (T, T) -> Bool
/// Create a max-heap
init() {
self.orderCriteria = { left, right -> Bool in
left > right
}
}
/// Create a max-heap
init(array: [T]) {
self.orderCriteria = { left, right -> Bool in
left > right
}
self.configureHeap(from: array)
}
/**
* Creates an empty heap.
* The sort function determines whether this is a min-heap or max-heap.
* For comparable data types, > makes a max-heap, < makes a min-heap.
*/
init(sort: @escaping (T, T) -> Bool) {
self.orderCriteria = sort
}
/**
* Creates a heap from an array. The order of the array does not matter;
* the elements are inserted into the heap in the order determined by the
* sort function. For comparable data types, '>' makes a max-heap,
* '<' makes a min-heap.
*/
init(array: [T], sort: @escaping (T, T) -> Bool) {
self.orderCriteria = sort
self.configureHeap(from: array)
}
/**
* Configures the max-heap or min-heap from an array, in a bottom-up manner.
* Performance: This runs pretty much in O(n).
*/
private mutating func configureHeap(from array: [T]) {
self.nodes = array
for index in stride(from: self.nodes.count / 2 - 1, through: 0, by: -1) {
self.shiftDown(index)
}
}
var isEmpty: Bool {
return self.nodes.isEmpty
}
var count: Int {
return self.nodes.count
}
/**
* Returns the index of the parent of the element at index i.
* The element at index 0 is the root of the tree and has no parent.
*/
@inline(__always) internal func parentIndex(ofIndex index: Int) -> Int {
return (index - 1) / 2
}
/**
* Returns the index of the left child of the element at index i.
* Note that this index can be greater than the heap size, in which case
* there is no left child.
*/
@inline(__always) internal func leftChildIndex(ofIndex index: Int) -> Int {
return 2 * index + 1
}
/**
* Returns the index of the right child of the element at index i.
* Note that this index can be greater than the heap size, in which case
* there is no right child.
*/
@inline(__always) internal func rightChildIndex(ofIndex index: Int) -> Int {
return 2 * index + 2
}
/**
* Returns the maximum value in the heap (for a max-heap) or the minimum
* value (for a min-heap).
*/
func peek() -> T? {
return self.nodes.first
}
/**
* Adds a new value to the heap. This reorders the heap so that the max-heap
* or min-heap property still holds. Performance: O(log n).
*/
mutating func insert(_ value: T) {
self.nodes.append(value)
self.shiftUp(self.nodes.count - 1)
}
/**
* Adds a sequence of values to the heap. This reorders the heap so that
* the max-heap or min-heap property still holds. Performance: O(log n).
*/
mutating func insert<S: Sequence>(_ sequence: S) where S.Iterator.Element == T {
for value in sequence {
self.insert(value)
}
}
/**
* Allows you to change an element. This reorders the heap so that
* the max-heap or min-heap property still holds.
*/
mutating func replace(index: Int, value: T) {
guard index < self.nodes.count else { return }
self.remove(at: index)
self.insert(value)
}
/**
* Removes the root node from the heap. For a max-heap, this is the maximum
* value; for a min-heap it is the minimum value. Performance: O(log n).
*/
@discardableResult mutating func remove() -> T? {
guard !self.nodes.isEmpty else { return nil }
if self.nodes.count == 1 {
return self.nodes.removeLast()
} else {
// Use the last node to replace the first one, then fix the heap by
// shifting this new first node into its proper position.
let value = self.nodes[0]
self.nodes[0] = self.nodes.removeLast()
self.shiftDown(0)
return value
}
}
/**
* Removes an arbitrary node from the heap. Performance: O(log n).
* Note that you need to know the node's index.
*/
@discardableResult mutating func remove(at index: Int) -> T? {
guard index < self.nodes.count else { return nil }
let size = self.nodes.count - 1
if index != size {
self.nodes.swapAt(index, size)
self.shiftDown(from: index, until: size)
self.shiftUp(index)
}
return self.nodes.removeLast()
}
/**
* Takes a child node and looks at its parents; if a parent is not larger
* (max-heap) or not smaller (min-heap) than the child, we exchange them.
*/
internal mutating func shiftUp(_ index: Int) {
var childIndex = index
let child = self.nodes[childIndex]
var parentIndex = self.parentIndex(ofIndex: childIndex)
while childIndex > 0, self.orderCriteria(child, self.nodes[parentIndex]) {
self.nodes[childIndex] = self.nodes[parentIndex]
childIndex = parentIndex
parentIndex = self.parentIndex(ofIndex: childIndex)
}
self.nodes[childIndex] = child
}
/**
* Looks at a parent node and makes sure it is still larger (max-heap) or
* smaller (min-heap) than its childeren.
*/
internal mutating func shiftDown(from index: Int, until endIndex: Int) {
let leftChildIndex = self.leftChildIndex(ofIndex: index)
let rightChildIndex = leftChildIndex + 1
// Figure out which comes first if we order them by the sort function:
// the parent, the left child, or the right child. If the parent comes
// first, we're done. If not, that element is out-of-place and we make
// it "float down" the tree until the heap property is restored.
var first = index
if leftChildIndex < endIndex, self.orderCriteria(self.nodes[leftChildIndex], self.nodes[first]) {
first = leftChildIndex
}
if rightChildIndex < endIndex, self.orderCriteria(self.nodes[rightChildIndex], self.nodes[first]) {
first = rightChildIndex
}
if first == index { return }
self.nodes.swapAt(index, first)
self.shiftDown(from: first, until: endIndex)
}
internal mutating func shiftDown(_ index: Int) {
self.shiftDown(from: index, until: self.nodes.count)
}
}
// MARK: - Searching
extension Heap where T: Equatable {
/** Get the index of a node in the heap. Performance: O(n). */
func index(of node: T) -> Int? {
return self.nodes.firstIndex(where: { $0 == node })
}
/** Removes the first occurrence of a node from the heap. Performance: O(n). */
@discardableResult mutating func remove(node: T) -> T? {
if let index = index(of: node) {
return self.remove(at: index)
}
return nil
}
}

View File

@ -0,0 +1,825 @@
/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Copyright (c) 2016 Matthijs Hollemans and contributors. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import Foundation
private enum RBTreeColor {
case red
case black
}
private enum RotationDirection {
case left
case right
}
// MARK: - RBNode
class RBTreeNode<T: Comparable, V>: Equatable {
typealias RBNode = RBTreeNode<T, V>
fileprivate var color: RBTreeColor = .black
fileprivate var key: T?
fileprivate var value: V?
var leftChild: RBNode?
var rightChild: RBNode?
fileprivate weak var parent: RBNode?
init(key: T?, value: V?, leftChild: RBNode?, rightChild: RBNode?, parent: RBNode?) {
self.key = key
self.value = value
self.leftChild = leftChild
self.rightChild = rightChild
self.parent = parent
self.leftChild?.parent = self
self.rightChild?.parent = self
}
convenience init(key: T?, value: V?) {
self.init(key: key, value: value, leftChild: RBNode(), rightChild: RBNode(), parent: RBNode())
}
// For initialising the nullLeaf
convenience init() {
self.init(key: nil, value: nil, leftChild: nil, rightChild: nil, parent: nil)
self.color = .black
}
var isRoot: Bool {
return self.parent == nil
}
var isLeaf: Bool {
return self.rightChild == nil && self.leftChild == nil
}
var isNullLeaf: Bool {
return self.key == nil && self.isLeaf && self.color == .black
}
var isLeftChild: Bool {
return self.parent?.leftChild === self
}
var isRightChild: Bool {
return self.parent?.rightChild === self
}
var grandparent: RBNode? {
return self.parent?.parent
}
var sibling: RBNode? {
if self.isLeftChild {
return self.parent?.rightChild
} else {
return self.parent?.leftChild
}
}
var uncle: RBNode? {
return self.parent?.sibling
}
}
// MARK: - RedBlackTree
class RedBlackTree<T: Comparable, V> {
typealias RBNode = RBTreeNode<T, V>
fileprivate(set) var root: RBNode
fileprivate(set) var size = 0
fileprivate let nullLeaf = RBNode()
fileprivate let allowDuplicateNode: Bool
init(_ allowDuplicateNode: Bool = false) {
self.root = self.nullLeaf
self.allowDuplicateNode = allowDuplicateNode
}
}
// MARK: - Size
extension RedBlackTree {
func count() -> Int {
return self.size
}
func isEmpty() -> Bool {
return self.size == 0
}
func allValues() -> [V] {
return self.allElements().compactMap { $0.value }
}
private func allElements() -> [RBNode] {
var nodes: [RBNode] = []
self.getAllElements(node: self.root, nodes: &nodes)
return nodes
}
private func getAllElements(node: RBTreeNode<T, V>, nodes: inout [RBNode]) {
guard !node.isNullLeaf else {
return
}
if let left = node.leftChild {
self.getAllElements(node: left, nodes: &nodes)
}
if node.key != nil {
nodes.append(node)
}
if let right = node.rightChild {
self.getAllElements(node: right, nodes: &nodes)
}
}
}
// MARK: - Equatable protocol
extension RBTreeNode {
static func == <T>(lhs: RBTreeNode<T, V>, rhs: RBTreeNode<T, V>) -> Bool {
return lhs.key == rhs.key
}
}
// MARK: - Finding a nodes successor
extension RBTreeNode {
/*
* Returns the inorder successor node of a node
* The successor is a node with the next larger key value of the current node
*/
func getSuccessor() -> RBNode? {
// If node has right child: successor min of this right tree
if let rightChild = self.rightChild {
if !rightChild.isNullLeaf {
return rightChild.minimum()
}
}
// Else go upward until node left child
var currentNode = self
var parent = currentNode.parent
while currentNode.isRightChild {
if let parent = parent {
currentNode = parent
}
parent = currentNode.parent
}
return parent
}
}
// MARK: - Searching
extension RBTreeNode {
/*
* Returns the node with the minimum key of the current subtree
*/
func minimum() -> RBNode? {
if let leftChild = leftChild {
if !leftChild.isNullLeaf {
return leftChild.minimum()
}
return self
}
return self
}
/*
* Returns the node with the maximum key of the current subtree
*/
func maximum() -> RBNode? {
if let rightChild = rightChild {
if !rightChild.isNullLeaf {
return rightChild.maximum()
}
return self
}
return self
}
}
extension RedBlackTree {
/*
* Returns the node with the given key |input| if existing
*/
func search(input: T) -> RBNode? {
var floorNode: RBNode?
return self.search(key: input, node: self.root, floorNode: &floorNode)
}
/*
* Returns the node with given |key| in subtree of |node|
*/
private func search(key: T, node: RBNode?, floorNode: inout RBNode?) -> RBNode? {
// If node nil -> key not found
guard let node = node else {
return nil
}
// If node is nullLeaf == semantically same as if nil
if !node.isNullLeaf {
if let nodeKey = node.key {
// Node found
if key == nodeKey {
return node
} else if key < nodeKey {
return self.search(key: key, node: node.leftChild, floorNode: &floorNode)
} else {
if floorNode == nil {
floorNode = node
} else if let floorNodeKey = floorNode?.key, floorNodeKey < nodeKey {
floorNode = node
}
return self.search(key: key, node: node.rightChild, floorNode: &floorNode)
}
}
}
return nil
}
/*
* Returns the node with given |key| in subtree of |node|
*/
func floorEntry(input: T) -> T? {
var floorNode: RBNode?
var result = self.search(key: input, node: self.root, floorNode: &floorNode)
if result == nil {
result = floorNode
}
return result?.key
}
}
// MARK: - Finding maximum and minimum value
extension RedBlackTree {
/*
* Returns the minimum key value of the whole tree
*/
func minValue() -> V? {
guard let minNode = root.minimum() else {
return nil
}
return minNode.value
}
/*
* Returns the maximum key value of the whole tree
*/
func maxValue() -> V? {
guard let maxNode = root.maximum() else {
return nil
}
return maxNode.value
}
}
// MARK: - Inserting new nodes
extension RedBlackTree {
/*
* Insert a node with key |key| into the tree
* 1. Perform normal insert operation as in a binary search tree
* 2. Fix red-black properties
* Runntime: O(log n)
*/
func insert(key: T, value: V) {
// If key must be unique and find the key already existed, quit
if self.search(input: key) != nil, !self.allowDuplicateNode {
return
}
if self.root.isNullLeaf {
self.root = RBNode(key: key, value: value)
} else {
self.insert(input: RBNode(key: key, value: value), node: self.root)
}
self.size += 1
}
/*
* Nearly identical insert operation as in a binary search tree
* Differences: All nil pointers are replaced by the nullLeaf, we color the inserted node red,
* after inserting we call insertFixup to maintain the red-black properties
*/
private func insert(input: RBNode, node: RBNode) {
guard let inputKey = input.key, let nodeKey = node.key else {
return
}
if inputKey < nodeKey {
guard let child = node.leftChild else {
self.addAsLeftChild(child: input, parent: node)
return
}
if child.isNullLeaf {
self.addAsLeftChild(child: input, parent: node)
} else {
self.insert(input: input, node: child)
}
} else {
guard let child = node.rightChild else {
self.addAsRightChild(child: input, parent: node)
return
}
if child.isNullLeaf {
self.addAsRightChild(child: input, parent: node)
} else {
self.insert(input: input, node: child)
}
}
}
private func addAsLeftChild(child: RBNode, parent: RBNode) {
parent.leftChild = child
child.parent = parent
child.color = .red
self.insertFixup(node: child)
}
private func addAsRightChild(child: RBNode, parent: RBNode) {
parent.rightChild = child
child.parent = parent
child.color = .red
self.insertFixup(node: child)
}
/*
* Fixes possible violations of the red-black property after insertion
* Only violation of red-black properties occurs at inserted node |z| and z.parent
* We have 3 distinct cases: case 1, 2a and 2b
* - case 1: may repeat, but only h/2 steps, where h is the height of the tree
* - case 2a -> case 2b -> red-black tree
* - case 2b -> red-black tree
*/
private func insertFixup(node zNode: RBNode) {
if !zNode.isNullLeaf {
guard let parentZ = zNode.parent else {
return
}
// If both |z| and his parent are red -> violation of red-black property -> need to fix it
if parentZ.color == .red {
guard let uncle = zNode.uncle else {
return
}
// Case 1: Uncle red -> recolor + move z
if uncle.color == .red {
parentZ.color = .black
uncle.color = .black
if let grandparentZ = parentZ.parent {
grandparentZ.color = .red
// Move z to grandparent and check again
self.insertFixup(node: grandparentZ)
}
}
// Case 2: Uncle black
else {
var zNew = zNode
// Case 2.a: z right child -> rotate
if parentZ.isLeftChild, zNode.isRightChild {
zNew = parentZ
leftRotate(node: zNew)
} else if parentZ.isRightChild, zNode.isLeftChild {
zNew = parentZ
rightRotate(node: zNew)
}
// Case 2.b: z left child -> recolor + rotate
zNew.parent?.color = .black
if let grandparentZnew = zNew.grandparent {
grandparentZnew.color = .red
if zNode.isLeftChild {
rightRotate(node: grandparentZnew)
} else {
leftRotate(node: grandparentZnew)
}
// We have a valid red-black-tree
}
}
}
}
self.root.color = .black
}
}
// MARK: - Deleting a node
extension RedBlackTree {
/*
* Delete a node with key |key| from the tree
* 1. Perform standard delete operation as in a binary search tree
* 2. Fix red-black properties
* Runntime: O(log n)
*/
func delete(key: T) {
var floorNode: RBNode?
if self.size == 1 {
self.root = self.nullLeaf
self.size -= 1
} else if let node = search(key: key, node: root, floorNode: &floorNode) {
if !node.isNullLeaf {
self.delete(node: node)
self.size -= 1
}
}
}
/*
* Nearly identical delete operation as in a binary search tree
* Differences: All nil pointers are replaced by the nullLeaf,
* after deleting we call insertFixup to maintain the red-black properties if the delted node was
* black (as if it was red -> no violation of red-black properties)
*/
private func delete(node zNode: RBNode) {
var nodeY = RBNode()
var nodeX = RBNode()
if let leftChild = zNode.leftChild, let rightChild = zNode.rightChild {
if leftChild.isNullLeaf || rightChild.isNullLeaf {
nodeY = zNode
} else {
if let successor = zNode.getSuccessor() {
nodeY = successor
}
}
}
if let leftChild = nodeY.leftChild {
if !leftChild.isNullLeaf {
nodeX = leftChild
} else if let rightChild = nodeY.rightChild {
nodeX = rightChild
}
}
nodeX.parent = nodeY.parent
if let parentY = nodeY.parent {
// Should never be the case, as parent of root = nil
if parentY.isNullLeaf {
self.root = nodeX
} else {
if nodeY.isLeftChild {
parentY.leftChild = nodeX
} else {
parentY.rightChild = nodeX
}
}
} else {
self.root = nodeX
}
if nodeY != zNode {
zNode.key = nodeY.key
zNode.value = nodeY.value
}
// If sliced out node was red -> nothing to do as red-black-property holds
// If it was black -> fix red-black-property
if nodeY.color == .black {
self.deleteFixup(node: nodeX)
}
}
/*
* Fixes possible violations of the red-black property after deletion
* We have w distinct cases: only case 2 may repeat, but only h many steps, where h is the height
* of the tree
* - case 1 -> case 2 -> red-black tree
* case 1 -> case 3 -> case 4 -> red-black tree
* case 1 -> case 4 -> red-black tree
* - case 3 -> case 4 -> red-black tree
* - case 4 -> red-black tree
*/
private func deleteFixup(node xNode: RBNode) {
var xTmp = xNode
if !xNode.isRoot, xNode.color == .black {
guard var sibling = xNode.sibling else {
return
}
// Case 1: Sibling of x is red
if sibling.color == .red {
// Recolor
sibling.color = .black
if let parentX = xNode.parent {
parentX.color = .red
// Rotation
if xNode.isLeftChild {
leftRotate(node: parentX)
} else {
rightRotate(node: parentX)
}
// Update sibling
if let sibl = xNode.sibling {
sibling = sibl
}
}
}
// Case 2: Sibling is black with two black children
if sibling.leftChild?.color == .black, sibling.rightChild?.color == .black {
// Recolor
sibling.color = .red
// Move fake black unit upwards
if let parentX = xNode.parent {
self.deleteFixup(node: parentX)
}
// We have a valid red-black-tree
} else {
// Case 3: a. Sibling black with one black child to the right
if xNode.isLeftChild, sibling.rightChild?.color == .black {
// Recolor
sibling.leftChild?.color = .black
sibling.color = .red
// Rotate
rightRotate(node: sibling)
// Update sibling of x
if let sibl = xNode.sibling {
sibling = sibl
}
}
// Still case 3: b. One black child to the left
else if xNode.isRightChild, sibling.leftChild?.color == .black {
// Recolor
sibling.rightChild?.color = .black
sibling.color = .red
// Rotate
leftRotate(node: sibling)
// Update sibling of x
if let sibl = xNode.sibling {
sibling = sibl
}
}
// Case 4: Sibling is black with red right child
// Recolor
if let parentX = xNode.parent {
sibling.color = parentX.color
parentX.color = .black
// a. x left and sibling with red right child
if xNode.isLeftChild {
sibling.rightChild?.color = .black
// Rotate
leftRotate(node: parentX)
}
// b. x right and sibling with red left child
else {
sibling.leftChild?.color = .black
// Rotate
rightRotate(node: parentX)
}
// We have a valid red-black-tree
xTmp = self.root
}
}
}
xTmp.color = .black
}
}
// MARK: - Rotation
private extension RedBlackTree {
/*
* Left rotation around node x
* Assumes that x.rightChild y is not a nullLeaf, rotates around the link from x to y,
* makes y the new root of the subtree with x as y's left child and y's left child as x's right
* child, where n = a node, [n] = a subtree
* | |
* x y
* / \ ~> / \
* [A] y x [C]
* / \ / \
* [B] [C] [A] [B]
*/
func leftRotate(node xNode: RBNode) {
self.rotate(node: xNode, direction: .left)
}
/*
* Right rotation around node y
* Assumes that y.leftChild x is not a nullLeaf, rotates around the link from y to x,
* makes x the new root of the subtree with y as x's right child and x's right child as y's left
* child, where n = a node, [n] = a subtree
* | |
* x y
* / \ <~ / \
* [A] y x [C]
* / \ / \
* [B] [C] [A] [B]
*/
func rightRotate(node xNode: RBNode) {
self.rotate(node: xNode, direction: .right)
}
/*
* Rotation around a node x
* Is a local operation preserving the binary-search-tree property that only exchanges pointers.
* Runntime: O(1)
*/
private func rotate(node xNode: RBNode, direction: RotationDirection) {
var nodeY: RBNode? = RBNode()
// Set |nodeY| and turn |nodeY|'s left/right subtree into |x|'s right/left subtree
switch direction {
case .left:
nodeY = xNode.rightChild
xNode.rightChild = nodeY?.leftChild
xNode.rightChild?.parent = xNode
case .right:
nodeY = xNode.leftChild
xNode.leftChild = nodeY?.rightChild
xNode.leftChild?.parent = xNode
}
// Link |x|'s parent to nodeY
nodeY?.parent = xNode.parent
if xNode.isRoot {
if let node = nodeY {
self.root = node
}
} else if xNode.isLeftChild {
xNode.parent?.leftChild = nodeY
} else if xNode.isRightChild {
xNode.parent?.rightChild = nodeY
}
// Put |x| on |nodeY|'s left
switch direction {
case .left:
nodeY?.leftChild = xNode
case .right:
nodeY?.rightChild = xNode
}
xNode.parent = nodeY
}
}
// MARK: - Verify
extension RedBlackTree {
/*
* Verifies that the existing tree fulfills all red-black properties
* Returns true if the tree is a valid red-black tree, false otherwise
*/
func verify() -> Bool {
if self.root.isNullLeaf {
print("The tree is empty")
return true
}
return self.property2() && self.property4() && self.property5()
}
// Property 1: Every node is either red or black -> fullfilled through setting node.color of type
// RBTreeColor
// Property 2: The root is black
private func property2() -> Bool {
if self.root.color == .red {
print("Property-Error: Root is red")
return false
}
return true
}
// Property 3: Every nullLeaf is black -> fullfilled through initialising nullLeafs with color = black
// Property 4: If a node is red, then both its children are black
private func property4() -> Bool {
return self.property4(node: self.root)
}
private func property4(node: RBNode) -> Bool {
if node.isNullLeaf {
return true
}
if let leftChild = node.leftChild, let rightChild = node.rightChild {
if node.color == .red {
if !leftChild.isNullLeaf, leftChild.color == .red {
print("Property-Error: Red node with key \(String(describing: node.key)) has red left child")
return false
}
if !rightChild.isNullLeaf, rightChild.color == .red {
print("Property-Error: Red node with key \(String(describing: node.key)) has red right child")
return false
}
}
return self.property4(node: leftChild) && self.property4(node: rightChild)
}
return false
}
// Property 5: For each node, all paths from the node to descendant leaves contain the same number
// of black nodes (same blackheight)
private func property5() -> Bool {
if self.property5(node: self.root) == -1 {
return false
} else {
return true
}
}
private func property5(node: RBNode) -> Int {
if node.isNullLeaf {
return 0
}
guard let leftChild = node.leftChild, let rightChild = node.rightChild else {
return -1
}
let left = self.property5(node: leftChild)
let right = self.property5(node: rightChild)
if left == -1 || right == -1 {
return -1
} else if left == right {
let addedHeight = node.color == .black ? 1 : 0
return left + addedHeight
} else {
print("Property-Error: Black height violated at node with key \(String(describing: node.key))")
return -1
}
}
}
// MARK: - Debugging
extension RBTreeNode: CustomDebugStringConvertible {
var debugDescription: String {
var str = ""
if self.isNullLeaf {
str = "nullLeaf"
} else {
if let key = key {
str = "key: \(key)"
} else {
str = "key: nil"
}
if let parent = parent {
str += ", parent: \(String(describing: parent.key))"
}
if let left = leftChild {
str += ", left = [" + left.debugDescription + "]"
}
if let right = rightChild {
str += ", right = [" + right.debugDescription + "]"
}
str += ", color = \(self.color)"
}
return str
}
}
extension RedBlackTree: CustomDebugStringConvertible {
var debugDescription: String {
return self.root.debugDescription
}
}
extension RBTreeNode: CustomStringConvertible {
var description: String {
var str = ""
if self.isNullLeaf {
str += "nullLeaf"
} else {
if let left = leftChild {
str += "(\(left.description)) <- "
}
if let key = key {
str += "\(key)"
} else {
str += "nil"
}
str += ", \(self.color)"
if let right = rightChild {
str += " -> (\(right.description))"
}
}
return str
}
}
extension RedBlackTree: CustomStringConvertible {
var description: String {
if self.root.isNullLeaf {
return "[]"
} else {
return self.root.description
}
}
}

104
Tests/Util/HeapTests.swift Normal file
View File

@ -0,0 +1,104 @@
/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import XCTest
@testable import Yorkie
class HeapTests: XCTestCase {
func test_can_push_and_pop() {
var target = Heap<Int>()
for value in [8, 7, 5, 6, 2, 1, 9, 4, 0, 3] {
target.insert(value)
}
for value in (0 ... 9).reversed() {
XCTAssertEqual(target.remove(), value)
}
}
func test_remove_root() {
let root = 9
var target = Heap(array: [8, 7, 5, 6, 2, 1, root, 4, 0, 3])
XCTAssertEqual(target.remove(), root)
for value in (0 ... 8).reversed() {
XCTAssertEqual(target.remove(), value)
}
}
func test_remove_parent_node() {
let parent = 5
var target = Heap(array: [8, 7, parent, 6, 2, 1, 9, 4, 0, 3])
XCTAssertEqual(target.remove(node: parent), parent)
for value in [0, 1, 2, 3, 4, 6, 7, 8, 9].reversed() {
XCTAssertEqual(target.remove(), value)
}
}
func test_remove_leaf_node() {
let leaf = 0
var target = Heap(array: [8, 7, 5, 6, 2, 1, 9, 4, leaf, 3])
XCTAssertEqual(target.remove(node: leaf), leaf)
for value in [1, 2, 3, 4, 5, 6, 7, 8, 9].reversed() {
XCTAssertEqual(target.remove(), value)
}
}
func test_empty() {
var target = Heap<Int>()
for value in [8, 7, 5, 6, 2, 1, 9, 4, 0, 3] {
target.insert(value)
}
for value in (0 ... 9).reversed() {
target.remove(node: value)
}
XCTAssertTrue(target.isEmpty)
XCTAssertEqual(target.count, 0)
}
func test_remove_root_by_node() {
var target = Heap<Int>()
for value in [8, 7, 5, 6, 2, 1, 9, 4, 0, 3] {
target.insert(value)
}
target.remove(node: 9)
XCTAssertEqual(target.remove(), 8)
}
func test_remove_root_by_index() {
var target = Heap<Int>()
for value in [8, 7, 5, 6, 2, 1, 9, 4, 0, 3] {
target.insert(value)
}
XCTAssertEqual(target.remove(at: 0), 9)
XCTAssertEqual(target.remove(), 8)
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2022 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import XCTest
@testable import Yorkie
class RedBlackTreeTests: XCTestCase {
private let sources = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[8, 5, 7, 9, 1, 3, 6, 0, 4, 2],
[7, 2, 0, 3, 1, 9, 8, 4, 6, 5],
[2, 0, 3, 5, 8, 6, 4, 1, 9, 7],
[8, 4, 7, 9, 2, 6, 0, 3, 1, 5],
[7, 1, 5, 2, 8, 6, 3, 4, 0, 9],
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
]
func test_can_put_and_remove_while_keeping_order() {
for array in self.sources {
let target = RedBlackTree<Int, Int>()
for value in array {
target.insert(key: value, value: value)
}
XCTAssertEqual(target.minValue(), 0)
XCTAssertEqual(target.maxValue(), 9)
XCTAssertEqual(target.allValues(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
target.delete(key: 8)
XCTAssertEqual(target.allValues(), [0, 1, 2, 3, 4, 5, 6, 7, 9])
target.delete(key: 2)
XCTAssertEqual(target.allValues(), [0, 1, 3, 4, 5, 6, 7, 9])
target.delete(key: 5)
XCTAssertEqual(target.allValues(), [0, 1, 3, 4, 6, 7, 9])
}
}
func test_can_query_floor_entry() {
for array in self.sources {
let target = RedBlackTree<Int, Int>()
for value in array {
target.insert(key: value, value: value)
}
XCTAssertEqual(target.floorEntry(input: 8), 8)
target.delete(key: 8)
XCTAssertEqual(target.floorEntry(input: 8), 7)
target.delete(key: 7)
XCTAssertEqual(target.floorEntry(input: 8), 6)
}
}
}

View File

@ -9,16 +9,19 @@
/* Begin PBXBuildFile section */
96DA808C28C5B7B400E2C1DA /* Yorkie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96DA808228C5B7B400E2C1DA /* Yorkie.framework */; };
96DA809128C5B7B400E2C1DA /* GRPCTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96DA809028C5B7B400E2C1DA /* GRPCTests.swift */; };
CE3EC94D28D189EF009471BC /* HeapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC94B28D1887B009471BC /* HeapTests.swift */; };
CE3EC94F28D1922E009471BC /* RedBlackTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC94E28D1922E009471BC /* RedBlackTree.swift */; };
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE3EC95028D195E0009471BC /* RedBlackTreeTests.swift */; };
CE6071E528C5D7EE00A8783E /* CONTRIBUTING.md in Resources */ = {isa = PBXBuildFile; fileRef = CE6071E428C5D7EE00A8783E /* CONTRIBUTING.md */; };
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230428C9F1BD00432DE5 /* Client.swift */; };
CE8C230728D1514900432DE5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230628D1514900432DE5 /* Logger.swift */; };
CE8C230B28D15FF200432DE5 /* ClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230928D15F5A00432DE5 /* ClientTests.swift */; };
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22EE28C9E85900432DE5 /* Change.swift */; };
CE8C22F128C9E86A00432DE5 /* Root.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F028C9E86A00432DE5 /* Root.swift */; };
CE8C22F328C9E87800432DE5 /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F228C9E87800432DE5 /* Object.swift */; };
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F428C9E88500432DE5 /* Operation.swift */; };
CE8C22F728C9E89100432DE5 /* Ticket.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F628C9E89100432DE5 /* Ticket.swift */; };
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C22F828C9E8CA00432DE5 /* Heap.swift */; };
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230428C9F1BD00432DE5 /* Client.swift */; };
CE8C230728D1514900432DE5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230628D1514900432DE5 /* Logger.swift */; };
CE8C230B28D15FF200432DE5 /* ClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE8C230928D15F5A00432DE5 /* ClientTests.swift */; };
CECCCB8428C96CD600544204 /* XCTestCase+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CECCCB8228C96C9200544204 /* XCTestCase+Extension.swift */; };
CEEB17E328C84D26004988DD /* yorkie.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB17E028C84D26004988DD /* yorkie.pb.swift */; };
CEEB17E428C84D26004988DD /* resources.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEEB17E128C84D26004988DD /* resources.pb.swift */; };
@ -43,16 +46,19 @@
96DA808B28C5B7B400E2C1DA /* YorkieTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = YorkieTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
96DA809028C5B7B400E2C1DA /* GRPCTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRPCTests.swift; sourceTree = "<group>"; };
96DA809228C5B7B400E2C1DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
CE3EC94B28D1887B009471BC /* HeapTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeapTests.swift; sourceTree = "<group>"; };
CE3EC94E28D1922E009471BC /* RedBlackTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedBlackTree.swift; sourceTree = "<group>"; };
CE3EC95028D195E0009471BC /* RedBlackTreeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedBlackTreeTests.swift; sourceTree = "<group>"; };
CE6071E428C5D7EE00A8783E /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
CE8C230428C9F1BD00432DE5 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
CE8C230628D1514900432DE5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Sources/Core/Logger.swift; sourceTree = SOURCE_ROOT; };
CE8C230928D15F5A00432DE5 /* ClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTests.swift; sourceTree = "<group>"; };
CE8C22EE28C9E85900432DE5 /* Change.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Change.swift; sourceTree = "<group>"; };
CE8C22F028C9E86A00432DE5 /* Root.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Root.swift; sourceTree = "<group>"; };
CE8C22F228C9E87800432DE5 /* Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = "<group>"; };
CE8C22F428C9E88500432DE5 /* Operation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Operation.swift; sourceTree = "<group>"; };
CE8C22F628C9E89100432DE5 /* Ticket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ticket.swift; sourceTree = "<group>"; };
CE8C22F828C9E8CA00432DE5 /* Heap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Heap.swift; sourceTree = "<group>"; };
CE8C230428C9F1BD00432DE5 /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
CE8C230628D1514900432DE5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Logger.swift; path = Sources/Core/Logger.swift; sourceTree = SOURCE_ROOT; };
CE8C230928D15F5A00432DE5 /* ClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTests.swift; sourceTree = "<group>"; };
CECCCB8228C96C9200544204 /* XCTestCase+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCTestCase+Extension.swift"; sourceTree = "<group>"; };
CEEB17DC28C84CC3004988DD /* resources.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; path = resources.proto; sourceTree = "<group>"; };
CEEB17DD28C84CC3004988DD /* yorkie.proto */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.protobuf; path = yorkie.proto; sourceTree = "<group>"; };
@ -90,6 +96,7 @@
96DA808F28C5B7B400E2C1DA /* Tests */,
CE6071E428C5D7EE00A8783E /* CONTRIBUTING.md */,
96DA808328C5B7B400E2C1DA /* Products */,
CE3EC94928D18720009471BC /* Recovered References */,
);
sourceTree = "<group>";
};
@ -117,14 +124,32 @@
96DA808F28C5B7B400E2C1DA /* Tests */ = {
isa = PBXGroup;
children = (
CE8C230828D15F5200432DE5 /* Core */,
CECCCB8128C96C8500544204 /* TestUtils */,
CE6071EB28C5ECA900A8783E /* API */,
CE8C230828D15F5200432DE5 /* Core */,
CE3EC94A28D1885F009471BC /* Util */,
CECCCB8128C96C8500544204 /* TestUtils */,
96DA809228C5B7B400E2C1DA /* Info.plist */,
);
path = Tests;
sourceTree = "<group>";
};
CE3EC94928D18720009471BC /* Recovered References */ = {
isa = PBXGroup;
children = (
CE8C230628D1514900432DE5 /* Logger.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
CE3EC94A28D1885F009471BC /* Util */ = {
isa = PBXGroup;
children = (
CE3EC94B28D1887B009471BC /* HeapTests.swift */,
CE3EC95028D195E0009471BC /* RedBlackTreeTests.swift */,
);
path = Util;
sourceTree = "<group>";
};
CE6071E828C5EC2500A8783E /* API */ = {
isa = PBXGroup;
children = (
@ -172,18 +197,11 @@
isa = PBXGroup;
children = (
CE8C22F828C9E8CA00432DE5 /* Heap.swift */,
CE3EC94E28D1922E009471BC /* RedBlackTree.swift */,
);
path = Util;
sourceTree = "<group>";
};
CE8C230828D15F5200432DE5 /* Core */ = {
isa = PBXGroup;
children = (
CE8C230928D15F5A00432DE5 /* ClientTests.swift */,
);
path = Core;
sourceTree = "<group>";
};
CE8C22E628C9E55300432DE5 /* Change */ = {
isa = PBXGroup;
children = (
@ -224,6 +242,14 @@
path = Time;
sourceTree = "<group>";
};
CE8C230828D15F5200432DE5 /* Core */ = {
isa = PBXGroup;
children = (
CE8C230928D15F5A00432DE5 /* ClientTests.swift */,
);
path = Core;
sourceTree = "<group>";
};
CECCCB8028C96BB500544204 /* V1 */ = {
isa = PBXGroup;
children = (
@ -423,6 +449,7 @@
CE8C22F928C9E8CA00432DE5 /* Heap.swift in Sources */,
CE8C22EF28C9E85900432DE5 /* Change.swift in Sources */,
CEEB17E328C84D26004988DD /* yorkie.pb.swift in Sources */,
CE3EC94F28D1922E009471BC /* RedBlackTree.swift in Sources */,
CE8C230528C9F1BD00432DE5 /* Client.swift in Sources */,
CE8C22F528C9E88500432DE5 /* Operation.swift in Sources */,
CE8C22F728C9E89100432DE5 /* Ticket.swift in Sources */,
@ -439,6 +466,8 @@
CECCCB8428C96CD600544204 /* XCTestCase+Extension.swift in Sources */,
CE8C230B28D15FF200432DE5 /* ClientTests.swift in Sources */,
96DA809128C5B7B400E2C1DA /* GRPCTests.swift in Sources */,
CE3EC94D28D189EF009471BC /* HeapTests.swift in Sources */,
CE3EC95228D195E4009471BC /* RedBlackTreeTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};