Add Heap and RBTree (#8)
This commit is contained in:
parent
d34fafccf1
commit
3e6345228e
|
@ -9,4 +9,4 @@
|
|||
--patternlet inline
|
||||
--stripunusedargs unnamed-only
|
||||
--ifdef no-indent
|
||||
|
||||
--commas inline
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue