diff --git a/SideStore/Utils/datastructures/LinkedHashMap.swift b/SideStore/Utils/datastructures/LinkedHashMap.swift new file mode 100644 index 00000000..70d1819e --- /dev/null +++ b/SideStore/Utils/datastructures/LinkedHashMap.swift @@ -0,0 +1,226 @@ +// +// LinkedHashMap.swift +// SideStore +// +// Created by Magesh K on 21/02/25. +// Copyright © 2025 SideStore. All rights reserved. +// + + +/// A generic LinkedHashMap implementation in Swift. +/// It provides constant-time lookup along with predictable (insertion) ordering. +public final class LinkedHashMap: Sequence { + + /// Internal doubly-linked list node + fileprivate final class Node { + let key: Key + var value: Value + var next: Node? + weak var prev: Node? // weak to avoid strong reference cycle + + init(key: Key, value: Value) { + self.key = key + self.value = value + } + } + + // MARK: - Storage + + /// Dictionary for fast lookup from key to node. + private var dict: [Key: Node] = [:] + + /// Head and tail of the doubly-linked list to maintain order. + private var head: Node? + private var tail: Node? + + // MARK: - Initialization + + /// Creates an empty LinkedHashMap. + public init() { } + + /// Creates a LinkedHashMap from a standard dictionary. + public init(_ dictionary: [Key: Value]) { + for (key, value) in dictionary { + _ = self.put(key: key, value: value) + } + } + + // MARK: - Public API + + /// The number of key-value pairs in the map. + public var count: Int { + return dict.count + } + + /// A Boolean value indicating whether the map is empty. + public var isEmpty: Bool { + return dict.isEmpty + } + + /// Returns the value for the given key, or `nil` if the key is not found. + public func get(key: Key) -> Value? { + return dict[key]?.value + } + + /// Inserts or updates the value for the given key. + /// - Returns: The previous value for the key if it existed; otherwise, `nil`. + @discardableResult + public func put(key: Key, value: Value) -> Value? { + if let node = dict[key] { + let oldValue = node.value + node.value = value + return oldValue + } else { + let newNode = Node(key: key, value: value) + dict[key] = newNode + appendNode(newNode) + return nil + } + } + + /// Removes the value for the given key. + /// - Returns: The removed value if it existed; otherwise, `nil`. + @discardableResult + public func remove(key: Key) -> Value? { + guard let node = dict.removeValue(forKey: key) else { return nil } + removeNode(node) + return node.value + } + + /// Removes all key-value pairs from the map. + public func clear() { + dict.removeAll() + head = nil + tail = nil + } + + /// Determines whether the map contains the given key. + public func containsKey(_ key: Key) -> Bool { + return dict[key] != nil + } + + /// Determines whether the map contains the given value. + /// Note: This method requires that Value conforms to Equatable. + public func containsValue(_ value: Value) -> Bool where Value: Equatable { + var current = head + while let node = current { + if node.value == value { + return true + } + current = node.next + } + return false + } + + /// Returns all keys in insertion order. + public var keys: [Key] { + var result = [Key]() + var current = head + while let node = current { + result.append(node.key) + current = node.next + } + return result + } + + /// Returns all values in insertion order. + public var values: [Value] { + var result = [Value]() + var current = head + while let node = current { + result.append(node.value) + current = node.next + } + return result + } + + /// Subscript for getting and setting values. + public subscript(key: Key) -> Value? { + get { + return get(key: key) + } + set { + if let newValue = newValue { + _ = put(key: key, value: newValue) + } else { + _ = remove(key: key) + } + } + } + + // MARK: - Sequence Conformance + + /// Iterator that yields key-value pairs in insertion order. + public struct Iterator: IteratorProtocol { + private var current: Node? + + fileprivate init(start: Node?) { + self.current = start + } + + public mutating func next() -> (key: Key, value: Value)? { + guard let node = current else { return nil } + current = node.next + return (node.key, node.value) + } + } + + public func makeIterator() -> Iterator { + return Iterator(start: head) + } + + // MARK: - Private Helpers + + /// Appends a new node to the end of the linked list. + private func appendNode(_ node: Node) { + if let tailNode = tail { + tailNode.next = node + node.prev = tailNode + tail = node + } else { + head = node + tail = node + } + } + + /// Removes the given node from the linked list. + private func removeNode(_ node: Node) { + let prevNode = node.prev + let nextNode = node.next + + if let prevNode = prevNode { + prevNode.next = nextNode + } else { + head = nextNode + } + + if let nextNode = nextNode { + nextNode.prev = prevNode + } else { + tail = prevNode + } + + // Disconnect node's pointers. + node.prev = nil + node.next = nil + } + + public func removeValue(forKey key: Key) -> Value? { + return remove(key: key) + } +} + +extension LinkedHashMap { + public subscript(key: Key, default defaultValue: @autoclosure () -> Value) -> Value { + get { + if let value = self[key] { + return value + } else { + return defaultValue() + } + } + set { + self[key] = newValue + } + } +} diff --git a/SideStore/Utils/datastructures/TreeMap.swift b/SideStore/Utils/datastructures/TreeMap.swift new file mode 100644 index 00000000..7afca202 --- /dev/null +++ b/SideStore/Utils/datastructures/TreeMap.swift @@ -0,0 +1,397 @@ +// +// TreeMap.swift +// SideStore +// +// Created by Magesh K on 21/02/25. +// Copyright © 2025 SideStore. All rights reserved. +// + +public class TreeMap: Sequence { + + // MARK: - Node and Color Definitions + + fileprivate enum Color { + case red + case black + } + + fileprivate class Node { + var key: Key + var value: Value + var left: Node? + var right: Node? + weak var parent: Node? + var color: Color + + init(key: Key, value: Value, color: Color = .red, parent: Node? = nil) { + self.key = key + self.value = value + self.color = color + self.parent = parent + } + } + + // MARK: - TreeMap Properties and Initializer + + private var root: Node? + public private(set) var count: Int = 0 + + public init() {} + + // MARK: - Public Dictionary-like API + + /// Subscript: Get or set value for a given key. + public subscript(key: Key) -> Value? { + get { return get(key: key) } + set { + if let newValue = newValue { + _ = insert(key: key, value: newValue) + } else { + _ = remove(key: key) + } + } + } + + /// Returns the value associated with the given key. + public func get(key: Key) -> Value? { + guard let node = getNode(forKey: key) else { return nil } + return node.value + } + + /// Inserts (or updates) the key with the given value. + /// Returns the old value if the key was already present. + @discardableResult + public func insert(key: Key, value: Value) -> Value? { + if let node = getNode(forKey: key) { + let oldValue = node.value + node.value = value + return oldValue + } + // Create new node + let newNode = Node(key: key, value: value) + var parent: Node? = nil + var current = root + while let cur = current { + parent = cur + if newNode.key < cur.key { + current = cur.left + } else { + current = cur.right + } + } + newNode.parent = parent + if parent == nil { + root = newNode + } else if newNode.key < parent!.key { + parent!.left = newNode + } else { + parent!.right = newNode + } + count += 1 + fixAfterInsertion(newNode) + return nil + } + + /// Removes the node with the given key. + /// Returns the removed value if it existed. + @discardableResult + public func remove(key: Key) -> Value? { + guard let node = getNode(forKey: key) else { return nil } + let removedValue = node.value + deleteNode(node) + count -= 1 + return removedValue + } + + /// Returns true if the map is empty. + public var isEmpty: Bool { + return count == 0 + } + + /// Returns all keys in sorted order. + public var keys: [Key] { + var result = [Key]() + for (k, _) in self { result.append(k) } + return result + } + + /// Returns all values in order of their keys. + public var values: [Value] { + var result = [Value]() + for (_, v) in self { result.append(v) } + return result + } + + /// Removes all entries. + public func removeAll() { + root = nil + count = 0 + } + + // MARK: - Internal Helper Methods + + /// Standard BST search for a node matching the key. + private func getNode(forKey key: Key) -> Node? { + var current = root + while let node = current { + if key == node.key { + return node + } else if key < node.key { + current = node.left + } else { + current = node.right + } + } + return nil + } + + /// Returns the minimum node in the subtree rooted at `node`. + private func minimum(_ node: Node) -> Node { + var current = node + while let next = current.left { + current = next + } + return current + } + + // MARK: - Rotation Methods + + private func rotateLeft(_ x: Node) { + guard let y = x.right else { return } + x.right = y.left + if let leftChild = y.left { + leftChild.parent = x + } + y.parent = x.parent + if x.parent == nil { + root = y + } else if x === x.parent?.left { + x.parent?.left = y + } else { + x.parent?.right = y + } + y.left = x + x.parent = y + } + + private func rotateRight(_ x: Node) { + guard let y = x.left else { return } + x.left = y.right + if let rightChild = y.right { + rightChild.parent = x + } + y.parent = x.parent + if x.parent == nil { + root = y + } else if x === x.parent?.right { + x.parent?.right = y + } else { + x.parent?.left = y + } + y.right = x + x.parent = y + } + + // MARK: - Insertion Fix-Up + + /// Restores red–black properties after insertion. + private func fixAfterInsertion(_ x: Node) { + var node = x + node.color = .red + while node !== root, let parent = node.parent, parent.color == .red { + if parent === parent.parent?.left { + if let uncle = parent.parent?.right, uncle.color == .red { + parent.color = .black + uncle.color = .black + parent.parent?.color = .red + if let grandparent = parent.parent { + node = grandparent + } + } else { + if node === parent.right { + node = parent + rotateLeft(node) + } + node.parent?.color = .black + node.parent?.parent?.color = .red + if let grandparent = node.parent?.parent { + rotateRight(grandparent) + } + } + } else { + if let uncle = parent.parent?.left, uncle.color == .red { + parent.color = .black + uncle.color = .black + parent.parent?.color = .red + if let grandparent = parent.parent { + node = grandparent + } + } else { + if node === parent.left { + node = parent + rotateRight(node) + } + node.parent?.color = .black + node.parent?.parent?.color = .red + if let grandparent = node.parent?.parent { + rotateLeft(grandparent) + } + } + } + } + root?.color = .black + } + + // MARK: - Deletion Helpers + + /// Replaces subtree rooted at u with subtree rooted at v. + private func transplant(_ u: Node, _ v: Node?) { + if u.parent == nil { + root = v + } else if u === u.parent?.left { + u.parent?.left = v + } else { + u.parent?.right = v + } + if let vNode = v { + vNode.parent = u.parent + } + } + + /// Deletes node z and fixes red–black properties. + private func deleteNode(_ z: Node) { + var y = z + let originalColor = y.color + var x: Node? + + if z.left == nil { + x = z.right + transplant(z, z.right) + } else if z.right == nil { + x = z.left + transplant(z, z.left) + } else { + y = minimum(z.right!) + let yOriginalColor = y.color + x = y.right + if y.parent === z { + if x != nil { x!.parent = y } + } else { + transplant(y, y.right) + y.right = z.right + y.right?.parent = y + } + transplant(z, y) + y.left = z.left + y.left?.parent = y + y.color = z.color + if yOriginalColor == .black { + fixAfterDeletion(x, parent: y.parent) + } + return + } + if originalColor == .black { + fixAfterDeletion(x, parent: z.parent) + } + } + + /// Restores red–black properties after deletion. + private func fixAfterDeletion(_ x: Node?, parent: Node?) { + var x = x + var parent = parent + while (x == nil || x!.color == .black) && (x !== root) { + if x === parent?.left { + var w = parent?.right + if w?.color == .red { + w?.color = .black + parent?.color = .red + rotateLeft(parent!) + w = parent?.right + } + if (w?.left == nil || w?.left?.color == .black) && + (w?.right == nil || w?.right?.color == .black) { + w?.color = .red + x = parent + parent = x?.parent + } else { + if w?.right == nil || w?.right?.color == .black { + w?.left?.color = .black + w?.color = .red + if let wUnwrapped = w { rotateRight(wUnwrapped) } + w = parent?.right + } + w?.color = parent?.color ?? .black + parent?.color = .black + w?.right?.color = .black + rotateLeft(parent!) + x = root + parent = nil + } + } else { + var w = parent?.left + if w?.color == .red { + w?.color = .black + parent?.color = .red + rotateRight(parent!) + w = parent?.left + } + if (w?.left == nil || w?.left?.color == .black) && + (w?.right == nil || w?.right?.color == .black) { + w?.color = .red + x = parent + parent = x?.parent + } else { + if w?.left == nil || w?.left?.color == .black { + w?.right?.color = .black + w?.color = .red + if let wUnwrapped = w { rotateLeft(wUnwrapped) } + w = parent?.left + } + w?.color = parent?.color ?? .black + parent?.color = .black + w?.left?.color = .black + rotateRight(parent!) + x = root + parent = nil + } + } + } + x?.color = .black + } + + // Convenience overload if parent is not separately tracked. + private func fixAfterDeletion(_ x: Node?) { + fixAfterDeletion(x, parent: x?.parent) + } + + // MARK: - Sequence Conformance (In-Order Traversal) + + public struct Iterator: IteratorProtocol { + private var stack: [Node] = [] + + // Marked as private because Node is a private type. + fileprivate init(root: Node?) { + var current = root + while let node = current { + stack.append(node) + current = node.left + } + } + + public mutating func next() -> (Key, Value)? { + if stack.isEmpty { return nil } + let node = stack.removeLast() + let result = (node.key, node.value) + var current = node.right + while let n = current { + stack.append(n) + current = n.left + } + return result + } + } + + public func makeIterator() -> Iterator { + return Iterator(root: root) + } +}