From 224120c175de5980474702161cbfe84be541c988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Wei=C3=9F?= Date: Wed, 28 Mar 2018 17:06:05 +0100 Subject: [PATCH] bring back the deprecated NIOPriorityQueue module (#250) Motivation: We moved the priority queue implementation into the main `NIO` module for performance reasons. I initially thought we didn't expose it to external packages but we did (thanks @lukasa). Modifications: Bring back the `NIOPriorityQueue` module but deprecate it. Result: Speed & backwards compatibility :) --- Package.swift | 3 + Sources/NIO/Heap.swift | 2 +- Sources/NIO/PriorityQueue.swift | 6 +- Sources/NIOPriorityQueue/Heap.swift | 284 +++++++++++++++++++ Sources/NIOPriorityQueue/PriorityQueue.swift | 107 +++++++ 5 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 Sources/NIOPriorityQueue/Heap.swift create mode 100644 Sources/NIOPriorityQueue/PriorityQueue.swift diff --git a/Package.swift b/Package.swift index 3d9b48e9..09abb228 100644 --- a/Package.swift +++ b/Package.swift @@ -21,6 +21,7 @@ var targets: [PackageDescription.Target] = [ "CNIODarwin", "NIOConcurrencyHelpers", "CNIOAtomics", + "NIOPriorityQueue", "CNIOSHA1"]), .target(name: "NIOFoundationCompat", dependencies: ["NIO"]), .target(name: "CNIOAtomics", dependencies: []), @@ -29,6 +30,8 @@ var targets: [PackageDescription.Target] = [ .target(name: "CNIODarwin", dependencies: []), .target(name: "NIOConcurrencyHelpers", dependencies: ["CNIOAtomics"]), + .target(name: "NIOPriorityQueue", + dependencies: []), .target(name: "NIOHTTP1", dependencies: ["NIO", "NIOConcurrencyHelpers", "CNIOHTTPParser", "CNIOZlib"]), .target(name: "NIOEchoServer", diff --git a/Sources/NIO/Heap.swift b/Sources/NIO/Heap.swift index 836f0150..556941bf 100644 --- a/Sources/NIO/Heap.swift +++ b/Sources/NIO/Heap.swift @@ -18,7 +18,7 @@ import Darwin import Glibc #endif -public enum HeapType { +internal enum HeapType { case maxHeap case minHeap diff --git a/Sources/NIO/PriorityQueue.swift b/Sources/NIO/PriorityQueue.swift index 566894db..8dca0a43 100644 --- a/Sources/NIO/PriorityQueue.swift +++ b/Sources/NIO/PriorityQueue.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public struct PriorityQueue { +internal struct PriorityQueue { private var heap: Heap public init(ascending: Bool = false) { @@ -75,8 +75,8 @@ extension PriorityQueue: Sequence { } } -public extension PriorityQueue { - public var count: Int { +internal extension PriorityQueue { + var count: Int { return self.heap.count } } diff --git a/Sources/NIOPriorityQueue/Heap.swift b/Sources/NIOPriorityQueue/Heap.swift new file mode 100644 index 00000000..bd695fe4 --- /dev/null +++ b/Sources/NIOPriorityQueue/Heap.swift @@ -0,0 +1,284 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) +import Darwin +#else +import Glibc +#endif + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +public enum HeapType { + case maxHeap + case minHeap + + public func comparator(type: T.Type) -> (T, T) -> Bool { + switch self { + case .maxHeap: + return (>) + case .minHeap: + return (<) + } + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +internal struct Heap { + internal let type: HeapType + internal private(set) var storage: ContiguousArray = [] + private let comparator: (T, T) -> Bool + + internal init?(type: HeapType, storage: ContiguousArray) { + self.comparator = type.comparator(type: T.self) + self.storage = storage + self.type = type + if !self.checkHeapProperty() { + return nil + } + } + + public init(type: HeapType) { + self.comparator = type.comparator(type: T.self) + self.type = type + } + + // MARK: verbatim from CLRS's (Introduction to Algorithms) Heapsort chapter with the following changes + // - added a `compare` parameter to make it either a min or a max heap + // - renamed `largest` to `root` + // - removed `max` from the method names like `maxHeapify`, `maxHeapInsert` etc + // - made the arrays 0-based + + // named `PARENT` in CLRS + private static func parentIndex(_ i: Int) -> Int { + return (i-1) / 2 + } + + // named `LEFT` in CLRS + private static func leftIndex(_ i: Int) -> Int { + return 2*i + 1 + } + + // named `RIGHT` in CLRS + private static func rightIndex(_ i: Int) -> Int { + return 2*i + 2 + } + + // named `MAX-HEAPIFY` in CLRS + private static func heapify(storage: inout ContiguousArray, compare: (T, T) -> Bool, _ i: Int) { + let l = Heap.leftIndex(i) + let r = Heap.rightIndex(i) + + var root: Int + if l <= (storage.count - 1) && compare(storage[l], storage[i]) { + root = l + } else { + root = i + } + + if r <= (storage.count - 1) && compare(storage[r], storage[root]) { + root = r + } + + if root != i { + storage.swapAt(i, root) + heapify(storage: &storage, compare: compare, root) + } + } + + // named `MAX-HEAP-INSERT` in CRLS + private static func heapInsert(storage: inout ContiguousArray, compare: (T, T) -> Bool, key: T) { + var i = storage.count + storage.append(key) + while i > 0 && compare(storage[i], storage[parentIndex(i)]) { + storage.swapAt(i, parentIndex(i)) + i = parentIndex(i) + } + } + + // named `HEAP-INCREASE-KEY` in CRLS + private static func heapRootify(storage: inout ContiguousArray, compare: (T, T) -> Bool, index: Int, key: T) { + var index = index + if compare(storage[index], key) { + fatalError("New key must be closer to the root than current key") + } + + storage[index] = key + while index > 0 && compare(storage[index], storage[parentIndex(index)]) { + storage.swapAt(index, parentIndex(index)) + index = parentIndex(index) + } + } + + // MARK: Swift interface using the low-level methods above + public mutating func append(_ value: T) { + Heap.heapInsert(storage: &self.storage, compare: self.comparator, key: value) + } + + public mutating func removeRoot() -> T? { + return self.remove(index: 0) + } + + public mutating func remove(value: T) -> Bool { + if let idx = self.storage.index(of: value) { + _ = self.remove(index: idx) + return true + } else { + return false + } + } + + private mutating func remove(index: Int) -> T? { + guard self.storage.count > 0 else { + return nil + } + let element = self.storage[index] + if self.storage.count == 1 || self.storage[index] == self.storage[self.storage.count - 1] { + _ = self.storage.removeLast() + } else if !self.comparator(self.storage[index], self.storage[self.storage.count - 1]) { + Heap.heapRootify(storage: &self.storage, compare: self.comparator, index: index, key: self.storage[self.storage.count - 1]) + _ = self.storage.removeLast() + } else { + self.storage[index] = self.storage[self.storage.count - 1] + _ = self.storage.removeLast() + Heap.heapify(storage: &self.storage, compare: self.comparator, index) + } + return element + } + + internal func checkHeapProperty() -> Bool { + func checkHeapProperty(index: Int) -> Bool { + let li = Heap.leftIndex(index) + let ri = Heap.rightIndex(index) + if index >= self.storage.count { + return true + } else { + let me = self.storage[index] + var lCond = true + var rCond = true + if li < self.storage.count { + let l = self.storage[li] + lCond = !self.comparator(l, me) + } + if ri < self.storage.count { + let r = self.storage[ri] + rCond = !self.comparator(r, me) + } + return lCond && rCond && checkHeapProperty(index: li) && checkHeapProperty(index: ri) + } + } + return checkHeapProperty(index: 0) + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension Heap: CustomDebugStringConvertible { + public var debugDescription: String { + guard self.storage.count > 0 else { + return "" + } + let descriptions = self.storage.map { String(describing: $0) } + let maxLen: Int = descriptions.map { $0.count }.max()! + let paddedDescs = descriptions.map { (desc: String) -> String in + var desc = desc + while desc.count < maxLen { + if desc.count % 2 == 0 { + desc = " \(desc)" + } else { + desc = "\(desc) " + } + } + return desc + } + + var all = "\n" + let spacing = String(repeating: " ", count: maxLen) + func subtreeWidths(rootIndex: Int) -> (Int, Int) { + let lcIdx = Heap.leftIndex(rootIndex) + let rcIdx = Heap.rightIndex(rootIndex) + var leftSpace = 0 + var rightSpace = 0 + if lcIdx < self.storage.count { + let sws = subtreeWidths(rootIndex: lcIdx) + leftSpace += sws.0 + sws.1 + maxLen + } + if rcIdx < self.storage.count { + let sws = subtreeWidths(rootIndex: rcIdx) + rightSpace += sws.0 + sws.1 + maxLen + } + return (leftSpace, rightSpace) + } + for (index, desc) in paddedDescs.enumerated() { + let (leftWidth, rightWidth) = subtreeWidths(rootIndex: index) + all += String(repeating: " ", count: leftWidth) + all += desc + all += String(repeating: " ", count: rightWidth) + + func height(index: Int) -> Int { + return Int(log2(Double(index + 1))) + } + let myHeight = height(index: index) + let nextHeight = height(index: index + 1) + if myHeight != nextHeight { + all += "\n" + } else { + all += spacing + } + } + all += "\n" + return all + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +struct HeapIterator: IteratorProtocol { + typealias Element = T + + private var heap: Heap + + init(heap: Heap) { + self.heap = heap + } + + mutating func next() -> T? { + return self.heap.removeRoot() + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension Heap: Sequence { + typealias Element = T + + var startIndex: Int { return self.storage.startIndex } + var endIndex: Int { return self.storage.endIndex } + + var underestimatedCount: Int { + return self.storage.count + } + + func makeIterator() -> HeapIterator { + return HeapIterator(heap: self) + } + + subscript(position: Int) -> T { + return self.storage[position] + } + + func index(after i: Int) -> Int { + return i + 1 + } + + var count: Int { + return self.storage.count + } +} diff --git a/Sources/NIOPriorityQueue/PriorityQueue.swift b/Sources/NIOPriorityQueue/PriorityQueue.swift new file mode 100644 index 00000000..f9fb03f1 --- /dev/null +++ b/Sources/NIOPriorityQueue/PriorityQueue.swift @@ -0,0 +1,107 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +public struct PriorityQueue { + private var heap: Heap + + public init(ascending: Bool = false) { + self.heap = Heap(type: ascending ? .minHeap : .maxHeap) + } + + public mutating func remove(_ key: Element) { + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + _ = self.heap.remove(value: key) + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + } + + public mutating func push(_ key: Element) { + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + self.heap.append(key) + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + } + + public func peek() -> Element? { + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + return self.heap.storage.first + } + + public var isEmpty: Bool { + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + return self.heap.storage.isEmpty + } + + public mutating func pop() -> Element? { + assert(self.heap.checkHeapProperty(), "broken heap: \(self.heap.debugDescription)") + return self.heap.removeRoot() + } + + public mutating func clear() { + self.heap = Heap(type: self.heap.type) + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension PriorityQueue: Equatable { + public static func ==(lhs: PriorityQueue, rhs: PriorityQueue) -> Bool { + return lhs.count == rhs.count && lhs.elementsEqual(rhs) + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension PriorityQueue: Sequence { + public struct Iterator: IteratorProtocol { + + private var queue: PriorityQueue + fileprivate init(queue: PriorityQueue) { + self.queue = queue + } + + public mutating func next() -> Element? { + return self.queue.pop() + } + } + + public func makeIterator() -> Iterator { + return Iterator(queue: self) + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +public extension PriorityQueue { + public var count: Int { + return self.heap.count + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension PriorityQueue: CustomStringConvertible { + public var description: String { + return "PriorityQueue(count: \(self.underestimatedCount)): \(Array(self))" + } +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension PriorityQueue { + @available(*, deprecated, renamed: "Element") + public typealias T = Element + @available(*, deprecated, renamed: "PriorityQueue.Iterator") + public typealias PriorityQueueIterator = PriorityQueue.Iterator +} + +@available(*, deprecated, message: "The NIOPriorityQueue module is deprecated and will be removed in the next major release.") +extension PriorityQueue.Iterator { + @available(*, deprecated, renamed: "Element") + public typealias T = Element +}