├── .editorconfig ├── .github └── workflows │ ├── build.yml │ └── docs.yml ├── .gitignore ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── Utils │ ├── Asynchrony │ ├── Promise.swift │ └── PromiseUtils.swift │ ├── Collections │ ├── ArrayUtils.swift │ ├── AvlTree.swift │ ├── BiDictionary.swift │ ├── BinaryHeap.swift │ ├── BitArray.swift │ ├── CircularArray.swift │ ├── ExpiringList.swift │ ├── FixedArray.swift │ ├── LazyDictionary.swift │ ├── PriorityQueue.swift │ ├── SearchTree.swift │ └── StablePriorityQueue.swift │ ├── Concurrency │ └── UncheckedSendable.swift │ ├── Extensions │ ├── Array+Extensions.swift │ ├── ChoiceOf+Extensions.swift │ ├── Collection+Extensions.swift │ ├── Dictionary+Extensions.swift │ ├── Optional+Extensions.swift │ ├── RandomAccessCollection+Extensions.swift │ ├── Result+Extensions.swift │ ├── Sequence+Extensions.swift │ ├── StringProtocol+Extensions.swift │ └── TimeInterval+Extensions.swift │ ├── Filesystem │ ├── TemporaryDirectory.swift │ └── TemporaryFile.swift │ ├── Iterators │ └── PeekableIterator.swift │ ├── Metaprogramming │ ├── ConstBoolTypes.swift │ └── ConstIntTypes.swift │ ├── Misc │ ├── AsyncRunnable.swift │ ├── CompareUtils.swift │ ├── DefaultInitializable.swift │ ├── DiskFileError.swift │ ├── KeyParameterizable.swift │ ├── Runnable.swift │ ├── Startable.swift │ ├── StringBuilder.swift │ ├── StringEnum.swift │ └── UnionStringEnum.swift │ ├── Networking │ ├── AddressUtils.swift │ └── NetworkError.swift │ ├── Numerics │ ├── Axis.swift │ ├── Complex.swift │ ├── Direction.swift │ ├── FibonacciSequence.swift │ ├── Mat2.swift │ ├── MathUtils.swift │ ├── Matrix.swift │ ├── NDArray.swift │ ├── NDArrayError.swift │ ├── NDArrayParser.swift │ ├── NDArrayParserError.swift │ ├── Rational.swift │ ├── Vec2.swift │ └── Vector.swift │ ├── Operations │ ├── Absolutable.swift │ ├── Addable.swift │ ├── AnyAsyncBijection.swift │ ├── AnyBijection.swift │ ├── AsyncBijection.swift │ ├── Bijection.swift │ ├── ComposedAsyncBijection.swift │ ├── ComposedBijection.swift │ ├── Divisible.swift │ ├── IdentityBijection.swift │ ├── IntExpressibleAlgebraicField.swift │ ├── InverseAsyncBijection.swift │ ├── InverseBijection.swift │ ├── Magnitudable.swift │ ├── Multipliable.swift │ ├── Negatable.swift │ ├── Remainderable.swift │ ├── Scaling.swift │ ├── Subtractable.swift │ ├── Translation.swift │ └── UnsignedConvertible.swift │ ├── Parsing │ ├── LegacyRegex.swift │ └── TokenIterator.swift │ ├── Processes │ ├── NodePackage.swift │ └── Shell.swift │ ├── Ranges │ ├── LowBoundedIntRange.swift │ └── RangeUtils.swift │ ├── Scheduling │ ├── RepeatingTimer.swift │ └── TimerContext.swift │ ├── Serialization │ ├── AnyCodable.swift │ ├── AnyCodingKey.swift │ ├── AutoSerializing.swift │ ├── DiskJsonSerializer.swift │ └── EncodeError.swift │ ├── Statistics │ ├── Averager.swift │ ├── CustomDiscreteDistribution.swift │ └── Distribution.swift │ ├── Synchronization │ ├── MutexLock.swift │ └── Synchronized.swift │ ├── Time │ └── Time.swift │ ├── Web │ ├── DocumentToMarkdownConverter.swift │ └── HTTPRequest.swift │ └── Wrappers │ ├── AsyncLazyExpiring.swift │ ├── Binding.swift │ ├── Box.swift │ ├── Expiring.swift │ ├── Lazy.swift │ └── PhantomWrapped.swift └── Tests └── UtilsTests ├── Collections ├── AvlTreeTests.swift ├── BiDictionaryTests.swift ├── BinaryHeapTests.swift ├── BitArrayTests.swift ├── CircularArrayTests.swift ├── CollectionUtilsTests.swift └── StablePriorityQueueTests.swift ├── Extensions ├── AsyncExtensionsTests.swift └── StringExtensionsTests.swift ├── Numerics ├── ComplexTests.swift ├── FibonacciSequenceTests.swift ├── Mat2Tests.swift ├── MathUtilsTests.swift ├── MatrixTests.swift ├── NDArrayTests.swift └── RationalTests.swift ├── Parsing └── TokenIteratorTests.swift └── Processes └── ShellTests.swift /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | swift: ['5.10', '6.0'] 15 | 16 | runs-on: ubuntu-latest 17 | container: swift:${{ matrix.swift }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Build 22 | run: swift build 23 | - name: Test 24 | run: swift test 25 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # Allow one concurrent deployment 16 | concurrency: 17 | group: "pages" 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | docs: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: Set up Pages 27 | uses: actions/configure-pages@v5 28 | - name: Set up Swift 29 | uses: swift-actions/setup-swift@v2 30 | with: 31 | swift-version: '5.10' 32 | - name: Generate Docs 33 | uses: fwcd/swift-docc-action@v1 34 | with: 35 | target: Utils 36 | output: ./public 37 | transform-for-static-hosting: 'true' 38 | disable-indexing: 'true' 39 | hosting-base-path: swift-utils 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v3 42 | with: 43 | path: ./public 44 | 45 | deploy: 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | runs-on: ubuntu-latest 50 | needs: docs 51 | 52 | steps: 53 | - name: Deploy Docs 54 | uses: actions/deploy-pages@v4 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .vscode 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 fwcd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swift-docc-plugin", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/apple/swift-docc-plugin.git", 7 | "state" : { 8 | "revision" : "26ac5758409154cc448d7ab82389c520fa8a8247", 9 | "version" : "1.3.0" 10 | } 11 | }, 12 | { 13 | "identity" : "swift-docc-symbolkit", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/apple/swift-docc-symbolkit", 16 | "state" : { 17 | "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", 18 | "version" : "1.0.0" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-log", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/apple/swift-log.git", 25 | "state" : { 26 | "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", 27 | "version" : "1.6.1" 28 | } 29 | }, 30 | { 31 | "identity" : "swiftsoup", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/scinfu/SwiftSoup.git", 34 | "state" : { 35 | "revision" : "3c2c7e1e72b8abd96eafbae80323c5c1e5317437", 36 | "version" : "2.7.5" 37 | } 38 | }, 39 | { 40 | "identity" : "xmlcoder", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/MaxDesiatov/XMLCoder.git", 43 | "state" : { 44 | "revision" : "b1e944cbd0ef33787b13f639a5418d55b3bed501", 45 | "version" : "0.17.1" 46 | } 47 | } 48 | ], 49 | "version" : 2 50 | } 51 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.10 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "swift-utils", 8 | platforms: [.macOS(.v13)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, and make them visible to other packages. 11 | .library( 12 | name: "Utils", 13 | targets: ["Utils"] 14 | ), 15 | ], 16 | dependencies: [ 17 | // Dependencies declare other packages that this package depends on. 18 | .package(url: "https://github.com/apple/swift-docc-plugin.git", from: "1.1.0"), 19 | .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"), 20 | .package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"), 21 | .package(url: "https://github.com/MaxDesiatov/XMLCoder.git", from: "0.17.1"), 22 | ], 23 | targets: [ 24 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 25 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 26 | .target( 27 | name: "Utils", 28 | dependencies: [ 29 | .product(name: "Logging", package: "swift-log"), 30 | .product(name: "SwiftSoup", package: "SwiftSoup"), 31 | .product(name: "XMLCoder", package: "XMLCoder"), 32 | ] 33 | ), 34 | .testTarget( 35 | name: "UtilsTests", 36 | dependencies: [ 37 | .target(name: "Utils"), 38 | ] 39 | ) 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift Utils 2 | 3 | [![Build](https://github.com/fwcd/swift-utils/actions/workflows/build.yml/badge.svg)](https://github.com/fwcd/swift-utils/actions/workflows/build.yml) 4 | [![Docs](https://github.com/fwcd/swift-utils/actions/workflows/docs.yml/badge.svg)](https://fwcd.github.io/swift-utils/documentation/utils) 5 | 6 | A large collection of general-purpose utilities for Swift, including: 7 | 8 | * Data structures 9 | * Promises 10 | * Priority queues (including binary heaps) 11 | * Circular arrays 12 | * Fixed-size arrays 13 | * Expiring lists 14 | * Search trees (including AVL trees) 15 | * Locks 16 | * Lazy dictionaries 17 | * Bidirectional dictionaries 18 | * Numerical utilities 19 | * N-dimensional arrays (including vectors and matrices) 20 | * Invertible operations 21 | * Fine-grained numeric protocol 22 | * Distributions 23 | * Averagers 24 | * Serialization helpers 25 | * Property wrappers for automatic JSON serialization 26 | * Collection utilities 27 | * Token iterators with lookahead and lookbehind 28 | * A wide range of extension functions 29 | * String processing 30 | * Edit distances (Levenshtein, LCS) 31 | * A wide range of extension functions 32 | * System interfacing 33 | * Subprocess/shell wrappers 34 | * Temporary file abstractions 35 | * Network packet wrappers 36 | -------------------------------------------------------------------------------- /Sources/Utils/Asynchrony/Promise.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | @preconcurrency import Logging 3 | 4 | fileprivate let log = Logger(label: "Utils.Promise") 5 | 6 | /// Represents an asynchronously computed value. 7 | /// 8 | /// Promises are executed immediately, i.e. is body runs synchronously with the 9 | /// constructor, similar to e.g. JavaScript's promises, but different from 10 | /// Python/Rust. 11 | /// 12 | /// Sendable safety: Promises are sendable because they guard their internal state with a mutex. 13 | public final class Promise: @unchecked Sendable where T: Sendable, E: Error { 14 | private let mutex = MutexLock() 15 | private var state: State 16 | private var listeners: [@Sendable (Result) -> Void] = [] 17 | private var wasListenedTo: Bool = false 18 | 19 | /// Creates a finished, succeeded promise. 20 | public convenience init(_ value: T) { 21 | self.init(.success(value)) 22 | } 23 | 24 | /// Creates a finished promise. 25 | public required init(_ value: Result) { 26 | state = .finished(value) 27 | } 28 | 29 | /// Creates a promise from the given thenable and 30 | /// synchronously begins running it. 31 | public required init(_ thenable: (@Sendable @escaping (Result) -> Void) -> Void) { 32 | state = .pending 33 | let mutex = self.mutex 34 | thenable { result in 35 | mutex.lock { 36 | self.state = .finished(result) 37 | for listener in self.listeners { 38 | listener(result) 39 | } 40 | self.listeners = [] 41 | } 42 | } 43 | } 44 | 45 | deinit { 46 | switch state { 47 | case .pending: 48 | log.warning("Deinitializing a pending promise, this is probably an error.") 49 | case .finished(.failure(let error)): 50 | if !wasListenedTo { 51 | log.error("Unhandled promise error: \(error)") 52 | } 53 | default: 54 | break 55 | } 56 | } 57 | 58 | private enum State: Sendable { 59 | case pending 60 | case finished(Result) 61 | } 62 | 63 | /// Listens for the result. Only fires once. 64 | public func listen(_ listener: @Sendable @escaping (Result) -> Void) { 65 | mutex.lock { 66 | wasListenedTo = true 67 | if case let .finished(result) = state { 68 | listener(result) 69 | } else { 70 | listeners.append(listener) 71 | } 72 | } 73 | } 74 | 75 | /// Listens for a successful result or logs an error otherwise. Only fires once. 76 | public func listenOrLogError(file: String = #file, line: Int = #line, _ listener: @Sendable @escaping (T) -> Void) { 77 | listen { 78 | switch $0 { 79 | case .success(let value): 80 | listener(value) 81 | case .failure(let error): 82 | log.error("Asynchronous error (listened for at \(file):\(line)): \(error)") 83 | } 84 | } 85 | } 86 | 87 | /// Listens for the result. Only fires once. Returns the promise. 88 | public func peekListen(_ listener: @Sendable @escaping (Result) -> Void) -> Self { 89 | listen(listener) 90 | return self 91 | } 92 | 93 | /// Chains another asynchronous computation after this one. 94 | public func then(_ next: @Sendable @escaping (T) -> Promise) -> Promise { 95 | Promise { then in 96 | listen { 97 | switch $0 { 98 | case .success(let value): 99 | next(value).listen(then) 100 | case .failure(let error): 101 | then(.failure(error)) 102 | } 103 | } 104 | } 105 | } 106 | 107 | /// Chains another synchronous computation after this one. 108 | public func map(_ transform: @Sendable @escaping (T) -> U) -> Promise { 109 | Promise { then in 110 | listen { 111 | then($0.map(transform)) 112 | } 113 | } 114 | } 115 | 116 | /// Chains another synchronous computation after this one. 117 | public func mapResult(_ transform: @Sendable @escaping (Result) -> Result) -> Promise { 118 | Promise { then in 119 | listen { 120 | then(transform($0)) 121 | } 122 | } 123 | } 124 | 125 | /// Ignores the return value of the promise. 126 | public func void() -> Promise { 127 | map { _ in } 128 | } 129 | 130 | /// Voids the result and swallows the error. 131 | public func swallow() -> Promise { 132 | Promise { then in 133 | listen { _ in 134 | then(.success(())) 135 | } 136 | } 137 | } 138 | 139 | /// Convenience method for discarding the promise in a method chain. 140 | /// Making this explicit helps preventing accidental race conditions. 141 | public func forget() {} 142 | 143 | /// Awaits the promise result synchronously by blocking the 144 | /// current thread. You should make sure to not run this method 145 | /// from the same thread that also fulfills your promise since that 146 | /// might result in a deadlock. 147 | public func wait() throws -> T { 148 | let semaphore = DispatchSemaphore(value: 0) 149 | 150 | // Safety: Since `listen` only fires once, the variable will only be 151 | // mutated by a single thread and not concurrently. 152 | nonisolated(unsafe) var result: Result! 153 | 154 | listen { 155 | result = $0 156 | semaphore.signal() 157 | } 158 | 159 | semaphore.wait() 160 | return try result.get() 161 | } 162 | 163 | /// Fetches the result asynchronously. 164 | public func get() async throws -> T { 165 | try await withCheckedThrowingContinuation { continuation in 166 | listen { 167 | continuation.resume(with: $0) 168 | } 169 | } 170 | } 171 | 172 | /// Fetches the result asynchronously and logs an error if absent. 173 | public func getOrLogError(file: String = #file, line: Int = #line) async -> T? { 174 | await withCheckedContinuation { continuation in 175 | listen { 176 | switch $0 { 177 | case .success(let value): 178 | continuation.resume(returning: value) 179 | case .failure(let error): 180 | log.error("Asynchronous error (fetched at \(file):\(line)): \(error)") 181 | continuation.resume(returning: nil) 182 | } 183 | } 184 | } 185 | } 186 | } 187 | 188 | extension Promise where E == Error { 189 | /// Creates a (finished) promise catching the given block. 190 | public static func catching(_ block: () throws -> T) -> Self { 191 | Self(Result(catching: block)) 192 | } 193 | 194 | /// Creates a promise catching the given block returning another promise. 195 | public static func catchingThen(_ block: () throws -> Promise) -> Promise { 196 | Promise, Error>.catching(block).then { $0 } 197 | } 198 | 199 | /// Creates another promise from mapping the current one with a callback that may synchronously throw. 200 | public func mapCatching(_ transform: @Sendable @escaping (T) throws -> U) -> Promise { 201 | then { x in 202 | .catching { 203 | try transform(x) 204 | } 205 | } 206 | } 207 | 208 | /// Chains another promise with a callback that may synchronously throw. 209 | public func thenCatching(_ next: @Sendable @escaping (T) throws -> Promise) -> Promise { 210 | then { x in 211 | .catchingThen { 212 | try next(x) 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Sources/Utils/Asynchrony/PromiseUtils.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /// Returns a new promise that completes once all results from the 4 | /// individual promises have returned. This means that they possibly 5 | /// execute concurrently. 6 | @discardableResult 7 | public func all(promises: [Promise]) -> Promise<[T], E> where E: Error { 8 | Promise { then in 9 | let queue = DispatchQueue(label: "all(promises:)") 10 | 11 | // Safety: All accesses to the state are via the queue, thus synchronized 12 | nonisolated(unsafe) var values = [T]() 13 | nonisolated(unsafe) var remaining = promises.count 14 | nonisolated(unsafe) var failed = false 15 | 16 | for promise in promises { 17 | promise.listen { result in 18 | queue.sync { 19 | switch result { 20 | case let .success(value): 21 | values.append(value) 22 | remaining -= 1 23 | if remaining == 0 && !failed { 24 | then(.success(values)) 25 | } 26 | case let .failure(error): 27 | if !failed { 28 | failed = true 29 | then(.failure(error)) 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | /// Sequentially executes the promises. 39 | @discardableResult 40 | public func sequence(promises: C) -> Promise<[T], E> 41 | where E: Error, 42 | C: Collection & Sendable, 43 | C.SubSequence: Sendable, 44 | C.Element == (() -> Promise) { 45 | if let promise = promises.first { 46 | return promise().then { value in sequence(promises: promises.dropFirst()).map { [value] + $0 } } 47 | } else { 48 | return Promise(.success([])) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/ArrayUtils.swift: -------------------------------------------------------------------------------- 1 | // TODO: Implement this as a generic extension over collections containing optionals 2 | // once Swift supports this. 3 | public func allNonNil(_ array: [T?]) -> [T]? where T: Equatable { 4 | array.contains(nil) ? nil : array.map { $0! } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/AvlTree.swift: -------------------------------------------------------------------------------- 1 | @preconcurrency import Logging 2 | 3 | fileprivate let log = Logger(label: "Utils.AvlTree") 4 | 5 | /** 6 | * A balanced binary search tree. 7 | */ 8 | public class AvlTree: Equatable, CustomStringConvertible, SearchTree { 9 | private var value: Element? 10 | private var left: AvlTree? = nil 11 | private var right: AvlTree? = nil 12 | private var balance: Int8 = 0 13 | private var height: Int 14 | public var description: String { 15 | return "[\(left?.description ?? "_") \(value.map { "\($0)" } ?? "_") \(right?.description ?? "_")]" 16 | } 17 | 18 | public convenience init(value: Element? = nil) { 19 | self.init(value: value, left: nil, right: nil) 20 | } 21 | 22 | // Internal constructor, the left- and 23 | // right-tree arguments are used solely 24 | // for testing. 25 | init( 26 | value: Element?, 27 | left: AvlTree?, 28 | right: AvlTree? 29 | ) { 30 | self.value = value 31 | self.left = left 32 | self.right = right 33 | 34 | height = (value == nil) ? 0 : 1 35 | } 36 | 37 | @discardableResult 38 | public func contains(_ element: Element) -> Bool { 39 | guard let value = self.value else { return false } 40 | if value == element { 41 | return true 42 | } else if element < value { 43 | return left?.contains(element) ?? false 44 | } else { // element > value 45 | return right?.contains(element) ?? false 46 | } 47 | } 48 | 49 | /** 50 | * Inserts a node into the AVL tree, 51 | * rebalancing if necessary. The return 52 | * value indicates whether the node has 53 | * been rebalanced. 54 | */ 55 | @discardableResult 56 | public func insert(_ element: Element) -> Bool { 57 | guard let value = self.value else { 58 | self.value = element 59 | return false 60 | } 61 | var rebalanced = false 62 | 63 | if element < value { 64 | rebalanced = insert(element, into: &left) 65 | } else { // element > value 66 | rebalanced = insert(element, into: &right) 67 | } 68 | 69 | if !rebalanced { 70 | updateBalanceAndHeight() 71 | rebalanced = rebalance() 72 | } 73 | 74 | return rebalanced 75 | } 76 | 77 | private func insert(_ element: Element, into child: inout AvlTree?) -> Bool { 78 | if let child = child { 79 | return child.insert(element) 80 | } else { 81 | child = AvlTree(value: element) 82 | return false 83 | } 84 | } 85 | 86 | /** 87 | * Removes a node from the AVL tree, 88 | * rebalancing if necessary. The return 89 | * value indicates whether the node has 90 | * been rebalanced. 91 | */ 92 | @discardableResult 93 | public func remove(_ element: Element) -> Bool { 94 | guard let value = self.value else { return false } 95 | var rebalanced = false 96 | 97 | if value == element { 98 | self.value = nil 99 | } else if element < value { 100 | rebalanced = remove(element, from: &left) 101 | } else if element > value { 102 | rebalanced = remove(element, from: &right) 103 | } 104 | 105 | if !rebalanced { 106 | updateBalanceAndHeight() 107 | rebalanced = rebalance() 108 | } 109 | 110 | return rebalanced 111 | } 112 | 113 | private func remove(_ element: Element, from child: inout AvlTree?) -> Bool { 114 | if let child = child { 115 | return child.remove(element) 116 | } else { 117 | child = AvlTree(value: element) 118 | return false 119 | } 120 | } 121 | 122 | private func rebalance() -> Bool { 123 | if balance > 1 { 124 | // Left-heavy 125 | if left!.balance > 0 { 126 | rotateRight() 127 | } else { 128 | log.debug("Left-balance: \(left!.balance) of \(self)") 129 | doubleRotateLeftRight() 130 | } 131 | return true 132 | } else if balance < -1 { 133 | // Right-heavy 134 | if right!.balance < 0 { 135 | rotateLeft() 136 | } else { 137 | doubleRotateRightLeft() 138 | } 139 | return true 140 | } 141 | return false 142 | } 143 | 144 | /** 145 | * Performs a simple rotation lowering this node 146 | * and lifting up the right child into the current 147 | * instance. 148 | */ 149 | private func rotateLeft() { 150 | let oldLifted = right! 151 | let newLowered = AvlTree(value: value) 152 | newLowered.left = left 153 | newLowered.right = oldLifted.left 154 | 155 | left = newLowered 156 | right = oldLifted.right 157 | value = oldLifted.value 158 | } 159 | 160 | /** 161 | * Performs a simple rotation lowering this node 162 | * and lifting up the left child into the current 163 | * instance. 164 | */ 165 | private func rotateRight() { 166 | let oldLifted = left! 167 | let newLowered = AvlTree(value: value) 168 | newLowered.left = oldLifted.right 169 | newLowered.right = right 170 | 171 | left = oldLifted.left 172 | right = newLowered 173 | value = oldLifted.value 174 | } 175 | 176 | /** 177 | * Performs a double rotation lifting 178 | * the left-right grandchild up. 179 | */ 180 | private func doubleRotateLeftRight() { 181 | left!.rotateLeft() 182 | rotateRight() 183 | } 184 | 185 | /** 186 | * Performs a double rotation lifting 187 | * the right-left grandchild up. 188 | */ 189 | private func doubleRotateRightLeft() { 190 | right!.rotateRight() 191 | rotateLeft() 192 | } 193 | 194 | private func updateBalanceAndHeight() { 195 | let leftHeight = left?.height ?? 0 196 | let rightHeight = right?.height ?? 0 197 | balance = Int8(leftHeight - rightHeight) 198 | height = max(leftHeight, rightHeight) + 1 199 | } 200 | 201 | public static func ==(lhs: AvlTree, rhs: AvlTree) -> Bool { 202 | return (lhs.value == rhs.value) 203 | && (lhs.left == rhs.left) 204 | && (lhs.right == rhs.right) 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/BiDictionary.swift: -------------------------------------------------------------------------------- 1 | /// A dictionary supporting bidirectional lookup. 2 | public struct BiDictionary: ExpressibleByDictionaryLiteral, Sequence, Hashable, Equatable, CustomStringConvertible where K: Hashable, V: Hashable { 3 | public private(set) var keysToValues: [K: V] 4 | public private(set) var valuesToKeys: [V: K] 5 | 6 | public var keys: Dictionary.Keys { keysToValues.keys } 7 | public var values: Dictionary.Keys { valuesToKeys.keys } 8 | public var count: Int { keysToValues.count } 9 | public var isEmpty: Bool { count == 0 } 10 | 11 | public var description: String { "\(keysToValues)" } 12 | 13 | public init() { 14 | keysToValues = [:] 15 | valuesToKeys = [:] 16 | } 17 | 18 | public init(dictionaryLiteral elements: (K, V)...) { 19 | self.init() 20 | 21 | for (key, value) in elements { 22 | self[key] = value 23 | } 24 | } 25 | 26 | public subscript(_ key: K) -> V? { 27 | get { keysToValues[key] } 28 | set(value) { 29 | if let previousValue = keysToValues[key] { 30 | valuesToKeys[previousValue] = nil 31 | } 32 | 33 | keysToValues[key] = value 34 | 35 | if let v = value { 36 | valuesToKeys[v] = key 37 | } 38 | } 39 | } 40 | 41 | public subscript(value value: V) -> K? { 42 | get { valuesToKeys[value] } 43 | set(key) { 44 | if let previousKey = valuesToKeys[value] { 45 | keysToValues[previousKey] = nil 46 | } 47 | 48 | valuesToKeys[value] = key 49 | 50 | if let k = key { 51 | keysToValues[k] = value 52 | } 53 | } 54 | } 55 | 56 | public func makeIterator() -> Dictionary.Iterator { 57 | keysToValues.makeIterator() 58 | } 59 | } 60 | 61 | extension BiDictionary: Sendable where K: Sendable, V: Sendable {} 62 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/BinaryHeap.swift: -------------------------------------------------------------------------------- 1 | public struct BinaryHeap: PriorityQueue where E: Comparable { 2 | public typealias Element = E 3 | 4 | // Only accessible (internally) for testing purposes 5 | private(set) var elements: [E] = [] 6 | public var count: Int { return elements.count } 7 | 8 | public init() {} 9 | 10 | public mutating func insert(_ element: E) { 11 | elements.append(element) 12 | if elements.count > 1 { 13 | heapifyUp(at: elements.count - 1) 14 | } 15 | } 16 | 17 | public mutating func popMax() -> E? { 18 | if elements.count <= 1 { 19 | return elements.popLast() 20 | } else { 21 | let removed = elements[0] 22 | elements.swapAt(0, elements.count - 1) 23 | elements.removeLast() 24 | heapifyDown(at: 0) 25 | return removed 26 | } 27 | } 28 | 29 | private mutating func heapifyUp(at index: Int) { 30 | let par = parent(of: index) 31 | if par >= 0 { 32 | if elements[par] < elements[index] { 33 | elements.swapAt(par, index) 34 | heapifyUp(at: par) 35 | } 36 | } 37 | } 38 | 39 | private mutating func heapifyDown(at index: Int) { 40 | let left = leftChild(of: index) 41 | let right = rightChild(of: index) 42 | var child: Int? = nil 43 | 44 | if left < elements.count { 45 | if elements[left] > elements[index] { 46 | child = left 47 | } 48 | } 49 | 50 | if right < elements.count { 51 | if elements[right] > elements[child ?? index] { 52 | child = right 53 | } 54 | } 55 | 56 | if let c = child { 57 | elements.swapAt(c, index) 58 | heapifyDown(at: c) 59 | } 60 | } 61 | 62 | /** Internal function to check the validity of this heap. */ 63 | func isValidHeap(at index: Int = 0) -> Bool { 64 | let left = leftChild(of: index) 65 | let right = rightChild(of: index) 66 | 67 | if left < elements.count { 68 | guard elements[left] <= elements[index] else { return false } 69 | guard isValidHeap(at: left) else { return false } 70 | } 71 | 72 | if right < elements.count { 73 | guard elements[right] <= elements[index] else { return false } 74 | guard isValidHeap(at: right) else { return false } 75 | } 76 | 77 | return true 78 | } 79 | 80 | private func leftChild(of index: Int) -> Int { return 2 * index + 1 } 81 | 82 | private func rightChild(of index: Int) -> Int { return 2 * index + 2 } 83 | 84 | private func parent(of index: Int) -> Int { return (index - 1) / 2 } 85 | 86 | private func isLeaf(_ index: Int) -> Bool { return leftChild(of: index) >= elements.count || rightChild(of: index) >= elements.count } 87 | } 88 | 89 | extension BinaryHeap: Sendable where E: Sendable {} 90 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/BitArray.swift: -------------------------------------------------------------------------------- 1 | public struct BitArray { 2 | public private(set) var bytes: [UInt8] = [] 3 | private var next = 0 4 | 5 | public init() {} 6 | 7 | public subscript(boolAt index: Int) -> Bool { 8 | get { return self[bitAt: index] == 1 } 9 | set(newValue) { self[bitAt: index] = (newValue ? 1 : 0) } 10 | } 11 | 12 | public subscript(bitAt index: Int) -> UInt8 { 13 | get { return (bytes[index / 8] >> (7 - (index % 8))) & 1 } 14 | set(newValue) { 15 | let byteIndex = index / 8 16 | let bitIndex = index % 8 17 | while bytes.count <= byteIndex { 18 | bytes.append(0) 19 | } 20 | 21 | let previousByte: UInt8 = bytes[byteIndex] 22 | let newBit: UInt8 = newValue & 1 23 | let newByte: UInt8 = previousByte | (newBit << (7 - bitIndex)) 24 | bytes[index / 8] = newByte 25 | } 26 | } 27 | 28 | public mutating func append(bool: Bool) { 29 | append(bit: bool ? 1 : 0) 30 | } 31 | 32 | public mutating func append(bit: UInt8) { 33 | self[bitAt: next] = bit 34 | next += 1 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/CircularArray.swift: -------------------------------------------------------------------------------- 1 | public struct CircularArray: Sequence { 2 | private var values = [T]() 3 | public let capacity: Int 4 | private(set) var insertPos = 0 5 | 6 | public var isEmpty: Bool { return values.isEmpty } 7 | public var count: Int { return values.count } 8 | 9 | public init(capacity: Int) { 10 | self.capacity = capacity 11 | } 12 | 13 | public subscript(_ index: Int) -> T { 14 | get { return values[index] } 15 | set(newValue) { values[index] = newValue } 16 | } 17 | 18 | public mutating func push(_ value: T) { 19 | if count < capacity { 20 | values.append(value) 21 | } else { 22 | values[insertPos] = value 23 | } 24 | insertPos = (insertPos + 1) % capacity 25 | } 26 | 27 | public func makeIterator() -> Iterator { 28 | return Iterator(values: values, capacity: capacity, insertPos: insertPos) 29 | } 30 | 31 | public struct Iterator: IteratorProtocol { 32 | private let values: [T] 33 | private let capacity: Int 34 | private let insertPos: Int 35 | private let destinationPos: Int 36 | private let checkFirstIteration: Bool 37 | private var isFirstIteration: Bool = true 38 | private var index: Int 39 | 40 | public init(values: [T], capacity: Int, insertPos: Int) { 41 | self.values = values 42 | self.capacity = capacity 43 | self.insertPos = insertPos 44 | 45 | if values.count >= capacity { 46 | // Circular array is filled, wrap around while iterating 47 | index = insertPos 48 | destinationPos = insertPos 49 | checkFirstIteration = false 50 | } else { 51 | // Circular array is not filled, iterate normally 52 | index = 0 53 | destinationPos = values.count 54 | checkFirstIteration = true 55 | } 56 | } 57 | 58 | public mutating func next() -> T? { 59 | guard (isFirstIteration && !checkFirstIteration) || (index != destinationPos) else { return nil } 60 | 61 | let element = values[index] 62 | index = (index + 1) % capacity 63 | isFirstIteration = false 64 | 65 | return element 66 | } 67 | } 68 | } 69 | 70 | extension CircularArray: Sendable where T: Sendable {} 71 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/ExpiringList.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * A linked list of expiring elements. 5 | * 6 | * Note that elements are expired _lazily_, i.e. 7 | * they are not freed from memory before 8 | * accessing the list. 9 | */ 10 | public class ExpiringList: Sequence { 11 | public typealias Element = T 12 | 13 | private var head: Node? = nil 14 | private var tail: Node? = nil 15 | private var currentCount: Int = 0 16 | 17 | private var dateProvider: () -> Date 18 | 19 | public var count: Int { 20 | removeExpired() 21 | return currentCount 22 | } 23 | public var isEmpty: Bool { return count == 0 } 24 | 25 | class Node { 26 | let element: T 27 | let expiry: Date 28 | var next: Node? = nil 29 | 30 | init(element: T, expiry: Date) { 31 | self.element = element 32 | self.expiry = expiry 33 | } 34 | } 35 | 36 | public struct Iterator: IteratorProtocol { 37 | private var current: Node? 38 | 39 | init(from head: Node?) { 40 | current = head 41 | } 42 | 43 | public mutating func next() -> T? { 44 | let value = current?.element 45 | current = current?.next 46 | return value 47 | } 48 | } 49 | 50 | public init(dateProvider: @escaping () -> Date = Date.init) { 51 | self.dateProvider = dateProvider 52 | } 53 | 54 | public func append(_ element: T, expiry: Date) { 55 | removeExpired() 56 | 57 | let node = Node(element: element, expiry: expiry) 58 | tail?.next = node 59 | tail = node 60 | if head == nil { 61 | head = node 62 | } 63 | 64 | currentCount += 1 65 | } 66 | 67 | private func removeExpired() { 68 | while (head?.expiry.timeIntervalSince(dateProvider()) ?? 1) < 0 { 69 | head = head?.next 70 | currentCount -= 1 71 | } 72 | } 73 | 74 | public func makeIterator() -> Iterator { 75 | removeExpired() 76 | return Iterator(from: head) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/FixedArray.swift: -------------------------------------------------------------------------------- 1 | /// A workaround that provides a stack-allocated, 2 | /// fixed size array up to length 10 and falls back 3 | /// to dynamically allocation for longer arrays. 4 | public enum FixedArray { 5 | case len0 6 | case len1(T) 7 | case len2(T, T) 8 | case len3(T, T, T) 9 | case len4(T, T, T, T) 10 | case len5(T, T, T, T, T) 11 | case len6(T, T, T, T, T, T) 12 | case len7(T, T, T, T, T, T, T) 13 | case len8(T, T, T, T, T, T, T, T) 14 | case len9(T, T, T, T, T, T, T, T, T) 15 | case len10(T, T, T, T, T, T, T, T, T, T) 16 | case lenDyn([T]) 17 | 18 | public var isEmpty: Bool { return count == 0 } 19 | 20 | public var count: Int { 21 | switch self { 22 | case .len0: return 0 23 | case .len1(_): return 1 24 | case .len2(_, _): return 2 25 | case .len3(_, _, _): return 3 26 | case .len4(_, _, _, _): return 4 27 | case .len5(_, _, _, _, _): return 5 28 | case .len6(_, _, _, _, _, _): return 6 29 | case .len7(_, _, _, _, _, _, _): return 7 30 | case .len8(_, _, _, _, _, _, _, _): return 8 31 | case .len9(_, _, _, _, _, _, _, _, _): return 9 32 | case .len10(_, _, _, _, _, _, _, _, _, _): return 10 33 | case .lenDyn(let a): return a.count 34 | } 35 | } 36 | 37 | public func withAppended(_ value: T) -> FixedArray { 38 | switch self { 39 | case .len0: return .len1(value) 40 | case let .len1(a): return .len2(a, value) 41 | case let .len2(a, b): return .len3(a, b, value) 42 | case let .len3(a, b, c): return .len4(a, b, c, value) 43 | case let .len4(a, b, c, d): return .len5(a, b, c, d, value) 44 | case let .len5(a, b, c, d, e): return .len6(a, b, c, d, e, value) 45 | case let .len6(a, b, c, d, e, f): return .len7(a, b, c, d, e, f, value) 46 | case let .len7(a, b, c, d, e, f, g): return .len8(a, b, c, d, e, f, g, value) 47 | case let .len8(a, b, c, d, e, f, g, h): return .len9(a, b, c, d, e, f, g, h, value) 48 | case let .len9(a, b, c, d, e, f, g, h, i): return .len10(a, b, c, d, e, f, g, h, i, value) 49 | case let .len10(a, b, c, d, e, f, g, h, i, j): return .lenDyn([a, b, c, d, e, f, g, h, i, j, value]) 50 | case let .lenDyn(a): return .lenDyn(a + [value]) 51 | } 52 | } 53 | 54 | public subscript(_ n: Int) -> T { 55 | switch self { 56 | case .len0: fatalError("Cannot subscript an empty array") 57 | case let .len1(a): switch n { 58 | case 0: return a 59 | default: fatalError("Index \(n) is out of bounds") 60 | } 61 | case let .len2(a, b): switch n { 62 | case 0: return a 63 | case 1: return b 64 | default: fatalError("Index \(n) is out of bounds") 65 | } 66 | case let .len3(a, b, c): switch n { 67 | case 0: return a 68 | case 1: return b 69 | case 2: return c 70 | default: fatalError("Index \(n) is out of bounds") 71 | } 72 | case let .len4(a, b, c, d): switch n { 73 | case 0: return a 74 | case 1: return b 75 | case 2: return c 76 | case 3: return d 77 | default: fatalError("Index \(n) is out of bounds") 78 | } 79 | case let .len5(a, b, c, d, e): switch n { 80 | case 0: return a 81 | case 1: return b 82 | case 2: return c 83 | case 3: return d 84 | case 4: return e 85 | default: fatalError("Index \(n) is out of bounds") 86 | } 87 | case let .len6(a, b, c, d, e, f): switch n { 88 | case 0: return a 89 | case 1: return b 90 | case 2: return c 91 | case 3: return d 92 | case 4: return e 93 | case 5: return f 94 | default: fatalError("Index \(n) is out of bounds") 95 | } 96 | case let .len7(a, b, c, d, e, f, g): switch n { 97 | case 0: return a 98 | case 1: return b 99 | case 2: return c 100 | case 3: return d 101 | case 4: return e 102 | case 5: return f 103 | case 6: return g 104 | default: fatalError("Index \(n) is out of bounds") 105 | } 106 | case let .len8(a, b, c, d, e, f, g, h): switch n { 107 | case 0: return a 108 | case 1: return b 109 | case 2: return c 110 | case 3: return d 111 | case 4: return e 112 | case 5: return f 113 | case 6: return g 114 | case 7: return h 115 | default: fatalError("Index \(n) is out of bounds") 116 | } 117 | case let .len9(a, b, c, d, e, f, g, h, i): switch n { 118 | case 0: return a 119 | case 1: return b 120 | case 2: return c 121 | case 3: return d 122 | case 4: return e 123 | case 5: return f 124 | case 6: return g 125 | case 7: return h 126 | case 8: return i 127 | default: fatalError("Index \(n) is out of bounds") 128 | } 129 | case let .len10(a, b, c, d, e, f, g, h, i, j): switch n { 130 | case 0: return a 131 | case 1: return b 132 | case 2: return c 133 | case 3: return d 134 | case 4: return e 135 | case 5: return f 136 | case 6: return g 137 | case 7: return h 138 | case 8: return i 139 | case 9: return j 140 | default: fatalError("Index \(n) is out of bounds") 141 | } 142 | case let .lenDyn(a): return a[n] 143 | } 144 | } 145 | } 146 | 147 | extension FixedArray: Sendable where T: Sendable {} 148 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/LazyDictionary.swift: -------------------------------------------------------------------------------- 1 | public struct LazyDictionary: ExpressibleByDictionaryLiteral, Sequence where K: Hashable { 2 | private var inner: [K: Lazy] = [:] 3 | 4 | public var count: Int { inner.count } 5 | public var isEmpty: Bool { inner.isEmpty } 6 | public var keys: Dictionary>.Keys { inner.keys } 7 | 8 | public init(dictionaryLiteral elements: (K, V)...) { 9 | for (key, value) in elements { 10 | inner[key] = .computed(value) 11 | } 12 | } 13 | 14 | public subscript(_ key: K) -> V? { 15 | get { inner[key]?.wrappedValue } 16 | set { inner[key] = newValue.map { .computed($0) } } 17 | } 18 | 19 | public subscript(lazy key: K) -> Lazy? { 20 | get { inner[key] } 21 | set { inner[key] = newValue } 22 | } 23 | 24 | public func makeIterator() -> LazyMapSequence], (K, V)?>>, (K, V)>.Iterator { 25 | return inner.lazy.compactMap { (k, v) in v.wrappedValue.map { (k, $0) } }.makeIterator() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/PriorityQueue.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * A data structure that allows efficient 3 | * insertion and dequeueing of prioritized items. 4 | * 5 | * This protocol does _not_ make any guarantees 6 | * regarding the order of elements with the same 7 | * priority. Use the `Stable` wrapper to ensure 8 | * a FIFO order, if this property is desired. 9 | */ 10 | public protocol PriorityQueue { 11 | associatedtype Element: Comparable 12 | var count: Int { get } 13 | 14 | init() 15 | 16 | /** Removes the element with the highest priority. */ 17 | mutating func popMax() -> Element? 18 | 19 | /** Inserts an element. */ 20 | mutating func insert(_ element: Element) 21 | } 22 | 23 | public extension PriorityQueue { 24 | var isEmpty: Bool { return count == 0 } 25 | 26 | init(_ elements: S) where S: Sequence, S.Element == Element { 27 | self.init() 28 | for element in elements { 29 | insert(element) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/SearchTree.swift: -------------------------------------------------------------------------------- 1 | public protocol SearchTree { 2 | associatedtype Element: Comparable 3 | 4 | @discardableResult 5 | func insert(_ element: Element) -> Bool 6 | 7 | @discardableResult 8 | func remove(_ element: Element) -> Bool 9 | 10 | func contains(_ element: Element) -> Bool 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Utils/Collections/StablePriorityQueue.swift: -------------------------------------------------------------------------------- 1 | public typealias StableBinaryHeap = StablePriorityQueue>, E> 2 | 3 | public struct StableElement: Comparable, CustomStringConvertible where E: Comparable { 4 | public let inner: E 5 | public let insertion: Int 6 | public var description: String { return "(\(inner))<\(insertion)>" } 7 | 8 | public static func <(lhs: StableElement, rhs: StableElement) -> Bool { 9 | if lhs.inner == rhs.inner { 10 | return lhs.insertion < rhs.insertion 11 | } else { 12 | return lhs.inner < rhs.inner 13 | } 14 | } 15 | 16 | public static func ==(lhs: StableElement, rhs: StableElement) -> Bool { 17 | return lhs.inner == rhs.inner && lhs.insertion == rhs.insertion 18 | } 19 | } 20 | 21 | /** 22 | * A wrapper around a priority queue that 23 | * ensures a FIFO order for elements of the 24 | * same priority. 25 | */ 26 | public struct StablePriorityQueue: PriorityQueue where Q: PriorityQueue, E: Comparable, Q.Element == StableElement { 27 | public typealias Element = E 28 | 29 | // Only accessible (internally) for testing purposes 30 | private(set) var inner: Q = Q.init() 31 | public var count: Int { return inner.count } 32 | private var counter: Int = 0 33 | 34 | public init() {} 35 | 36 | public mutating func insert(_ element: E) { 37 | inner.insert(StableElement(inner: element, insertion: counter)) 38 | counter -= 1 39 | } 40 | 41 | public mutating func popMax() -> E? { 42 | return inner.popMax()?.inner 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Utils/Concurrency/UncheckedSendable.swift: -------------------------------------------------------------------------------- 1 | @propertyWrapper 2 | public struct UncheckedSendable: @unchecked Sendable { 3 | public var wrappedValue: Value 4 | 5 | public init(wrappedValue: Value) { 6 | self.wrappedValue = wrappedValue 7 | } 8 | 9 | public init(_ wrappedValue: Value) { 10 | self.init(wrappedValue: wrappedValue) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Array+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Array { 2 | /// Picks a random index, then swaps the element to the end 3 | /// and pops it from the array. This should only be used 4 | /// if the order of the list does not matter. 5 | /// 6 | /// Runs in O(1). 7 | mutating func removeRandomElementBySwap() -> Element? { 8 | guard !isEmpty else { return nil } 9 | let index = Int.random(in: 0.. [Element] { 18 | guard chosenCount <= count else { fatalError("Cannot choose \(chosenCount) elements from an array of size \(count)!") } 19 | var elements = [Element]() 20 | for _ in 0.. [Element] { 28 | var copy = self 29 | return copy.removeRandomlyChosenBySwap(count: chosenCount) 30 | } 31 | } 32 | 33 | public extension Array where Element: Equatable { 34 | @discardableResult 35 | mutating func removeFirst(value: Element) -> Element? { 36 | guard let index = firstIndex(of: value) else { return nil } 37 | return remove(at: index) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/ChoiceOf+Extensions.swift: -------------------------------------------------------------------------------- 1 | import RegexBuilder 2 | 3 | extension ChoiceOf where RegexOutput == Substring { 4 | /// Constructs a choice from the given sequence. 5 | /// 6 | /// Credit: https://stackoverflow.com/a/73916264 7 | public init(nonEmptyComponents: S) where S: Sequence { 8 | let expressions = nonEmptyComponents.map { AlternationBuilder.buildExpression($0) } 9 | 10 | guard !expressions.isEmpty else { 11 | fatalError("Cannot construct an empty ChoiceOf!") 12 | } 13 | 14 | self = expressions.dropFirst().reduce(AlternationBuilder.buildPartialBlock(first: expressions[0])) { acc, next in 15 | AlternationBuilder.buildPartialBlock(accumulated: acc, next: next) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Collection+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Collection { 2 | var nilIfEmpty: Self? { 3 | isEmpty ? nil : self 4 | } 5 | 6 | subscript(safely index: Index) -> Element? { 7 | indices.contains(index) ? self[index] : nil 8 | } 9 | 10 | /// Splits the collection into chunks 11 | func chunks(ofLength chunkLength: Int) -> [SubSequence] { 12 | guard chunkLength > 0 else { return [] } 13 | var chunks = [SubSequence]() 14 | var remaining = self[...] 15 | var index = startIndex 16 | while formIndex(&index, offsetBy: chunkLength, limitedBy: endIndex) { 17 | chunks.append(remaining.prefix(upTo: index)) 18 | remaining = remaining.suffix(from: index) 19 | } 20 | if !remaining.isEmpty { 21 | chunks.append(remaining) 22 | } 23 | return chunks 24 | } 25 | } 26 | 27 | public extension Collection where Element: RandomAccessCollection { 28 | var transposed: [[Element.Element]] { 29 | first?.indices.map { i in map { $0[i] } } ?? [] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Dictionary where Key: StringProtocol, Value: StringProtocol { 2 | var urlQueryEncoded: String { 3 | map { "\($0.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? String($0))=\($1.addingPercentEncoding(withAllowedCharacters: .alphanumerics) ?? String($1))" } 4 | .joined(separator: "&") 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Optional+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Optional { 2 | func filter(_ predicate: (Wrapped) throws -> Bool) rethrows -> Wrapped? { 3 | try flatMap { try predicate($0) ? $0 : nil } 4 | } 5 | 6 | // MARK: Async combinators 7 | 8 | func asyncFilter(_ predicate: @Sendable (Wrapped) async throws -> Bool) async rethrows -> Wrapped? { 9 | try await asyncFlatMap { try await predicate($0) ? $0 : nil } 10 | } 11 | 12 | func asyncMap(_ transform: @Sendable (Wrapped) async throws -> T) async rethrows -> T? { 13 | try await asyncFlatMap(transform) 14 | } 15 | 16 | func asyncFlatMap(_ transform: @Sendable (Wrapped) async throws -> T?) async rethrows -> T? { 17 | if let self { 18 | try await transform(self) 19 | } else { 20 | nil 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/RandomAccessCollection+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension RandomAccessCollection { 2 | func truncated(to length: Int, appending appended: Element? = nil) -> [Element] { 3 | if count > length { 4 | return appended.map { prefix(length - 1) + [$0] } ?? Array(prefix(length)) 5 | } else { 6 | return Array(self) 7 | } 8 | } 9 | 10 | func truncated(to length: Int, _ appender: ([Element]) -> Element) -> [Element] { 11 | if count > length { 12 | return prefix(length - 1) + [appender(Array(dropFirst(length - 1)))] 13 | } else { 14 | return Array(self) 15 | } 16 | } 17 | 18 | func repeated(count: Int) -> [Element] { 19 | assert(count >= 0) 20 | var result = [Element]() 21 | for _ in 0.. Bool) rethrows -> (SubSequence, SubSequence) { 29 | let pre = try prefix(while: inPrefix) 30 | let rest = self[pre.endIndex...] 31 | return (pre, rest) 32 | } 33 | } 34 | 35 | public extension RandomAccessCollection where Element: StringProtocol { 36 | /// Creates a natural language 'enumeration' of the items, e.g. 37 | /// 38 | /// ["apples", "bananas", "pears"] -> "apples, bananas and pears" 39 | func englishEnumerated() -> String { 40 | switch count { 41 | case 0: return "" 42 | case 1: return String(first!) 43 | default: return "\(prefix(count - 1).joined(separator: ", ")) and \(last!)" 44 | } 45 | } 46 | } 47 | 48 | public extension RandomAccessCollection where Element: Equatable { 49 | func allIndices(of element: Element) -> [Index] { 50 | return zip(indices, self).filter { $0.1 == element }.map { $0.0 } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Result+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Result { 2 | static func from(_ value: Success?, errorIfNil: Failure) -> Self { 3 | value.map { Result.success($0) } ?? Result.failure(errorIfNil) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/Sequence+Extensions.swift: -------------------------------------------------------------------------------- 1 | public extension Sequence { 2 | func count(forWhich predicate: (Element) throws -> Bool) rethrows -> Int { 3 | // TODO: Implemented in https://github.com/apple/swift-evolution/blob/master/proposals/0220-count-where.md 4 | try reduce(0) { try predicate($1) ? $0 + 1 : $0 } 5 | } 6 | 7 | /// Similar to reduce, but returns a list of successive reduced values from the left 8 | func scan(_ initial: T, _ accumulator: (T, Element) throws -> T) rethrows -> [T] { 9 | var scanned = [initial] 10 | for value in self { 11 | scanned.append(try accumulator(scanned.last!, value)) 12 | } 13 | return scanned 14 | } 15 | 16 | /// Similar to scan, but without an initial element 17 | func scan1(_ accumulator: (Element, Element) throws -> Element) rethrows -> [Element] { 18 | var scanned = [Element]() 19 | for value in self { 20 | scanned.append(try scanned.last.map { try accumulator($0, value) } ?? value) 21 | } 22 | return scanned 23 | } 24 | 25 | /// Reduce, but without an initial element 26 | func reduce1(_ accumulator: (Element, Element) throws -> Element) rethrows -> Element? { 27 | var result: Element? = nil 28 | for value in self { 29 | result = try result.map { try accumulator($0, value) } ?? value 30 | } 31 | return result 32 | } 33 | 34 | /// Turns a list of optionals into an optional list, like Haskell's 'sequence'. 35 | func sequenceMap(_ transform: (Element) throws -> T? ) rethrows -> [T]? { 36 | var result = [T]() 37 | 38 | for element in self { 39 | guard let transformed = try transform(element) else { return nil } 40 | result.append(transformed) 41 | } 42 | 43 | return result 44 | } 45 | 46 | func withoutDuplicates(by mapper: (Element) throws -> T) rethrows -> [Element] where T: Hashable { 47 | var result = [Element]() 48 | var keys = Set() 49 | 50 | for element in self { 51 | let key = try mapper(element) 52 | if !keys.contains(key) { 53 | keys.insert(key) 54 | result.append(element) 55 | } 56 | } 57 | 58 | return result 59 | } 60 | 61 | @available(*, deprecated, message: "Use groupingPreservingOrder(by:) instead") 62 | func grouped(by mapper: (Element) throws -> K) rethrows -> [(K, [Element])] where K: Hashable { 63 | try Dictionary(grouping: enumerated(), by: { try mapper($0.1) }) 64 | .map { ($0.0, $0.1.sorted(by: ascendingComparator { $0.0 })) } 65 | .sorted(by: ascendingComparator { ($0.1)[0].0 }) 66 | .map { ($0.0, $0.1.map(\.1)) } 67 | } 68 | 69 | // Groups a sequence, preserving the order of the elements 70 | func groupingPreservingOrder(by mapper: (Element) throws -> K) rethrows -> [(K, [Element])] where K: Hashable { 71 | try Dictionary(grouping: enumerated(), by: { try mapper($0.1) }) 72 | .map { ($0.0, $0.1.sorted(by: ascendingComparator { $0.0 })) } 73 | .sorted(by: ascendingComparator { ($0.1)[0].0 }) 74 | .map { ($0.0, $0.1.map(\.1)) } 75 | } 76 | 77 | // MARK: Async combinators 78 | 79 | func asyncFilter(_ predicate: @Sendable (Element) async throws -> Bool) async rethrows -> [Element] { 80 | var result: [Element] = [] 81 | 82 | for element in self { 83 | if try await predicate(element) { 84 | result.append(element) 85 | } 86 | } 87 | 88 | return result 89 | } 90 | 91 | func asyncMap(_ transform: @Sendable (Element) async throws -> T) async rethrows -> [T] { 92 | var result: [T] = [] 93 | 94 | for element in self { 95 | result.append(try await transform(element)) 96 | } 97 | 98 | return result 99 | } 100 | 101 | func asyncFlatMap(_ transform: @Sendable (Element) async throws -> S) async rethrows -> [T] where S: Sequence { 102 | var result: [T] = [] 103 | 104 | for element in self { 105 | result += try await transform(element) 106 | } 107 | 108 | return result 109 | } 110 | 111 | func asyncCompactMap(_ transform: @Sendable (Element) async throws -> T?) async rethrows -> [T] { 112 | var result: [T] = [] 113 | 114 | for element in self { 115 | if let transformed = try await transform(element) { 116 | result.append(transformed) 117 | } 118 | } 119 | 120 | return result 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/StringProtocol+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | fileprivate let asciiCharacters = CharacterSet(charactersIn: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") 4 | fileprivate let quotes = CharacterSet(charactersIn: "\"'`") 5 | nonisolated(unsafe) fileprivate let markdownEscapable = #/[\[\]*_]/# 6 | 7 | extension StringProtocol { 8 | public var withFirstUppercased: String { 9 | prefix(1).uppercased() + dropFirst() 10 | } 11 | 12 | public var markdownEscaped: String { 13 | String(self).replacing(markdownEscapable, with: { "\\\($0.0)" }) 14 | } 15 | 16 | public var camelHumps: [String] { 17 | var humps = [String]() 18 | var hump = "" 19 | 20 | for c in self { 21 | if c.isUppercase && hump.count > 1 { 22 | humps.append(hump) 23 | hump = String(c) 24 | } else { 25 | hump.append(c) 26 | } 27 | } 28 | 29 | if !hump.isEmpty { 30 | humps.append(hump) 31 | } 32 | 33 | return humps 34 | } 35 | 36 | public var camelHumpsWithUnderscores: [String] { 37 | camelHumps.flatMap { $0.split(separator: "_") }.map(String.init) 38 | } 39 | 40 | public func split(by length: Int) -> [String] { 41 | var start = startIndex 42 | var output = [String]() 43 | 44 | while start < endIndex { 45 | let end = index(start, offsetBy: length, limitedBy: endIndex) ?? endIndex 46 | output.append(String(self[start.. [String] { 54 | var split = [String]() 55 | var segment = "" 56 | var quoteStack = [Character]() 57 | var last: Character? = nil 58 | for c in self { 59 | if quoteStack.isEmpty && c == separator { 60 | split.append(segment) 61 | segment = "" 62 | } else { 63 | let isQuote = c.unicodeScalars.first.map { quotes.contains($0) } ?? false 64 | let isEscaped = last == "\\" 65 | 66 | if isQuote && !isEscaped { 67 | if let quote = quoteStack.last, quote == c { 68 | quoteStack.removeLast() 69 | } else { 70 | quoteStack.append(c) 71 | } 72 | } 73 | 74 | if isQuote && isEscaped && omitBackslashes { 75 | segment.removeLast() 76 | } 77 | 78 | if !omitQuotes || !isQuote || isEscaped { 79 | segment.append(c) 80 | } 81 | } 82 | last = c 83 | } 84 | split.append(segment) 85 | return split 86 | } 87 | 88 | public var asciiOnly: String? { 89 | return components(separatedBy: asciiCharacters).joined() 90 | } 91 | 92 | public var nilIfEmpty: Self? { 93 | return isEmpty ? nil : self 94 | } 95 | 96 | public var isAlphabetic: Bool { 97 | for scalar in unicodeScalars { 98 | if !CharacterSet.letters.contains(scalar) { 99 | return false 100 | } 101 | } 102 | return true 103 | } 104 | 105 | public func truncated(to length: Int, appending trailing: String = "") -> String { 106 | if count > length { 107 | return prefix(length) + trailing 108 | } else { 109 | return String(self) 110 | } 111 | } 112 | 113 | public func pluralized(with value: Int) -> String { 114 | value == 1 ? String(self) : "\(self)s" 115 | } 116 | 117 | public func editDistance( 118 | to rhs: S, 119 | caseSensitive: Bool = true, 120 | allowInsertionAndDeletion: Bool = true, 121 | allowSubstitution: Bool = true 122 | ) -> Int where S: StringProtocol { 123 | precondition( 124 | allowInsertionAndDeletion || allowSubstitution, 125 | "Either insertion/deletion or substitution must be allowed to compute an edit distance!" 126 | ) 127 | 128 | let width = count + 1 129 | let height = rhs.count + 1 130 | var matrix = Matrix(repeating: 0, width: width, height: height) 131 | let (lhsChars, rhsChars) = caseSensitive 132 | ? (Array(self), Array(rhs)) 133 | : (Array(lowercased()), Array(rhs.lowercased())) 134 | 135 | for i in 0..(to rhs: S, caseSensitive: Bool = true) -> Int where S: StringProtocol { 169 | editDistance( 170 | to: rhs, 171 | caseSensitive: caseSensitive 172 | ) 173 | } 174 | 175 | /// The Longest Common Subsequence (LCS) string distance, i.e. the minimal 176 | /// number of insertions and deletions to transform this string to the given 177 | /// string. 178 | /// 179 | /// This distance is equivalent to `self.count + rhs.count` minus the length 180 | /// of the LCS between `self` and `rhs`. 181 | public func lcsDistance(to rhs: S, caseSensitive: Bool = true) -> Int where S: StringProtocol { 182 | editDistance( 183 | to: rhs, 184 | caseSensitive: caseSensitive, 185 | allowSubstitution: false 186 | ) 187 | } 188 | 189 | /// Applies this string as a 'template' containing % placeholders to a list of arguments 190 | public func applyAsTemplate(to args: [String]) -> String { 191 | var result = "" 192 | var argIterator = args.makeIterator() 193 | for c in self { 194 | if c == "%" { 195 | guard let arg = argIterator.next() else { fatalError("Provided too few args to apply(template:to:)!") } 196 | result += arg 197 | } else { 198 | result.append(c) 199 | } 200 | } 201 | return result 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Sources/Utils/Extensions/TimeInterval+Extensions.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension TimeInterval { 4 | public var asMinutes: Double { self / 60 } 5 | public var asHours: Double { self / 3600 } 6 | public var asDays: Double { self / 86400 } 7 | 8 | public var displayString: String { 9 | if Int(asDays) > 0 { 10 | return String(format: "%.2f days", asDays) 11 | } else if Int(asHours) > 0 { 12 | return String(format: "%.2f hours", asHours) 13 | } else if Int(asMinutes) > 0 { 14 | return String(format: "%.2f minutes", asMinutes) 15 | } else { 16 | return String(format: "%.2f seconds", self) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Utils/Filesystem/TemporaryDirectory.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @preconcurrency import Logging 3 | 4 | fileprivate let log = Logger(label: "Utils.TemporaryDirectory") 5 | 6 | /** 7 | * A custom temporary directory. The directory is deleted 8 | * when this instance is deinitialized. 9 | */ 10 | public class TemporaryDirectory { 11 | public let url: URL 12 | public var deleteAutomatically: Bool = true 13 | public var exists: Bool { return FileManager.default.fileExists(atPath: url.path) } 14 | 15 | public init(prefix: String? = nil) { 16 | let fileManager = FileManager.default 17 | let temporaryDirectory: URL 18 | 19 | if #available(macOS 10.12, *) { 20 | temporaryDirectory = fileManager.temporaryDirectory 21 | } else { 22 | temporaryDirectory = URL(fileURLWithPath: NSTemporaryDirectory()) 23 | } 24 | 25 | let dirName = (`prefix`.map { "\($0)-" } ?? "") + UUID().uuidString 26 | url = temporaryDirectory.appendingPathComponent(dirName) 27 | } 28 | 29 | public func create(withIntermediateDirectories: Bool = true) throws { 30 | try FileManager.default.createDirectory(at: url, withIntermediateDirectories: withIntermediateDirectories) 31 | } 32 | 33 | public func childFile(named name: String) -> TemporaryFile { 34 | return TemporaryFile(url: url.appendingPathComponent(name)) 35 | } 36 | 37 | deinit { 38 | do { 39 | if deleteAutomatically && exists { 40 | try FileManager.default.removeItem(at: url) 41 | } 42 | } catch { 43 | log.error("Error while removing temporary directory: \(error)") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Utils/Filesystem/TemporaryFile.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @preconcurrency import Logging 3 | 4 | fileprivate let log = Logger(label: "Utils.TemporaryFile") 5 | 6 | /** 7 | * A custom temporary file. The file is deleted 8 | * when this instance is deinitialized if `deleteAutomatically` 9 | * is set. 10 | */ 11 | public class TemporaryFile { 12 | public let url: URL 13 | public var deleteAutomatically: Bool = false 14 | public var exists: Bool { return FileManager.default.fileExists(atPath: url.path) } 15 | 16 | public init(url: URL) { 17 | self.url = url 18 | } 19 | 20 | public func write(utf8 contents: String) throws { 21 | guard let data = contents.data(using: .utf8) else { throw EncodeError.couldNotEncode(contents) } 22 | try write(data: data) 23 | } 24 | 25 | public func write(data: Data) throws { 26 | let fileManager = FileManager.default 27 | 28 | if exists { 29 | try fileManager.removeItem(at: url) 30 | } 31 | 32 | fileManager.createFile(atPath: url.path, contents: data, attributes: nil) 33 | } 34 | 35 | public func readData() -> Data? { 36 | return FileManager.default.contents(atPath: url.path) 37 | } 38 | 39 | public func readUTF8() -> String? { 40 | return readData().flatMap { String(data: $0, encoding: .utf8) } 41 | } 42 | 43 | public func delete() throws { 44 | try FileManager.default.removeItem(at: url) 45 | } 46 | 47 | deinit { 48 | do { 49 | if deleteAutomatically && exists { 50 | try delete() 51 | } 52 | } catch { 53 | log.error("Error while removing temporary file: \(error)") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Utils/Iterators/PeekableIterator.swift: -------------------------------------------------------------------------------- 1 | public struct PeekableIterator: IteratorProtocol where I: IteratorProtocol { 2 | private var inner: I 3 | private var peeked: I.Element? = nil 4 | 5 | public private(set) var current: I.Element? = nil 6 | 7 | public init(_ inner: I) { 8 | self.inner = inner 9 | } 10 | 11 | public mutating func next() -> I.Element? { 12 | if let p = peeked { 13 | peeked = nil 14 | current = p 15 | } else { 16 | current = inner.next() 17 | } 18 | return current 19 | } 20 | 21 | public mutating func peek() -> I.Element? { 22 | if peeked == nil { 23 | peeked = inner.next() 24 | } 25 | return peeked 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Utils/Metaprogramming/ConstBoolTypes.swift: -------------------------------------------------------------------------------- 1 | // Type-level representation of booleans that let 2 | // you statically parameterize a type with a constant. 3 | // 4 | // See ConstIntTypes for more info. 5 | 6 | public protocol ConstBool { 7 | static var value: Bool { get } 8 | } 9 | 10 | public struct True: ConstBool { public static var value: Bool { true } } 11 | public struct False: ConstBool { public static var value: Bool { false } } 12 | -------------------------------------------------------------------------------- /Sources/Utils/Metaprogramming/ConstIntTypes.swift: -------------------------------------------------------------------------------- 1 | // Type-level representation of integers that let 2 | // you statically parameterize a type with a constant. 3 | // 4 | // Other languages, like C++ or Rust, have first-class 5 | // support for this feature under the name 'const generics' 6 | // offering support for parameterization of types over 7 | // compile-time constants. 8 | // 9 | // If you are curious, there is an even more powerful variant 10 | // of this feature called 'dependent types' that let 11 | // you not just parameterize over constants but also 12 | // over variables whose value may first be known at 13 | // runtime. 14 | 15 | public protocol ConstInt { 16 | static var value: Int { get } 17 | } 18 | 19 | public struct Zero: ConstInt { public static var value: Int { 0 } } 20 | public struct One: ConstInt { public static var value: Int { 1 } } 21 | public struct Two: ConstInt { public static var value: Int { 2 } } 22 | public struct Three: ConstInt { public static var value: Int { 3 } } 23 | public struct Four: ConstInt { public static var value: Int { 4 } } 24 | public struct Five: ConstInt { public static var value: Int { 5 } } 25 | public struct Ten: ConstInt { public static var value: Int { 10 } } 26 | public struct Twenty: ConstInt { public static var value: Int { 20 } } 27 | public struct Thirty: ConstInt { public static var value: Int { 30 } } 28 | public struct Fourty: ConstInt { public static var value: Int { 40 } } 29 | public struct Fifty: ConstInt { public static var value: Int { 50 } } 30 | public struct Hundred: ConstInt { public static var value: Int { 100 } } 31 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/AsyncRunnable.swift: -------------------------------------------------------------------------------- 1 | public protocol AsyncRunnable { 2 | func run() async 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/CompareUtils.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * A higher-order function that produces an ascending comparator 3 | * (as used by sorted(by:)) comparing a specific property. 4 | */ 5 | public func ascendingComparator(comparing property: @escaping (T) -> P) -> (T, T) -> Bool 6 | where P: Comparable { 7 | { property($0) < property($1) } 8 | } 9 | 10 | /** 11 | * A higher-order function that produces an ascending comparator 12 | * (as used by sorted(by:)) comparing a specific property. 13 | */ 14 | public func ascendingComparator(comparing property: @escaping (T) -> P, then inner: @escaping (T) -> R) -> (T, T) -> Bool 15 | where P: Equatable & Comparable, R: Comparable { 16 | { 17 | let p0 = property($0) 18 | let p1 = property($1) 19 | return p0 == p1 ? (inner($0) < inner($1)) : (p0 < p1) 20 | } 21 | } 22 | 23 | /** 24 | * A higher-order function that produces a descending comparator 25 | * (as used by sorted(by:)) comparing two specific properties lexicographically. 26 | */ 27 | public func descendingComparator(comparing property: @escaping (T) -> P) -> (T, T) -> Bool 28 | where P: Comparable { 29 | { property($0) > property($1) } 30 | } 31 | 32 | /** 33 | * A higher-order function that produces a descending comparator 34 | * (as used by sorted(by:)) comparing two specific properties lexicographically. 35 | */ 36 | public func descendingComparator(comparing property: @escaping (T) -> P, then inner: @escaping (T) -> R) -> (T, T) -> Bool 37 | where P: Equatable & Comparable, R: Comparable { 38 | { 39 | let p0 = property($0) 40 | let p1 = property($1) 41 | return p0 == p1 ? (inner($0) > inner($1)) : (p0 > p1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/DefaultInitializable.swift: -------------------------------------------------------------------------------- 1 | public protocol DefaultInitializable { 2 | init() 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/DiskFileError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum DiskFileError: Error { 4 | case fileNotFound(URL) 5 | case noData(String) 6 | case decodingError(String, String, Error) 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/KeyParameterizable.swift: -------------------------------------------------------------------------------- 1 | public struct EmptyKey: StringEnum { 2 | public static var allCases: [EmptyKey] { [] } 3 | public var rawValue: String { "" } 4 | 5 | public init?(rawValue: String) { nil } 6 | } 7 | 8 | /** 9 | * Anything with an enumerable key. 10 | */ 11 | public protocol KeyParameterizable { 12 | associatedtype Key: StringEnum = EmptyKey 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/Runnable.swift: -------------------------------------------------------------------------------- 1 | public protocol Runnable { 2 | func run() 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/Startable.swift: -------------------------------------------------------------------------------- 1 | public protocol Startable { 2 | /** 3 | * Starts something implementation-specific. 4 | * Should not block. 5 | */ 6 | func start() throws 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/StringBuilder.swift: -------------------------------------------------------------------------------- 1 | /** Provides a fluent interface for building strings. */ 2 | public final class StringBuilder { 3 | public private(set) var value: String 4 | public var trimmedValue: String { return value.trimmingCharacters(in: .whitespacesAndNewlines) } 5 | 6 | public init(value: String = "") { 7 | self.value = value 8 | } 9 | 10 | public func append(_ appended: T?, or defaultValue: String = "", withSeparator separator: String = "") -> StringBuilder { 11 | value += (appended.map { String($0) + separator } ?? defaultValue) 12 | return self 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/StringEnum.swift: -------------------------------------------------------------------------------- 1 | public protocol StringEnum: CaseIterable, Hashable { 2 | var rawValue: String { get } 3 | 4 | init?(rawValue: String) 5 | } 6 | -------------------------------------------------------------------------------- /Sources/Utils/Misc/UnionStringEnum.swift: -------------------------------------------------------------------------------- 1 | @dynamicMemberLookup 2 | public struct UnionStringEnum: StringEnum where R: StringEnum, S: StringEnum { 3 | public static var allCases: [UnionStringEnum] { (R.allCases.map { $0.rawValue } + S.allCases.map { $0.rawValue }).compactMap(Self.init(rawValue:)) } 4 | public let rawValue: String 5 | 6 | public init?(rawValue: String) { 7 | guard R.init(rawValue: rawValue) != nil || S.init(rawValue: rawValue) != nil else { return nil } 8 | self.rawValue = rawValue 9 | } 10 | 11 | public static subscript(dynamicMember rawValue: String) -> UnionStringEnum { 12 | guard let member = UnionStringEnum(rawValue: rawValue) else { fatalError("Not a valid union enum member: \(rawValue)") } 13 | return member 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Utils/Networking/AddressUtils.swift: -------------------------------------------------------------------------------- 1 | nonisolated(unsafe) fileprivate let hostPortPattern = #/([^:]+)(?::(\d+))?/# 2 | 3 | public func parseHostPort(from raw: String) -> (String, Int32?)? { 4 | (try? hostPortPattern.firstMatch(in: raw)).map { 5 | (String($0.1), $0.2.flatMap { Int32($0) }) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Networking/NetworkError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum NetworkError: Error { 4 | case couldNotCreateURL(URLComponents) 5 | case invalidAddress(String, Int32) 6 | case ioError(Error) 7 | case missingData 8 | case missingURL 9 | case notUTF8(Data) 10 | case jsonDecodingError(String) 11 | } 12 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Axis.swift: -------------------------------------------------------------------------------- 1 | /** An axis in the 2D-plane. */ 2 | public enum Axis: String { 3 | case x 4 | case y 5 | 6 | public var asUnitVector: Vec2 { 7 | switch self { 8 | case .x: return Vec2(x: 1, y: 0) 9 | case .y: return Vec2(x: 0, y: 1) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Complex.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A complex number, i.e. an element of the algebraic 4 | /// closure of the real numbers. 5 | public struct Complex: SignedNumeric, Addable, Subtractable, Multipliable, Divisible, Negatable, Absolutable, Hashable, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, CustomStringConvertible, Sendable { 6 | public static let i = Complex(0, i: 1) 7 | public var real: Double 8 | public var imag: Double 9 | public var description: String { return "\(real) + \(imag)i" } 10 | public var magnitudeSquared: Double { return (real * real) + (imag * imag) } 11 | public var magnitude: Double { return magnitudeSquared.squareRoot() } 12 | public var absolute: Double { return magnitude } 13 | public var squared: Complex { return self * self } 14 | public var conjugate: Complex { return Complex(real, i: -imag) } 15 | 16 | public var exp: Complex { 17 | guard imag != 0 else { 18 | return Complex(Foundation.exp(real)) 19 | } 20 | 21 | guard real != 0 else { 22 | return Complex(cos(imag), i: sin(imag)) 23 | } 24 | 25 | return Complex(real).exp * Complex(i: imag).exp 26 | } 27 | 28 | public init(_ real: Double = 0, i imag: Double = 0) { 29 | self.real = real 30 | self.imag = imag 31 | } 32 | 33 | public init(exactly value: T) { 34 | self.init(Double(value)) 35 | } 36 | 37 | public init(integerLiteral value: Int) { 38 | self.init(Double(value)) 39 | } 40 | 41 | public init(floatLiteral value: Double) { 42 | self.init(value) 43 | } 44 | 45 | public static func +(lhs: Complex, rhs: Complex) -> Complex { 46 | return Complex(lhs.real + rhs.real, i: lhs.imag + rhs.imag) 47 | } 48 | 49 | public static func -(lhs: Complex, rhs: Complex) -> Complex { 50 | return Complex(lhs.real - rhs.real, i: lhs.imag - rhs.imag) 51 | } 52 | 53 | public static func +=(lhs: inout Complex, rhs: Complex) { 54 | lhs.real += rhs.real 55 | lhs.imag += rhs.imag 56 | } 57 | 58 | public static func -=(lhs: inout Complex, rhs: Complex) { 59 | lhs.real -= rhs.real 60 | lhs.imag -= rhs.imag 61 | } 62 | 63 | public static func *=(lhs: inout Complex, rhs: Complex) { 64 | let newReal = (lhs.real * rhs.real) - (lhs.imag * rhs.imag) 65 | let newImag = (lhs.real * rhs.imag) + (lhs.imag * rhs.real) 66 | lhs.real = newReal 67 | lhs.imag = newImag 68 | } 69 | 70 | public static func /=(lhs: inout Complex, rhs: Complex) { 71 | let denominator = (rhs.real * rhs.real) + (rhs.imag * rhs.imag) 72 | let newReal = ((lhs.real * rhs.real) + (lhs.imag * rhs.imag)) / denominator 73 | let newImag = ((lhs.imag * rhs.real) - (lhs.real * rhs.imag)) / denominator 74 | lhs.real = newReal 75 | lhs.imag = newImag 76 | } 77 | 78 | public static func *(lhs: Complex, rhs: Double) -> Complex { 79 | return Complex(lhs.real * rhs, i: lhs.imag * rhs) 80 | } 81 | 82 | public static func *(lhs: Complex, rhs: Int) -> Complex { 83 | return lhs * Double(rhs) 84 | } 85 | 86 | public static func /(lhs: Complex, rhs: Double) -> Complex { 87 | return Complex(lhs.real / rhs, i: lhs.imag / rhs) 88 | } 89 | 90 | public static func /(lhs: Complex, rhs: Int) -> Complex { 91 | return lhs / Double(rhs) 92 | } 93 | 94 | public static func *(lhs: Complex, rhs: Complex) -> Complex { 95 | var result = lhs 96 | result *= rhs 97 | return result 98 | } 99 | 100 | public static func /(lhs: Complex, rhs: Complex) -> Complex { 101 | var result = lhs 102 | result /= rhs 103 | return result 104 | } 105 | 106 | public mutating func negate() { 107 | real.negate() 108 | imag.negate() 109 | } 110 | 111 | public prefix static func -(operand: Complex) -> Complex { 112 | return Complex(-operand.real, i: -operand.imag) 113 | } 114 | 115 | public func equals(_ rhs: Complex, accuracy: Double) -> Bool { 116 | return (real - rhs.real).magnitude < accuracy 117 | && (imag - rhs.imag).magnitude < accuracy 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Direction.swift: -------------------------------------------------------------------------------- 1 | /** An axis-aligned direction in the 2D-plane. */ 2 | public enum Direction: String { 3 | case up 4 | case down 5 | case left 6 | case right 7 | 8 | public var asUnitVector: Vec2 { 9 | switch self { 10 | case .up: return Vec2(x: 0, y: -1) 11 | case .down: return Vec2(x: 0, y: 1) 12 | case .left: return Vec2(x: -1, y: 0) 13 | case .right: return Vec2(x: 1, y: 0) 14 | } 15 | } 16 | public var horizontal: Bool { 17 | self == .left || self == .right 18 | } 19 | public var axis: Axis { 20 | horizontal ? .x : .y 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/FibonacciSequence.swift: -------------------------------------------------------------------------------- 1 | public struct FibonacciSequence: Sequence where Value: ExpressibleByIntegerLiteral & Addable & Equatable { 2 | public init() {} 3 | 4 | public func makeIterator() -> Iterator { 5 | Iterator() 6 | } 7 | 8 | public struct Iterator: IteratorProtocol { 9 | private var nMinus2: Value = 0 10 | private var nMinus1: Value = 0 11 | 12 | public mutating func next() -> Value? { 13 | guard nMinus1 != 0 else { 14 | nMinus1 = 1 15 | return 1 16 | } 17 | let n = nMinus1 + nMinus2 18 | nMinus2 = nMinus1 19 | nMinus1 = n 20 | return n 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Mat2.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * A linear transformation in 2D euclidean space. 5 | */ 6 | public struct Mat2: Addable, Subtractable, Negatable, Hashable, CustomStringConvertible { 7 | public var ix: T 8 | public var jx: T 9 | public var iy: T 10 | public var jy: T 11 | 12 | public var determinant: T { (ix * jy) - (jx * iy) } 13 | public var inverse: Mat2? { 14 | let det = determinant 15 | guard det != 0 else { return nil } 16 | return Mat2(ix: jy / det, jx: -jx / det, iy: -iy / det, jy: ix / det) 17 | } 18 | public var asMatrix: Matrix { Matrix(width: 2, height: 2, values: [ix, jx, iy, jy]) } 19 | public var asNDArray: NDArray { try! NDArray([ix, jx, iy, jy], shape: [2, 2]) } 20 | public var description: String { ("(\(ix), \(jx))\n(\(iy), \(jy))") } 21 | 22 | public init(ix: T, jx: T, iy: T, jy: T) { 23 | self.ix = ix 24 | self.jx = jx 25 | self.iy = iy 26 | self.jy = jy 27 | } 28 | 29 | public static func zero() -> Mat2 { 30 | Mat2( 31 | ix: 0, jx: 0, 32 | iy: 0, jy: 0 33 | ) 34 | } 35 | 36 | public static func identity() -> Mat2 { 37 | Mat2( 38 | ix: 1, jx: 0, 39 | iy: 0, jy: 1 40 | ) 41 | } 42 | 43 | public static func rotation(by angle: Double) -> Mat2 { 44 | Mat2( 45 | ix: cos(angle), jx: -sin(angle), 46 | iy: sin(angle), jy: cos(angle) 47 | ) 48 | } 49 | 50 | public static func diagonal(x: T, y: T) -> Mat2 { 51 | Mat2( 52 | ix: x, jx: 0, 53 | iy: 0, jy: y 54 | ) 55 | } 56 | 57 | public static func +(lhs: Mat2, rhs: Mat2) -> Mat2 { 58 | Mat2( 59 | ix: lhs.ix + rhs.ix, jx: lhs.jx + rhs.jx, 60 | iy: lhs.iy + rhs.iy, jy: lhs.jy + rhs.jy 61 | ) 62 | } 63 | 64 | public static func -(lhs: Mat2, rhs: Mat2) -> Mat2 { 65 | Mat2( 66 | ix: lhs.ix - rhs.ix, jx: lhs.jx - rhs.jx, 67 | iy: lhs.iy - rhs.iy, jy: lhs.jy - rhs.jy 68 | ) 69 | } 70 | 71 | public static func *(lhs: Mat2, rhs: Mat2) -> Mat2 { 72 | Mat2( 73 | ix: lhs.ix * rhs.ix + lhs.jx * rhs.iy, jx: lhs.ix * rhs.jx + lhs.jx * rhs.jy, 74 | iy: lhs.iy * rhs.ix + lhs.jy * rhs.iy, jy: lhs.iy * rhs.jx + lhs.jy * rhs.jy 75 | ) 76 | } 77 | 78 | public static func *(lhs: Mat2, rhs: Vec2) -> Vec2 { 79 | Vec2( 80 | x: lhs.ix * rhs.x + lhs.jx * rhs.y, 81 | y: lhs.iy * rhs.x + lhs.jy * rhs.y 82 | ) 83 | } 84 | 85 | public static func /(lhs: Mat2, rhs: Mat2) -> Mat2? { 86 | rhs.inverse.map { lhs * $0 } 87 | } 88 | 89 | public static prefix func -(operand: Mat2) -> Mat2 { 90 | Mat2( 91 | ix: -operand.ix, jx: -operand.jx, 92 | iy: -operand.iy, jy: -operand.jy 93 | ) 94 | } 95 | 96 | public static func +=(lhs: inout Mat2, rhs: Mat2) { 97 | lhs.ix += rhs.ix 98 | lhs.iy += rhs.iy 99 | lhs.jx += rhs.jx 100 | lhs.jy += rhs.jy 101 | } 102 | 103 | public static func -=(lhs: inout Mat2, rhs: Mat2) { 104 | lhs.ix -= rhs.ix 105 | lhs.iy -= rhs.iy 106 | lhs.jx -= rhs.jx 107 | lhs.jy -= rhs.jy 108 | } 109 | 110 | public static func *=(lhs: inout Mat2, rhs: Mat2) { 111 | let product = lhs * rhs 112 | lhs.ix = product.ix 113 | lhs.iy = product.iy 114 | lhs.jx = product.jx 115 | lhs.jy = product.jy 116 | } 117 | 118 | public mutating func negate() { 119 | ix.negate() 120 | iy.negate() 121 | jx.negate() 122 | jy.negate() 123 | } 124 | } 125 | 126 | extension NDArray { 127 | public var asMat2: Mat2? { 128 | shape == [2, 2] ? Mat2(ix: values[0], jx: values[1], iy: values[2], jy: values[3]) : nil 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/MathUtils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @preconcurrency import Logging 3 | 4 | fileprivate let log = Logger(label: "Utils.MathUtils") 5 | 6 | extension Double { 7 | public func roundedTo(decimalPlaces: UInt) -> Double { 8 | let scale = pow(10.0, Double(decimalPlaces)) 9 | return (self * scale).rounded() / scale 10 | } 11 | } 12 | 13 | extension UnsignedInteger { 14 | public var leadingZeros: Int { 15 | let maxIndex = bitWidth - 1 16 | for i in 0..> (maxIndex - i)) & 1 != 0 { 18 | return i 19 | } 20 | } 21 | return bitWidth 22 | } 23 | 24 | public func log2Floor() -> Int { 25 | return (bitWidth - 1) - leadingZeros 26 | } 27 | } 28 | 29 | infix operator **: MultiplicationPrecedence 30 | 31 | public func **(lhs: Int, rhs: Int) -> Int { 32 | assert(rhs >= 0) 33 | var result = 1 34 | for _ in 0.. Int { 44 | (lhs % rhs + rhs) % rhs 45 | } 46 | 47 | public func leastCommonMultiple(_ lhs: I, _ rhs: I) -> I where I: ExpressibleByIntegerLiteral & Multipliable & Equatable & Divisible & Remainderable { 48 | (lhs * rhs) / greatestCommonDivisor(lhs, rhs) 49 | } 50 | 51 | public func greatestCommonDivisor(_ lhs: I, _ rhs: I) -> I where I: ExpressibleByIntegerLiteral & Equatable & Remainderable { 52 | rhs == 0 ? lhs : greatestCommonDivisor(rhs, lhs % rhs) 53 | } 54 | 55 | /// Computes the distance metric without risking over/underflow. 56 | private func distance(_ x: I, _ y: I) -> I.Magnitude where I: Comparable & Subtractable & Magnitudable { 57 | x < y ? (y - x).magnitude : (x - y).magnitude 58 | } 59 | 60 | /// Deterministically checks whether a number is prime. 61 | public func isPrime(_ n: I) -> Bool where I: ExpressibleByIntegerLiteral & Remainderable & Divisible & Comparable & Strideable, I.Stride: SignedInteger { 62 | // TODO: Use a better algorithm than trial division 63 | guard n != 2, n != 3 else { return true } 64 | for i in (2..<(n / 2)).reversed() { 65 | guard n % i != 0 else { return false } 66 | } 67 | return true 68 | } 69 | 70 | /// Finds a nontrivial scale of the given number. 71 | public func integerFactor(_ n: I, _ c: I = 1) -> I? where I: ExpressibleByIntegerLiteral & Addable & Multipliable & Subtractable & Divisible & Comparable & Remainderable & Magnitudable & Strideable, I.Magnitude == I, I.Stride: SignedInteger { 72 | func g(_ x: I) -> I { 73 | return (x * x + c) % n 74 | } 75 | 76 | // Pollard's rho algorithm 77 | var x: I = 2 78 | var y: I = 2 79 | var d: I = 1 80 | 81 | while d == 1 { 82 | x = g(x) 83 | y = g(g(y)) 84 | d = greatestCommonDivisor(distance(x, y), n) 85 | } 86 | 87 | return d == n ? (isPrime(n) ? nil : integerFactor(n, c + 1)) : d 88 | } 89 | 90 | /// Finds the prime factorization of the given integer. 91 | public func primeFactorization(_ n: I) -> [I] where I: ExpressibleByIntegerLiteral & Addable & Multipliable & Subtractable & Divisible & Equatable & Comparable & Remainderable & Magnitudable & Strideable, I.Magnitude == I, I.Stride: SignedInteger { 92 | log.trace("Factoring \(n)...") 93 | if let scale = integerFactor(n) { 94 | return primeFactorization(scale) + primeFactorization(n / scale) 95 | } else { 96 | return [n] 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Matrix.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | fileprivate let epsilon = 0.00001 4 | 5 | public struct Matrix: Addable, Subtractable, Hashable, CustomStringConvertible { 6 | public let width: Int 7 | public let height: Int 8 | private var values: [T] 9 | 10 | public var isSquare: Bool { width == height } 11 | 12 | public var rows: [[T]] { (0..] { rows.map(Vector.init) } 15 | public var columnVectors: [Vector] { columns.map(Vector.init) } 16 | public var asArray: [[T]] { rows } 17 | public var asNDArray: NDArray { try! NDArray(values, shape: [height, width]) } 18 | 19 | public var description: String { "(\((0.. 1 else { return values[0] } 30 | return (0..? { 34 | var rowEcholon = self 35 | for x in 0..<(width - 1) { 36 | for y in (x + 1).. epsilon else { return nil } 39 | rowEcholon.add(row: x, toRow: y, scaledBy: -rowEcholon[y, x] / denom) 40 | } 41 | } 42 | return rowEcholon 43 | } 44 | /// The row echolon form where all leading coefficients are 1. 45 | public var normalizedRowEcholonForm: Matrix? { 46 | guard var rowEcholon = rowEcholonForm else { return nil } 47 | for y in 0..? { 54 | guard isSquare else { return nil } 55 | var rowEcholon = self 56 | var inv = Matrix.identity(width: width) 57 | for x in 0..<(width - 1) { 58 | for y in (x + 1).. epsilon else { return nil } 67 | inv.scale(row: y, by: 1 / coeff) 68 | rowEcholon.scale(row: y, by: 1 / coeff) 69 | } 70 | for x in (1.. { 95 | var t = Matrix(width: height, height: width, values: Array(repeating: 0, count: width * height)) 96 | for y in 0.. T { 146 | get { values[y + x * width] } 147 | set { values[y + x * width] = newValue } 148 | } 149 | 150 | public subscript(row y: Int) -> Row { 151 | Row(matrix: self, y: y) 152 | } 153 | 154 | public subscript(column x: Int) -> Column { 155 | Column(matrix: self, x: x) 156 | } 157 | 158 | public static func zero(width: Int, height: Int) -> Matrix { 159 | Matrix(width: width, height: height, values: [T](repeating: 0, count: width * height)) 160 | } 161 | 162 | public static func identity(width: Int) -> Matrix { 163 | var values = [T](repeating: 0, count: width * width) 164 | for i in 0.. Matrix { 171 | let width = diagonalValues.count 172 | var values = [T](repeating: 0, count: width * width) 173 | for (i, diagonalValue) in diagonalValues.enumerated() { 174 | values[(i + 1) * width] = diagonalValue 175 | } 176 | return Matrix(width: width, height: width, values: values) 177 | } 178 | 179 | public func minor(_ y: Int, _ x: Int) -> Matrix { 180 | Matrix(width: width - 1, height: height - 1, values: values.enumerated().compactMap { (i, v) in 181 | (i / width == y || i % width == x) ? nil : v 182 | }) 183 | } 184 | 185 | public func leadingCoefficient(_ y: Int) -> T { 186 | for x in 0.. epsilon { 189 | return coeff 190 | } 191 | } 192 | return 0 193 | } 194 | 195 | public func reshaped(width: Int, height: Int) -> Matrix { 196 | Matrix(width: width, height: height, values: values) 197 | } 198 | 199 | public func with(_ value: T, atY y: Int, x: Int) -> Matrix { 200 | var result = self 201 | result[y, x] = value 202 | return result 203 | } 204 | 205 | public mutating func scale(row y: Int, by factor: T) { 206 | assert(factor != 0) 207 | for x in 0..(_ f: (T) throws -> U) rethrows -> Matrix where U: IntExpressibleAlgebraicField { 219 | Matrix(width: width, height: height, values: try values.map(f)) 220 | } 221 | 222 | public func zip(_ rhs: Matrix, with f: (T, T) throws -> U) rethrows -> Matrix where U: IntExpressibleAlgebraicField { 223 | assert(width == rhs.width && height == rhs.height) 224 | var zipped = [U](repeating: 0, count: values.count) 225 | for i in 0..(width: width, height: height, values: zipped) 229 | } 230 | 231 | public mutating func mapInPlace(_ f: (T) throws -> T) rethrows { 232 | values = try values.map(f) 233 | } 234 | 235 | public mutating func zipInPlace(_ rhs: Matrix, with f: (T, T) throws -> T) rethrows { 236 | values = try Swift.zip(values, rhs.values).map(f) 237 | } 238 | 239 | public static func +(lhs: Matrix, rhs: Matrix) -> Matrix { lhs.zip(rhs, with: +) } 240 | 241 | public static func -(lhs: Matrix, rhs: Matrix) -> Matrix { lhs.zip(rhs, with: -) } 242 | 243 | public static func *(lhs: Matrix, rhs: T) -> Matrix { lhs.map { $0 * rhs } } 244 | 245 | public static func *(lhs: T, rhs: Matrix) -> Matrix { rhs * lhs } 246 | 247 | public static func /(lhs: Matrix, rhs: T) -> Matrix { lhs.map { $0 / rhs } } 248 | 249 | public static func *(lhs: Matrix, rhs: Matrix) -> Matrix { 250 | assert(lhs.width == rhs.height) 251 | var product = Matrix.zero(width: rhs.width, height: lhs.height) 252 | for y in 0.., rhs: Matrix) { lhs.zipInPlace(rhs, with: +) } 265 | 266 | public static func -=(lhs: inout Matrix, rhs: Matrix) { lhs.zipInPlace(rhs, with: -) } 267 | 268 | public static func *=(lhs: inout Matrix, rhs: T) { lhs.mapInPlace { $0 * rhs } } 269 | 270 | public static func /=(lhs: inout Matrix, rhs: T) { lhs.mapInPlace { $0 / rhs } } 271 | 272 | public static func *=(lhs: inout Matrix, rhs: Matrix) { 273 | assert(lhs.width == rhs.width && lhs.height == rhs.height && lhs.width == lhs.height) 274 | lhs.values = (lhs * rhs).values 275 | } 276 | 277 | public struct Row: Sequence { 278 | private let matrix: Matrix 279 | public let y: Int 280 | private var i: Int = 0 281 | 282 | fileprivate init(matrix: Matrix, y: Int) { 283 | self.matrix = matrix 284 | self.y = y 285 | } 286 | 287 | public func makeIterator() -> Iterator { Iterator(row: self) } 288 | 289 | public struct Iterator: IteratorProtocol { 290 | private let row: Row 291 | private var i: Int = 0 292 | 293 | fileprivate init(row: Row) { 294 | self.row = row 295 | } 296 | 297 | public mutating func next() -> T? { 298 | guard i < row.matrix.width else { return nil } 299 | let x = i 300 | i += 1 301 | return row.matrix[row.y, x] 302 | } 303 | } 304 | } 305 | 306 | public struct Column: Sequence { 307 | private let matrix: Matrix 308 | public let x: Int 309 | 310 | fileprivate init(matrix: Matrix, x: Int) { 311 | self.matrix = matrix 312 | self.x = x 313 | } 314 | 315 | public func makeIterator() -> Iterator { Iterator(column: self) } 316 | 317 | public struct Iterator: IteratorProtocol { 318 | private let column: Column 319 | private var i: Int = 0 320 | 321 | fileprivate init(column: Column) { 322 | self.column = column 323 | } 324 | 325 | public mutating func next() -> T? { 326 | guard i < column.matrix.height else { return nil } 327 | let y = i 328 | i += 1 329 | return column.matrix[y, column.x] 330 | } 331 | } 332 | } 333 | } 334 | 335 | extension Matrix: Sendable where T: Sendable {} 336 | 337 | extension NDArray { 338 | public var asMatrix: Matrix? { 339 | dimension == 2 ? Matrix(width: shape[1], height: shape[0], values: values) : nil 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/NDArray.swift: -------------------------------------------------------------------------------- 1 | /// A multidimensional, rectangular, numeric array. 2 | /// Generalization of a matrix/vector/scalar and 3 | /// sometimes referred to as 'tensor'. 4 | public struct NDArray: Addable, Subtractable, Negatable, Hashable, CustomStringConvertible { 5 | public var values: [T] { 6 | didSet { assert(shape.reduce(1, *) == values.count) } 7 | } 8 | public var shape: [Int] { 9 | didSet { assert(shape.reduce(1, *) == values.count) } 10 | } 11 | 12 | public var dimension: Int { shape.count } 13 | public var description: String { generateDescription(coords: []) } 14 | 15 | public var isScalar: Bool { dimension == 0 } 16 | public var asScalar: T? { isScalar ? values[0] : nil } 17 | 18 | /// Creates an nd-array from a scalar. 19 | public init(_ value: T) { 20 | values = [value] 21 | shape = [] 22 | } 23 | 24 | /// Creates an nd-array from a vector. 25 | public init(_ values: [T]) { 26 | self.values = values 27 | shape = [values.count] 28 | } 29 | 30 | /// Creates an nd-array from a matrix. 31 | public init(_ values: [[T]]) throws { 32 | // Ensure the matrix is rectangular 33 | var width: Int? = nil 34 | for row in values { 35 | if let w = width, w != row.count { 36 | throw NDArrayError.shapeMismatch("Cannot create NDArray from non-rectangular matrix") 37 | } 38 | width = row.count 39 | } 40 | 41 | guard let w = width else { throw NDArrayError.shapeMismatch("Cannot create zero-width matrix") } 42 | self.values = values.flatMap { $0 } 43 | shape = [values.count, w] 44 | } 45 | 46 | /// Creates an nd-array from the given nd-arrays. 47 | public init(ndArrays: [NDArray]) throws { 48 | // Ensure the arrays have the same shape 49 | var innerShape: [Int]? = nil 50 | for ndArray in ndArrays { 51 | if let s = innerShape, s != ndArray.shape { 52 | throw NDArrayError.shapeMismatch("Cannot create NDArray from differently sized NDArrays") 53 | } 54 | innerShape = ndArray.shape 55 | } 56 | 57 | guard let shape = innerShape.map({ [ndArrays.count] + $0 }) else { 58 | throw NDArrayError.shapeMismatch("Cannot create a non-scalar NDArray with zero elements along one axis") 59 | } 60 | 61 | values = ndArrays.flatMap { $0.values } 62 | self.shape = shape 63 | } 64 | 65 | /// Creates an nd-array with a given shape. 66 | public init(_ values: [T], shape: [Int]) throws { 67 | guard shape.reduce(1, *) == values.count else { 68 | throw NDArrayError.shapeMismatch("Shape \(shape) does not match value count \(values.count)") 69 | } 70 | self.values = values 71 | self.shape = shape 72 | } 73 | 74 | private func generateDescription(coords: [Int]) -> String { 75 | if coords.count == dimension { 76 | return "\(self[coords])" 77 | } else { 78 | return "(\((0.. T { 83 | get { values[index(of: coords)] } 84 | set { values[index(of: coords)] = newValue } 85 | } 86 | 87 | private func index(of coords: [Int]) -> Int { 88 | var i = 0 89 | var stride = 1 90 | for j in (0.. [NDArray] { 99 | if dimension == 0 { 100 | throw NDArrayError.shapeMismatch("Cannot split scalar") 101 | } 102 | let innerShape = Array(shape.dropFirst()) 103 | return values.chunks(ofLength: shape[0]).map { try! NDArray(Array($0), shape: innerShape) } 104 | } 105 | 106 | public func map(_ f: (T) throws -> U) rethrows -> NDArray where U: IntExpressibleAlgebraicField { 107 | try! NDArray(try values.map(f), shape: shape) 108 | } 109 | 110 | public func zip(_ rhs: NDArray, with f: (T, T) throws -> U) throws -> NDArray where U: IntExpressibleAlgebraicField { 111 | guard shape == rhs.shape else { 112 | throw NDArrayError.shapeMismatch("Cannot zip two differently shaped NDArrays: \(shape), \(rhs.shape)") 113 | } 114 | return try! NDArray(try Swift.zip(values, rhs.values).map(f), shape: shape) 115 | } 116 | 117 | public mutating func mapInPlace(_ f: (T) throws -> T) rethrows { 118 | values = try values.map(f) 119 | } 120 | 121 | public mutating func zipInPlace(_ rhs: NDArray, with f: (T, T) throws -> T) throws { 122 | guard shape == rhs.shape else { 123 | throw NDArrayError.shapeMismatch("Cannot zip two differently shaped NDArrays: \(shape), \(rhs.shape)") 124 | } 125 | values = try Swift.zip(values, rhs.values).map(f) 126 | } 127 | 128 | public static func +(lhs: Self, rhs: Self) -> Self { try! lhs.zip(rhs, with: +) } 129 | 130 | public static func -(lhs: Self, rhs: Self) -> Self { try! lhs.zip(rhs, with: -) } 131 | 132 | public static func *(lhs: Self, rhs: T) -> Self { lhs.map { $0 * rhs } } 133 | 134 | public static func /(lhs: Self, rhs: T) -> Self { lhs.map { $0 / rhs } } 135 | 136 | public static func *(lhs: T, rhs: Self) -> Self { rhs.map { lhs * $0 } } 137 | 138 | public static prefix func -(operand: Self) -> Self { operand.map { -$0 } } 139 | 140 | public static func +=(lhs: inout Self, rhs: Self) { try! lhs.zipInPlace(rhs, with: +) } 141 | 142 | public static func -=(lhs: inout Self, rhs: Self) { try! lhs.zipInPlace(rhs, with: -) } 143 | 144 | public static func *=(lhs: inout Self, rhs: Self) { try! lhs.zipInPlace(rhs, with: *) } 145 | 146 | public static func /=(lhs: inout Self, rhs: Self) { try! lhs.zipInPlace(rhs, with: /) } 147 | 148 | public mutating func negate() { mapInPlace { -$0 } } 149 | 150 | public func dot(_ rhs: Self) throws -> T { 151 | try zip(rhs, with: *).values.reduce(0, +) 152 | } 153 | } 154 | 155 | extension NDArray: Sendable where T: Sendable {} 156 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/NDArrayError.swift: -------------------------------------------------------------------------------- 1 | public enum NDArrayError: Error { 2 | case shapeMismatch(String) 3 | case dimensionMismatch(String) 4 | } 5 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/NDArrayParser.swift: -------------------------------------------------------------------------------- 1 | import RegexBuilder 2 | 3 | nonisolated(unsafe) fileprivate let rawDecimalPattern = #/-?\d+(?:\.\d+)?/# 4 | nonisolated(unsafe) fileprivate let rawFractionPattern = #/-?\d+/\d+/# 5 | // Order of rawFractionPattern and rawDecimalPattern below matters since 6 | // otherwise numerator and denominator would get parsed as separate tokens 7 | nonisolated(unsafe) fileprivate let tokenPattern = Regex { 8 | ChoiceOf { 9 | #/[(),]/# 10 | rawFractionPattern 11 | rawDecimalPattern 12 | } 13 | } 14 | 15 | public struct NDArrayParser: Sendable { 16 | public init() {} 17 | 18 | public func parse(_ input: String) throws -> NDArray { 19 | let tokens = tokenize(input) 20 | return try parseNDArray(from: tokens) 21 | } 22 | 23 | public func parseMultiple(_ input: String) -> [NDArray] { 24 | let tokens = tokenize(input) 25 | var ndArrays = [NDArray]() 26 | 27 | while let ndArray = try? parseNDArray(from: tokens) { 28 | ndArrays.append(ndArray) 29 | } 30 | 31 | return ndArrays 32 | } 33 | 34 | private func tokenize(_ input: String) -> TokenIterator { 35 | let matches: [Substring] = input.matches(of: tokenPattern).map(\.output) 36 | let rawTokens = matches.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } 37 | return TokenIterator(rawTokens) 38 | } 39 | 40 | private func parseNDArray(from tokens: TokenIterator) throws -> NDArray { 41 | if let value = try? parseValue(from: tokens) { 42 | return NDArray(value) 43 | } else { 44 | return try NDArray(ndArrays: try parseCommaSeparatedList("nd-array", from: tokens, valueParser: parseNDArray)) 45 | } 46 | } 47 | 48 | private func parseValue(from tokens: TokenIterator) throws -> Rational { 49 | let token = tokens.peek() 50 | guard let value = token.flatMap({ Rational($0)?.reduced() }) else { throw NDArrayParserError.unrecognizedToken("Expected value, but got '\(token ?? "nil")'") } 51 | tokens.next() 52 | return value 53 | } 54 | 55 | private func parseCommaSeparatedList(_ what: String, from tokens: TokenIterator, valueParser: (TokenIterator) throws -> T) throws -> [T] { 56 | var values = [T]() 57 | let lparen = tokens.next() 58 | guard lparen == "(" else { throw NDArrayParserError.unrecognizedToken("Expected ( while parsing \(what), but got '\(lparen ?? "nil")'") } 59 | while tokens.peek() != ")" { 60 | let value = try valueParser(tokens) 61 | values.append(value) 62 | if tokens.peek() == "," { 63 | tokens.next() 64 | continue 65 | } 66 | let rparen = tokens.peek() 67 | guard rparen == ")" else { throw NDArrayParserError.unrecognizedToken("Expected ) while parsing \(what), but got '\(rparen ?? "nil")'") } 68 | } 69 | tokens.next() 70 | return values 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/NDArrayParserError.swift: -------------------------------------------------------------------------------- 1 | public enum NDArrayParserError: Error { 2 | case unrecognizedToken(String) 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Rational.swift: -------------------------------------------------------------------------------- 1 | nonisolated(unsafe) fileprivate let decimalPattern = #/(?-?)\s*(?\d+)(?:\.(?\d+))?/# 2 | nonisolated(unsafe) fileprivate let fractionPattern = #/(?-?\s*\d+)\s*/\s*(?-?\s*\d+)/# 3 | 4 | fileprivate let reduceThreshold = 1000 5 | 6 | /// A numeric type supporting precise division. 7 | public struct Rational: SignedNumeric, Addable, Subtractable, Multipliable, Divisible, Negatable, Absolutable, ExpressibleByIntegerLiteral, Sendable, Hashable, Comparable, CustomStringConvertible { 8 | public var numerator: Int 9 | public var denominator: Int 10 | public var isPrecise: Bool 11 | 12 | public var asDouble: Double { Double(numerator) / Double(denominator) } 13 | public var magnitude: Rational { Rational(abs(numerator), denominator) } 14 | public var absolute: Double { magnitude.asDouble } 15 | 16 | public var isDisplayedAsFraction: Bool { isPrecise && denominator != 1 } 17 | public var directDescription: String { isPrecise ? (denominator == 1 ? String(numerator) : "\(numerator)/\(denominator)") : "\(asDouble)" } 18 | public var description: String { reduced().directDescription } 19 | 20 | public init?(_ string: String) { 21 | if let parsedFraction = try? fractionPattern.firstMatch(in: string) { 22 | guard let numerator = Int(parsedFraction.numerator), 23 | let denominator = Int(parsedFraction.denominator), 24 | denominator != 0 else { return nil } 25 | self.init(numerator, denominator) 26 | } else if let parsedDecimal = try? decimalPattern.firstMatch(in: string) { 27 | let rawSign = parsedDecimal.sign 28 | let rawCharacteristic = parsedDecimal.characteristic 29 | let rawMantissa = parsedDecimal.mantissa ?? "" 30 | let sign = rawSign == "-" ? -1 : 1 31 | let factor = 10 ** rawMantissa.count 32 | guard let characteristic = Int(rawCharacteristic), factor != 0 else { return nil } 33 | let mantissa = Int(rawMantissa) ?? 0 34 | self.init(sign * (characteristic * factor + mantissa), factor) 35 | reduce() 36 | } else { 37 | return nil 38 | } 39 | } 40 | 41 | public init(exactly value: T) where T: BinaryInteger { 42 | self.init(integerLiteral: Int(value)) 43 | } 44 | 45 | public init(integerLiteral value: Int) { 46 | numerator = value 47 | denominator = 1 48 | isPrecise = true 49 | } 50 | 51 | public init(approximately value: Double, accuracy: Int = 10_000) { 52 | self.init(Int((value * Double(accuracy)).rounded()), accuracy, isPrecise: false) 53 | reduce() 54 | } 55 | 56 | public init(_ numerator: Int, _ denominator: Int, isPrecise: Bool = true) { 57 | guard denominator != 0 else { fatalError("Cannot create a rational with denominator == 0: \(numerator)/\(denominator)") } 58 | self.numerator = numerator 59 | self.denominator = denominator 60 | self.isPrecise = isPrecise 61 | normalizeSign() 62 | } 63 | 64 | public static func +(lhs: Rational, rhs: Rational) -> Rational { 65 | Rational(lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator, lhs.denominator * rhs.denominator, isPrecise: lhs.isPrecise && rhs.isPrecise).autoReduced() 66 | } 67 | 68 | public static func +=(lhs: inout Rational, rhs: Rational) { 69 | let newDenominator = lhs.denominator * rhs.denominator 70 | lhs.numerator = lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator 71 | lhs.denominator = newDenominator 72 | lhs.isPrecise = lhs.isPrecise && rhs.isPrecise 73 | lhs.autoReduce() 74 | } 75 | 76 | public static func -(lhs: Rational, rhs: Rational) -> Rational { 77 | lhs + (-rhs) 78 | } 79 | 80 | public static func -=(lhs: inout Rational, rhs: Rational) { 81 | lhs += (-rhs) 82 | } 83 | 84 | public static func *(lhs: Rational, rhs: Rational) -> Rational { 85 | Rational(lhs.numerator * rhs.numerator, lhs.denominator * rhs.denominator, isPrecise: lhs.isPrecise && rhs.isPrecise).autoReduced() 86 | } 87 | 88 | public static func *=(lhs: inout Rational, rhs: Rational) { 89 | lhs.numerator *= rhs.numerator 90 | lhs.denominator *= rhs.denominator 91 | lhs.isPrecise = lhs.isPrecise && rhs.isPrecise 92 | lhs.autoReduce() 93 | } 94 | 95 | public static func /(lhs: Rational, rhs: Rational) -> Rational { 96 | Rational(lhs.numerator * rhs.denominator, lhs.denominator * rhs.numerator, isPrecise: lhs.isPrecise && rhs.isPrecise).autoReduced() 97 | } 98 | 99 | public static func /=(lhs: inout Rational, rhs: Rational) { 100 | lhs.numerator *= rhs.denominator 101 | lhs.denominator *= rhs.numerator 102 | lhs.isPrecise = lhs.isPrecise && rhs.isPrecise 103 | lhs.autoReduce() 104 | } 105 | 106 | public static prefix func -(operand: Rational) -> Rational { 107 | Rational(-operand.numerator, operand.denominator, isPrecise: operand.isPrecise) 108 | } 109 | 110 | public static func <(lhs: Rational, rhs: Rational) -> Bool { 111 | if lhs.denominator == rhs.denominator { 112 | return lhs.numerator < rhs.numerator 113 | } else { 114 | return lhs.numerator * rhs.denominator < rhs.numerator * lhs.denominator 115 | } 116 | } 117 | 118 | public static func ==(lhs: Rational, rhs: Rational) -> Bool { 119 | let lr = lhs.reduced() 120 | let rr = rhs.reduced() 121 | return lr.numerator == rr.numerator && lr.denominator == rr.denominator 122 | } 123 | 124 | public func hash(into hasher: inout Hasher) { 125 | let r = reduced() 126 | hasher.combine(r.numerator) 127 | hasher.combine(r.denominator) 128 | } 129 | 130 | public func signum() -> Int { 131 | numerator.signum() * denominator.signum() 132 | } 133 | 134 | public mutating func negate() { 135 | numerator.negate() 136 | denominator.negate() 137 | } 138 | 139 | public func expanded(by factor: Int) -> Rational { 140 | Rational(numerator * factor, denominator * factor, isPrecise: isPrecise) 141 | } 142 | 143 | public mutating func expand(by factor: Int) { 144 | numerator *= factor 145 | denominator *= factor 146 | } 147 | 148 | public func reduced(by factor: Int) -> Rational { 149 | Rational(numerator / factor, denominator / factor, isPrecise: isPrecise).normalizedSign() 150 | } 151 | 152 | public func reduced() -> Rational { 153 | reduced(by: greatestCommonDivisor(numerator, denominator)) 154 | } 155 | 156 | public func autoReduced() -> Rational { 157 | var r = self 158 | r.autoReduce() 159 | return r 160 | } 161 | 162 | public mutating func reduce(by factor: Int) { 163 | numerator /= factor 164 | denominator /= factor 165 | normalizeSign() 166 | } 167 | 168 | public mutating func reduce() { 169 | reduce(by: greatestCommonDivisor(numerator, denominator)) 170 | } 171 | 172 | public mutating func autoReduce() { 173 | // Auto-reduce fraction if the denom gets too large 174 | if denominator > reduceThreshold { 175 | reduce() 176 | } 177 | } 178 | 179 | private mutating func normalizeSign() { 180 | let sign = signum() 181 | numerator = sign * abs(numerator) 182 | denominator = abs(denominator) 183 | } 184 | 185 | private func normalizedSign() -> Rational { 186 | var res = self 187 | res.normalizeSign() 188 | return res 189 | } 190 | } 191 | 192 | extension Double { 193 | public init(_ rational: Rational) { 194 | self = rational.asDouble 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Vec2.swift: -------------------------------------------------------------------------------- 1 | public struct Vec2: CustomStringConvertible { 2 | public var x: T 3 | public var y: T 4 | 5 | public var asTuple: (x: T, y: T) { (x: x, y: y) } 6 | public var description: String { "(\(x), \(y))" } 7 | 8 | public init(x: T, y: T) { 9 | self.x = x 10 | self.y = y 11 | } 12 | 13 | public init(both value: T) { 14 | x = value 15 | y = value 16 | } 17 | 18 | public func map(_ f: (T) throws -> U) rethrows -> Vec2 { 19 | try Vec2(x: f(x), y: f(y)) 20 | } 21 | 22 | public func mapBoth(_ fx: (T) throws -> U, _ fy: (T) throws -> U) rethrows -> Vec2 { 23 | try Vec2(x: fx(x), y: fy(y)) 24 | } 25 | 26 | public func with(x newX: T) -> Vec2 { 27 | Vec2(x: newX, y: y) 28 | } 29 | 30 | public func with(y newY: T) -> Vec2 { 31 | Vec2(x: x, y: newY) 32 | } 33 | } 34 | 35 | extension Vec2: Equatable where T: Equatable {} 36 | extension Vec2: Hashable where T: Hashable {} 37 | extension Vec2: Sendable where T: Sendable {} 38 | 39 | extension Vec2 where T: ExpressibleByIntegerLiteral { 40 | public static func zero() -> Vec2 { Vec2() } 41 | 42 | public init() { 43 | self.init(x: 0, y: 0) 44 | } 45 | 46 | public init(x: T) { 47 | self.init(x: x, y: 0) 48 | } 49 | 50 | public init(y: T) { 51 | self.init(x: 0, y: y) 52 | } 53 | } 54 | 55 | extension Vec2 where T: IntExpressibleAlgebraicField { 56 | public var asNDArray: NDArray { NDArray([x, y]) } 57 | } 58 | 59 | extension Vec2: Addable where T: Addable { 60 | public static func +(lhs: Vec2, rhs: Vec2) -> Vec2 { Vec2(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } 61 | 62 | public static func +=(lhs: inout Vec2, rhs: Vec2) { 63 | lhs.x += rhs.x 64 | lhs.y += rhs.y 65 | } 66 | } 67 | 68 | extension Vec2: Subtractable where T: Subtractable { 69 | public static func -(lhs: Vec2, rhs: Vec2) -> Vec2 { Vec2(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } 70 | 71 | public static func -=(lhs: inout Vec2, rhs: Vec2) { 72 | lhs.x -= rhs.x 73 | lhs.y -= rhs.y 74 | } 75 | } 76 | 77 | extension Vec2: Multipliable where T: Multipliable { 78 | public static func *(lhs: Vec2, rhs: Vec2) -> Vec2 { Vec2(x: lhs.x * rhs.x, y: lhs.y * rhs.y) } 79 | 80 | public static func *(lhs: T, rhs: Vec2) -> Vec2 { Vec2(x: lhs * rhs.x, y: lhs * rhs.y) } 81 | 82 | public static func *(lhs: Vec2, rhs: T) -> Vec2 { Vec2(x: lhs.x * rhs, y: lhs.y * rhs) } 83 | 84 | public static func *=(lhs: inout Vec2, rhs: Vec2) { 85 | lhs.x *= rhs.x 86 | lhs.y *= rhs.y 87 | } 88 | } 89 | 90 | extension Vec2: Divisible where T: Divisible { 91 | public static func /(lhs: Vec2, rhs: Vec2) -> Vec2 { Vec2(x: lhs.x / rhs.x, y: lhs.y / rhs.y) } 92 | 93 | public static func /(lhs: Vec2, rhs: T) -> Vec2 { Vec2(x: lhs.x / rhs, y: lhs.y / rhs) } 94 | 95 | public static func /=(lhs: inout Vec2, rhs: Vec2) { 96 | lhs.x /= rhs.x 97 | lhs.y /= rhs.y 98 | } 99 | } 100 | 101 | extension Vec2: Negatable where T: Negatable { 102 | public mutating func negate() { 103 | x.negate() 104 | y.negate() 105 | } 106 | 107 | public prefix static func -(operand: Vec2) -> Vec2 { Vec2(x: -operand.x, y: -operand.y) } 108 | } 109 | 110 | extension Vec2 where T: Multipliable & Addable { 111 | public func dot(_ other: Vec2) -> T { 112 | (x * other.x) + (y * other.y) 113 | } 114 | } 115 | 116 | extension Vec2 where T: Multipliable & Subtractable { 117 | public func cross(_ other: Vec2) -> T { 118 | (x * other.y) - (y * other.x) 119 | } 120 | } 121 | 122 | extension Vec2 where T: Negatable { 123 | public var xInverted: Vec2 { Vec2(x: -x, y: y) } 124 | public var yInverted: Vec2 { Vec2(x: x, y: -y) } 125 | } 126 | 127 | extension Vec2 where T: BinaryFloatingPoint { 128 | public var squaredMagnitude: T { ((x * x) + (y * y)) } 129 | public var magnitude: T { squaredMagnitude.squareRoot() } 130 | public var floored: Vec2 { Vec2(x: Int(x.rounded(.down)), y: Int(y.rounded(.down))) } 131 | } 132 | 133 | extension Vec2 where T: BinaryFloatingPoint & Divisible { 134 | public var normalized: Vec2 { self / magnitude } 135 | } 136 | 137 | extension Vec2 where T: BinaryInteger { 138 | public var squaredMagnitude: Double { Double((x * x) + (y * y)) } 139 | public var magnitude: Double { squaredMagnitude.squareRoot() } 140 | public var asDouble: Vec2 { map { Double($0) } } 141 | } 142 | 143 | extension NDArray { 144 | public var asVec2: Vec2? { 145 | shape == [2] ? Vec2(x: values[0], y: values[1]) : nil 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Sources/Utils/Numerics/Vector.swift: -------------------------------------------------------------------------------- 1 | public struct Vector: Addable, Subtractable, Multipliable, Divisible, Negatable, Hashable, CustomStringConvertible { 2 | public var values: [T] 3 | 4 | public var asNDArray: NDArray { NDArray(values) } 5 | public var description: String { "(\(values.map { "\($0)" }.joined(separator: ", ")))" } 6 | 7 | public init(_ values: [T]) { 8 | self.values = values 9 | } 10 | 11 | public static func zero(size: Int) -> Self { 12 | Vector(Array(repeating: 0, count: size)) 13 | } 14 | 15 | public subscript(_ i: Int) -> T { 16 | get { values[i] } 17 | set { values[i] = newValue } 18 | } 19 | 20 | public func map(_ transform: (T) throws -> U) rethrows -> Vector where U: IntExpressibleAlgebraicField { 21 | Vector(try values.map(transform)) 22 | } 23 | 24 | public func zip(_ rhs: Vector, _ zipper: (T, U) throws -> R) rethrows -> Vector where U: IntExpressibleAlgebraicField, R: IntExpressibleAlgebraicField { 25 | Vector(try Swift.zip(values, rhs.values).map(zipper)) 26 | } 27 | 28 | public mutating func mapInPlace(_ transform: (T) throws -> T) rethrows { 29 | values = try values.map(transform) 30 | } 31 | 32 | public mutating func zipInPlace(_ rhs: Vector, _ zipper: (T, U) throws -> T) rethrows where U: IntExpressibleAlgebraicField { 33 | values = try Swift.zip(values, rhs.values).map(zipper) 34 | } 35 | 36 | public static func +(lhs: Self, rhs: Self) -> Self { lhs.zip(rhs, +) } 37 | 38 | public static func -(lhs: Self, rhs: Self) -> Self { lhs.zip(rhs, -) } 39 | 40 | public static func *(lhs: Self, rhs: Self) -> Self { lhs.zip(rhs, *) } 41 | 42 | public static func /(lhs: Self, rhs: Self) -> Self { lhs.zip(rhs, /) } 43 | 44 | public static func *(lhs: Self, rhs: T) -> Self { lhs.map { $0 * rhs } } 45 | 46 | public static func /(lhs: Self, rhs: T) -> Self { lhs.map { $0 / rhs } } 47 | 48 | public static func *(lhs: T, rhs: Self) -> Self { rhs.map { lhs * $0 } } 49 | 50 | public static func /(lhs: T, rhs: Self) -> Self { rhs.map { lhs / $0 } } 51 | 52 | public prefix static func -(operand: Self) -> Self { operand.map { -$0 } } 53 | 54 | public static func +=(lhs: inout Self, rhs: Self) { lhs.zipInPlace(rhs, +) } 55 | 56 | public static func -=(lhs: inout Self, rhs: Self) { lhs.zipInPlace(rhs, -) } 57 | 58 | public static func *=(lhs: inout Self, rhs: Self) { lhs.zipInPlace(rhs, *) } 59 | 60 | public static func /=(lhs: inout Self, rhs: Self) { lhs.zipInPlace(rhs, /) } 61 | 62 | public mutating func negate() { mapInPlace { -$0 } } 63 | 64 | public func dot(_ other: Self) -> T { 65 | (self * other).values.reduce(0, +) 66 | } 67 | 68 | public func projected(onto other: Self) -> Vector { 69 | let factor: T = dot(other) / other.dot(other) 70 | return factor * other 71 | } 72 | } 73 | 74 | extension Vector where T: BinaryFloatingPoint { 75 | public var magnitude: T { dot(self).squareRoot() } 76 | public var normalized: Self { self / magnitude } 77 | public var floored: Vector { map { Int($0.rounded(.down)) } } 78 | } 79 | 80 | extension Vector where T: BinaryInteger { 81 | public var magnitude: Double { Double(dot(self)).squareRoot() } 82 | public var asDouble: Vector { map { Double($0) } } 83 | } 84 | 85 | extension Vector where T == Rational { 86 | public var magnitude: Double { dot(self).asDouble.squareRoot() } 87 | public var asDouble: Vector { map { $0.asDouble } } 88 | } 89 | 90 | extension Vector: Sendable where T: Sendable {} 91 | 92 | extension NDArray { 93 | public var asVector: Vector? { 94 | dimension == 1 ? Vector(values) : nil 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Absolutable.swift: -------------------------------------------------------------------------------- 1 | public protocol Absolutable { 2 | var absolute: Double { get } 3 | } 4 | 5 | extension Int: Absolutable { 6 | public var absolute: Double { Double(magnitude) } 7 | } 8 | extension UInt: Absolutable { 9 | public var absolute: Double { Double(magnitude) } 10 | } 11 | extension UInt32: Absolutable { 12 | public var absolute: Double { Double(magnitude) } 13 | } 14 | extension UInt64: Absolutable { 15 | public var absolute: Double { Double(magnitude) } 16 | } 17 | extension Double: Absolutable { 18 | public var absolute: Double { magnitude } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Addable.swift: -------------------------------------------------------------------------------- 1 | public protocol Addable { 2 | static func +(lhs: Self, rhs: Self) -> Self 3 | 4 | static func +=(lhs: inout Self, rhs: Self) 5 | } 6 | 7 | extension Int: Addable {} 8 | extension UInt: Addable {} 9 | extension UInt32: Addable {} 10 | extension UInt64: Addable {} 11 | extension Double: Addable {} 12 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/AnyAsyncBijection.swift: -------------------------------------------------------------------------------- 1 | public struct AnyAsyncBijection: AsyncBijection where V: Sendable { 2 | private let applyImpl: @Sendable (V) async -> V 3 | private let inverseApplyImpl: @Sendable (V) async -> V 4 | 5 | public init(apply applyImpl: @Sendable @escaping (V) async -> V, inverseApply inverseApplyImpl: @Sendable @escaping (V) async -> V) { 6 | self.applyImpl = applyImpl 7 | self.inverseApplyImpl = inverseApplyImpl 8 | } 9 | 10 | public init(_ bijection: B) where B: AsyncBijection, B.Value == V { 11 | applyImpl = { await bijection.apply($0) } 12 | inverseApplyImpl = { await bijection.inverseApply($0) } 13 | } 14 | 15 | public init(_ bijection: B) where B: Bijection, B.Value == V { 16 | applyImpl = { bijection.apply($0) } 17 | inverseApplyImpl = { bijection.inverseApply($0) } 18 | } 19 | 20 | public func apply(_ value: V) async -> V { return await applyImpl(value) } 21 | 22 | public func inverseApply(_ value: V) async -> V { return await inverseApplyImpl(value) } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/AnyBijection.swift: -------------------------------------------------------------------------------- 1 | public struct AnyBijection: Bijection { 2 | private let applyImpl: @Sendable (V) -> V 3 | private let inverseApplyImpl: @Sendable (V) -> V 4 | 5 | public init(apply applyImpl: @Sendable @escaping (V) -> V, inverseApply inverseApplyImpl: @Sendable @escaping (V) -> V) { 6 | self.applyImpl = applyImpl 7 | self.inverseApplyImpl = inverseApplyImpl 8 | } 9 | 10 | public init(_ bijection: B) where B: Bijection, B.Value == V { 11 | applyImpl = { bijection.apply($0) } 12 | inverseApplyImpl = { bijection.inverseApply($0) } 13 | } 14 | 15 | public func apply(_ value: V) -> V { return applyImpl(value) } 16 | 17 | public func inverseApply(_ value: V) -> V { return inverseApplyImpl(value) } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/AsyncBijection.swift: -------------------------------------------------------------------------------- 1 | public protocol AsyncBijection: Sendable { 2 | associatedtype Value: Sendable 3 | 4 | func apply(_ value: Value) async -> Value 5 | 6 | func inverseApply(_ value: Value) async -> Value 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Bijection.swift: -------------------------------------------------------------------------------- 1 | public protocol Bijection: AsyncBijection { 2 | associatedtype Value 3 | 4 | func apply(_ value: Value) -> Value 5 | 6 | func inverseApply(_ value: Value) -> Value 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/ComposedAsyncBijection.swift: -------------------------------------------------------------------------------- 1 | public struct ComposedAsyncBijection: AsyncBijection where B: AsyncBijection, C: AsyncBijection, B.Value == C.Value { 2 | public typealias Value = B.Value 3 | 4 | private let outer: B 5 | private let inner: C 6 | 7 | public init(outer: B, inner: C) { 8 | self.outer = outer 9 | self.inner = inner 10 | } 11 | 12 | public func apply(_ value: Value) async -> Value { 13 | await outer.apply(inner.apply(value)) 14 | } 15 | 16 | public func inverseApply(_ value: Value) async -> Value { 17 | await inner.inverseApply(outer.inverseApply(value)) 18 | } 19 | } 20 | 21 | extension AsyncBijection { 22 | public func then(_ outer: B) -> ComposedAsyncBijection where B.Value == Value { 23 | ComposedAsyncBijection(outer: outer, inner: self) 24 | } 25 | 26 | public func compose(_ inner: B) -> ComposedAsyncBijection where B.Value == Value { 27 | ComposedAsyncBijection(outer: self, inner: inner) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/ComposedBijection.swift: -------------------------------------------------------------------------------- 1 | public struct ComposedBijection: Bijection where B: Bijection, C: Bijection, B.Value == C.Value { 2 | public typealias Value = B.Value 3 | 4 | private let outer: B 5 | private let inner: C 6 | 7 | public init(outer: B, inner: C) { 8 | self.outer = outer 9 | self.inner = inner 10 | } 11 | 12 | public func apply(_ value: Value) -> Value { 13 | outer.apply(inner.apply(value)) 14 | } 15 | 16 | public func inverseApply(_ value: Value) -> Value { 17 | inner.inverseApply(outer.inverseApply(value)) 18 | } 19 | } 20 | 21 | extension Bijection { 22 | public func then(_ outer: B) -> ComposedBijection where B.Value == Value { 23 | ComposedBijection(outer: outer, inner: self) 24 | } 25 | 26 | public func compose(_ inner: B) -> ComposedBijection where B.Value == Value { 27 | ComposedBijection(outer: self, inner: inner) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Divisible.swift: -------------------------------------------------------------------------------- 1 | public protocol Divisible { 2 | static func /(lhs: Self, rhs: Self) -> Self 3 | 4 | static func /=(lhs: inout Self, rhs: Self) 5 | } 6 | 7 | extension Int: Divisible {} 8 | extension UInt: Divisible {} 9 | extension UInt32: Divisible {} 10 | extension UInt64: Divisible {} 11 | extension Double: Divisible {} 12 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/IdentityBijection.swift: -------------------------------------------------------------------------------- 1 | public struct IdentityBijection: Bijection { 2 | public init() {} 3 | 4 | public func apply(_ value: V) -> V { value } 5 | 6 | public func inverseApply(_ value: V) -> V { value } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/IntExpressibleAlgebraicField.swift: -------------------------------------------------------------------------------- 1 | public typealias IntExpressibleAlgebraicField = Addable & Subtractable & Multipliable & Divisible & Negatable & ExpressibleByIntegerLiteral & Hashable & Absolutable 2 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/InverseAsyncBijection.swift: -------------------------------------------------------------------------------- 1 | public struct InverseAsyncBijection: AsyncBijection where B: AsyncBijection { 2 | public typealias Value = B.Value 3 | 4 | private let inverse: B 5 | 6 | public init(inverting inverse: B) { 7 | self.inverse = inverse 8 | } 9 | 10 | public func apply(_ value: Value) async -> Value { 11 | await inverse.inverseApply(value) 12 | } 13 | 14 | public func inverseApply(_ value: Value) async -> Value { 15 | await inverse.apply(value) 16 | } 17 | } 18 | 19 | extension AsyncBijection { 20 | public var inverse: InverseAsyncBijection { InverseAsyncBijection(inverting: self) } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/InverseBijection.swift: -------------------------------------------------------------------------------- 1 | public struct InverseBijection: Bijection where B: Bijection { 2 | public typealias Value = B.Value 3 | 4 | private let inverse: B 5 | 6 | public init(inverting inverse: B) { 7 | self.inverse = inverse 8 | } 9 | 10 | public func apply(_ value: Value) -> Value { 11 | inverse.inverseApply(value) 12 | } 13 | 14 | public func inverseApply(_ value: Value) -> Value { 15 | inverse.apply(value) 16 | } 17 | } 18 | 19 | extension Bijection { 20 | public var inverse: InverseBijection { InverseBijection(inverting: self) } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Magnitudable.swift: -------------------------------------------------------------------------------- 1 | public protocol Magnitudable { 2 | associatedtype Magnitude 3 | 4 | var magnitude: Magnitude { get } 5 | } 6 | 7 | extension Int: Magnitudable {} 8 | extension UInt: Magnitudable {} 9 | extension UInt32: Magnitudable {} 10 | extension UInt64: Magnitudable {} 11 | extension Double: Magnitudable {} 12 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Multipliable.swift: -------------------------------------------------------------------------------- 1 | public protocol Multipliable { 2 | static func *(lhs: Self, rhs: Self) -> Self 3 | 4 | static func *=(lhs: inout Self, rhs: Self) 5 | } 6 | 7 | extension Int: Multipliable {} 8 | extension UInt: Multipliable {} 9 | extension UInt32: Multipliable {} 10 | extension UInt64: Multipliable {} 11 | extension Double: Multipliable {} 12 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Negatable.swift: -------------------------------------------------------------------------------- 1 | /** Describes a (signed) type that has an additive inverse. */ 2 | public protocol Negatable { 3 | mutating func negate() 4 | 5 | prefix static func -(operand: Self) -> Self 6 | } 7 | 8 | extension Int: Negatable {} 9 | extension Double: Negatable {} 10 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Remainderable.swift: -------------------------------------------------------------------------------- 1 | public protocol Remainderable { 2 | static func %(lhs: Self, rhs: Self) -> Self 3 | 4 | static func %=(lhs: inout Self, rhs: Self) 5 | } 6 | 7 | extension Int: Remainderable {} 8 | extension UInt: Remainderable {} 9 | extension UInt32: Remainderable {} 10 | extension UInt64: Remainderable {} 11 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Scaling.swift: -------------------------------------------------------------------------------- 1 | public struct Scaling: Bijection { 2 | private let factor: T 3 | 4 | public init(by factor: T) { 5 | self.factor = factor 6 | } 7 | 8 | public func apply(_ value: T) -> T { 9 | return value * factor 10 | } 11 | 12 | public func inverseApply(_ value: T) -> T { 13 | return value / factor 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Subtractable.swift: -------------------------------------------------------------------------------- 1 | public protocol Subtractable { 2 | static func -(lhs: Self, rhs: Self) -> Self 3 | 4 | static func -=(lhs: inout Self, rhs: Self) 5 | } 6 | 7 | extension Int: Subtractable {} 8 | extension UInt: Subtractable {} 9 | extension UInt32: Subtractable {} 10 | extension UInt64: Subtractable {} 11 | extension Double: Subtractable {} 12 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/Translation.swift: -------------------------------------------------------------------------------- 1 | public struct Translation: Bijection { 2 | private let offset: T 3 | 4 | public init(by offset: T) { 5 | self.offset = offset 6 | } 7 | 8 | public func apply(_ value: T) -> T { 9 | return value + offset 10 | } 11 | 12 | public func inverseApply(_ value: T) -> T { 13 | return value - offset 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Utils/Operations/UnsignedConvertible.swift: -------------------------------------------------------------------------------- 1 | public protocol UnsignedConvertible { 2 | associatedtype Unsigned: FixedWidthInteger 3 | 4 | var bitPatternUnsigned: Unsigned { get } 5 | 6 | init(bitPattern: Unsigned) 7 | } 8 | 9 | extension Int32: UnsignedConvertible { 10 | public var bitPatternUnsigned: UInt32 { return UInt32(bitPattern: self) } 11 | } 12 | 13 | extension Int64: UnsignedConvertible { 14 | public var bitPatternUnsigned: UInt64 { return UInt64(bitPattern: self) } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/Utils/Parsing/LegacyRegex.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A wrapper around `NSRegularExpression` with a more modern API. 4 | /// 5 | /// Kept around for legacy purposes. Please use the standard library's `Regex` type in new code. 6 | public struct LegacyRegex: CustomStringConvertible { 7 | private let pattern: NSRegularExpression 8 | 9 | public var caseSensitive: Bool { !pattern.options.contains(.caseInsensitive) } 10 | public var rawPattern: String { return pattern.pattern } 11 | public var description: String { return rawPattern } 12 | 13 | public init(from rawPattern: String, caseSensitive: Bool = true) throws { 14 | pattern = try NSRegularExpression(pattern: rawPattern, options: caseSensitive ? [] : [.caseInsensitive]) 15 | } 16 | 17 | public func matchCount(in str: String) -> Int { 18 | return pattern.numberOfMatches(in: str, range: NSRange(str.startIndex..., in: str)) 19 | } 20 | 21 | public func matches(in str: String) -> [String] { 22 | // Source: https://stackoverflow.com/questions/27880650/swift-extract-regex-matches 23 | 24 | return pattern.matches(in: str, range: NSRange(str.startIndex..., in: str)) 25 | .compactMap { Range($0.range, in: str).map { String(str[$0]) } } 26 | } 27 | 28 | private func groups(from match: NSTextCheckingResult, in str: String) -> [String] { 29 | return (0.. [String]? { 34 | return pattern.matches(in: str, range: NSRange(str.startIndex..., in: str)).first 35 | .map { groups(from: $0, in: str) } 36 | } 37 | 38 | public func allGroups(in str: String) -> [[String]] { 39 | return pattern.matches(in: str, range: NSRange(str.startIndex..., in: str)) 40 | .map { groups(from: $0, in: str) } 41 | } 42 | 43 | public func replace(in str: String, with replacement: String, casePreserving: Bool = false) -> String { 44 | if !casePreserving { 45 | return pattern.stringByReplacingMatches(in: str, range: NSRange(str.startIndex..., in: str), withTemplate: replacement) 46 | } else { 47 | guard !caseSensitive else { fatalError("Case-preserving replacement requires a case-insensitive regex! (...while trying to replace in '\(str)' with '\(replacement)')") } 48 | // Note that case-preserving replacement may truncate resulting substitutions if 49 | // matched strings are shorter than the replacement. 50 | return replace(in: str) { zip($0[0], replacement).map { $0.0.isLowercase ? $0.1.lowercased() : $0.1.uppercased() }.joined() } 51 | } 52 | } 53 | 54 | public func replace(in str: String, using replacer: ([String]) -> String) -> String { 55 | return replaceImpl(in: str) { replacer(groups(from: $0, in: str)) } 56 | } 57 | 58 | private func replaceImpl(in str: String, using replacer: (NSTextCheckingResult) -> String) -> String { 59 | var result = "" 60 | var i = str.startIndex 61 | 62 | for match in pattern.matches(in: str, range: NSRange(str.startIndex..., in: str)) { 63 | guard let range = Range(match.range, in: str) else { continue } 64 | result += str[i.. String { 75 | return NSRegularExpression.escapedPattern(for: str) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Sources/Utils/Parsing/TokenIterator.swift: -------------------------------------------------------------------------------- 1 | /// An iterator wrapper for a sequence of tokens 2 | /// that explicitly uses reference semantics. 3 | public class TokenIterator: IteratorProtocol { 4 | private var iterator: Array.Iterator 5 | private var lookahead: [T] = [] 6 | public private(set) var current: T? = nil 7 | 8 | public init(_ array: [T]) { 9 | iterator = array.makeIterator() 10 | } 11 | 12 | @discardableResult 13 | public func next() -> T? { 14 | if let peeked = lookahead.first { 15 | lookahead.removeFirst() 16 | return peeked 17 | } else { 18 | let nextValue = iterator.next() 19 | current = nextValue 20 | if !lookahead.isEmpty { 21 | lookahead.removeFirst() 22 | } 23 | return nextValue 24 | } 25 | } 26 | 27 | /// Peeks the kth token (if it exists). 28 | public func peek(_ k: Int = 1) -> T? { 29 | guard k >= 1 else { return nil } 30 | 31 | if let peeked = lookahead[safely: k - 1] { 32 | return peeked 33 | } else { 34 | while lookahead.count < k { 35 | if let nextElement = iterator.next() { 36 | lookahead.append(nextElement) 37 | } else { 38 | break 39 | } 40 | } 41 | return lookahead[safely: k - 1] 42 | } 43 | } 44 | 45 | /// Finds the first matching token in the rest of the 46 | /// array without advancing the iteration. This allows users 47 | /// to essentially do 'infinite lookahead'. Note that this will also 48 | /// extend the internal lookahead buffer. 49 | public func first(where predicate: (T) -> Bool) -> T? { 50 | var i = 1 51 | while let token = peek(i) { 52 | if predicate(token) { 53 | return token 54 | } 55 | i += 1 56 | } 57 | return nil 58 | } 59 | 60 | public func contains(where predicate: (T) -> Bool) -> Bool { 61 | first(where: predicate) != nil 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/Utils/Processes/NodePackage.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A wrapper around an executable node package that is located in `Node` under 4 | /// the current working directory. 5 | @available(*, deprecated, message: "This is too project-specific and will be removed from this library.") 6 | public struct NodePackage { 7 | private let directoryURL: URL 8 | 9 | public init(name: String) { 10 | directoryURL = URL(fileURLWithPath: "Node/\(name)") 11 | } 12 | 13 | /// Invokes `npm start` with the given arguments. 14 | public func start(withArgs args: [String]) -> Promise { 15 | Shell().output(for: "npm", in: directoryURL, args: ["run", "--silent", "start", "--"] + args) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/Utils/Processes/Shell.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @preconcurrency import Logging 3 | 4 | fileprivate let log = Logger(label: "Utils.Shell") 5 | 6 | /** A wrapper that simplifies the creation of subprocesses. */ 7 | public struct Shell { 8 | public init() {} 9 | 10 | /** Creates a new subprocess without launching it. */ 11 | public func newProcess( 12 | _ executable: String, 13 | in directory: URL? = nil, 14 | args: [String]? = nil, 15 | useBash: Bool = false, 16 | withPipedOutput: Bool = false, 17 | then terminationHandler: (@Sendable (Process, Pipe?) -> Void)? = nil 18 | ) -> (Pipe?, Process) { 19 | var pipe: Pipe? = nil 20 | let process = Process() 21 | let path = useBash ? "/bin/bash" : findPath(of: executable) 22 | 23 | setExecutable(for: process, toPath: path) 24 | 25 | if let dirURL = directory { 26 | setCurrentDirectory(for: process, toURL: dirURL) 27 | } 28 | 29 | process.arguments = (useBash ? ["-c", executable] : []) + (args ?? []) 30 | 31 | if withPipedOutput { 32 | pipe = Pipe() 33 | process.standardOutput = pipe 34 | } 35 | 36 | if let handler = terminationHandler { 37 | let pipe = pipe 38 | process.terminationHandler = { handler($0, pipe) } 39 | } 40 | 41 | return (pipe, process) 42 | } 43 | 44 | /** Runs the executable and returns the standard output synchronously after the process exits. */ 45 | @discardableResult 46 | public func outputSync(for executable: String, in directory: URL? = nil, args: [String]? = nil, useBash: Bool = false) throws -> Data { 47 | let (pipe, process) = newProcess(executable, in: directory, args: args, useBash: useBash, withPipedOutput: true) 48 | 49 | try execute(process: process) 50 | process.waitUntilExit() 51 | 52 | return pipe!.fileHandleForReading.availableData 53 | } 54 | 55 | /** Runs the executable and returns the standard output synchronously after the process exits. */ 56 | @discardableResult 57 | public func utf8Sync(for executable: String, in directory: URL? = nil, args: [String]? = nil, useBash: Bool = false) throws -> String? { 58 | return String(data: try outputSync(for: executable, in: directory, args: args, useBash: useBash), encoding: .utf8) 59 | } 60 | 61 | /** Runs the executable and asynchronously returns the standard output. */ 62 | @discardableResult 63 | public func output(for executable: String, in directory: URL? = nil, args: [String]? = nil, useBash: Bool = false) -> Promise { 64 | Promise { then in 65 | let (_, process) = newProcess(executable, in: directory, args: args, useBash: useBash, withPipedOutput: true) { 66 | then(.success(($0, $1))) 67 | } 68 | 69 | do { 70 | try execute(process: process) 71 | } catch { 72 | then(.failure(error)) 73 | } 74 | } 75 | .map { (_: Process, pipe: Pipe?) in 76 | pipe!.fileHandleForReading.availableData 77 | } 78 | } 79 | 80 | /** Runs the executable and asynchronously returns the standard output. */ 81 | @discardableResult 82 | public func utf8(for executable: String, in directory: URL? = nil, args: [String]? = nil, useBash: Bool = false) -> Promise { 83 | output(for: executable, in: directory, args: args, useBash: useBash) 84 | .map { String(data: $0, encoding: .utf8) } 85 | } 86 | 87 | /** Creates a new subprocess and launches it. */ 88 | public func run( 89 | _ executable: String, 90 | in directory: URL? = nil, 91 | args: [String]? = nil, 92 | useBash: Bool = false, 93 | withPipedOutput: Bool = false, 94 | then terminationHandler: (@Sendable (Process, Pipe?) -> Void)? = nil 95 | ) throws { 96 | try execute(process: newProcess(executable, in: directory, args: args, useBash: useBash, withPipedOutput: withPipedOutput, then: terminationHandler).1) 97 | } 98 | 99 | public func findPath(of executable: String) -> String { 100 | if executable.contains("/") { 101 | return executable 102 | } else { 103 | // Find executable using 'which'. This code fragment explicitly 104 | // does not invoke 'outputSync' to avoid infinite recursion. 105 | 106 | let pipe = Pipe() 107 | let process = Process() 108 | 109 | setExecutable(for: process, toPath: "/usr/bin/which") 110 | process.arguments = [executable] 111 | process.standardOutput = pipe 112 | 113 | do { 114 | try execute(process: process) 115 | process.waitUntilExit() 116 | } catch { 117 | log.warning("Shell.findPath could launch 'which' to find \(executable)") 118 | return executable 119 | } 120 | 121 | if let output = String(data: pipe.fileHandleForReading.availableData, encoding: .utf8) { 122 | return output.trimmingCharacters(in: .whitespacesAndNewlines) 123 | } else { 124 | log.warning("Shell.findPath could not read 'which' output to find \(executable)") 125 | return executable 126 | } 127 | } 128 | } 129 | 130 | private func setExecutable(for process: Process, toPath filePath: String) { 131 | if #available(macOS 10.13, *) { 132 | process.executableURL = URL(fileURLWithPath: filePath) 133 | } else { 134 | process.launchPath = filePath 135 | } 136 | } 137 | 138 | private func setCurrentDirectory(for process: Process, toURL url: URL) { 139 | if #available(macOS 10.13, *) { 140 | process.currentDirectoryURL = url 141 | } else { 142 | process.currentDirectoryPath = url.path 143 | } 144 | } 145 | 146 | public func execute(process: Process) throws { 147 | if #available(macOS 10.13, *) { 148 | try process.run() 149 | } else { 150 | process.launch() 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /Sources/Utils/Ranges/LowBoundedIntRange.swift: -------------------------------------------------------------------------------- 1 | public protocol LowBoundedIntRange: Sequence { 2 | var count: Int { get } 3 | var lowerBound: Int { get } 4 | } 5 | 6 | extension Range: LowBoundedIntRange where Bound == Int {} 7 | 8 | extension ClosedRange: LowBoundedIntRange where Bound == Int {} 9 | -------------------------------------------------------------------------------- /Sources/Utils/Ranges/RangeUtils.swift: -------------------------------------------------------------------------------- 1 | nonisolated(unsafe) fileprivate let intRangePattern = #/(\d+)\.\.<(\d+)/# 2 | nonisolated(unsafe) fileprivate let closedIntRangePattern = #/(\d+)\.\.\.(\d+)/# 3 | 4 | public func parseIntRange(from str: String) -> Range? { 5 | if let rawBounds = try? intRangePattern.firstMatch(in: str) { 6 | return Int(rawBounds.1)!.. ClosedRange? { 13 | if let rawBounds = try? closedIntRangePattern.firstMatch(in: str) { 14 | return Int(rawBounds.1)!...Int(rawBounds.2)! 15 | } else { 16 | return nil 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/Utils/Scheduling/RepeatingTimer.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | @MainActor 4 | fileprivate var globalTimerIndex: Int = 0 5 | 6 | // TODO: Reimplement this in terms of Swift Concurrency 7 | 8 | public class RepeatingTimer { 9 | public let interval: DispatchTimeInterval 10 | private var timer: DispatchSourceTimer? = nil 11 | private var context: TimerContext 12 | public var isRunning: Bool { return timer != nil } 13 | 14 | public init(interval: DispatchTimeInterval = .seconds(1)) { 15 | self.interval = interval 16 | context = TimerContext() 17 | context.cancel = { [unowned self] in 18 | self.timer?.cancel() 19 | self.timer = nil 20 | } 21 | } 22 | 23 | @MainActor 24 | public func schedule(nTimes n: Int = 1, beginImmediately: Bool = true, action: @escaping (Int, TimerContext) -> Void) { 25 | // (Re)start timer 26 | let queue = DispatchQueue(label: "RepeatingTimer #\(globalTimerIndex)") 27 | var count = 0 28 | 29 | globalTimerIndex += 1 30 | timer?.cancel() 31 | 32 | timer = DispatchSource.makeTimerSource(queue: queue) 33 | let deadline: DispatchTime = beginImmediately ? .now() : .now() + interval 34 | timer!.schedule(deadline: deadline, repeating: interval, leeway: .milliseconds(100)) 35 | timer!.setEventHandler { [unowned self] in 36 | if count >= n { 37 | self.context.cancel() 38 | } else { 39 | action(count, self.context) 40 | count += 1 41 | } 42 | } 43 | 44 | timer!.resume() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Utils/Scheduling/TimerContext.swift: -------------------------------------------------------------------------------- 1 | public struct TimerContext { 2 | public var cancel: (() -> Void)! 3 | } 4 | -------------------------------------------------------------------------------- /Sources/Utils/Serialization/AnyCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Source: https://gist.github.com/harrytwright/6cadb8e19c12525a7bf2b844baaeaa8a 4 | // https://medium.com/@harrywright_57770/the-pain-of-codable-de736e69e9ff 5 | 6 | fileprivate protocol AnyCodableBox { 7 | var base: AnyHashable { get } 8 | } 9 | 10 | fileprivate struct ConcreteCodableBox: AnyCodableBox { 11 | var baseCodable: Base 12 | var base: AnyHashable { AnyHashable(baseCodable) } 13 | 14 | init(_ base: Base) { 15 | self.baseCodable = base 16 | } 17 | } 18 | 19 | public struct AnyCodable: Codable, Hashable, CustomStringConvertible { 20 | private var box: AnyCodableBox 21 | public var description: String { "\(box.base)" } 22 | 23 | public init(_ base: Base) { 24 | box = ConcreteCodableBox(base) 25 | } 26 | 27 | public init(from decoder: Decoder) throws { 28 | let container = try decoder.singleValueContainer() 29 | if let intValue = try? container.decode(Int.self) { 30 | box = ConcreteCodableBox(intValue) 31 | } else if let doubleValue = try? container.decode(Double.self) { 32 | box = ConcreteCodableBox(doubleValue) 33 | } else if let floatValue = try? container.decode(Float.self) { 34 | box = ConcreteCodableBox(floatValue) 35 | } else if let stringValue = try? container.decode(String.self) { 36 | box = ConcreteCodableBox(stringValue) 37 | } else if let dictionaryValue = try? container.decode([String:AnyCodable].self) { 38 | box = ConcreteCodableBox<[String: AnyCodable]>(dictionaryValue) 39 | } else if let arrayValue = try? container.decode([AnyCodable].self) { 40 | box = ConcreteCodableBox<[AnyCodable]>(arrayValue) 41 | } else { 42 | throw DecodingError.typeMismatch( 43 | type(of: self), 44 | .init(codingPath: decoder.codingPath, debugDescription: "Unsupported JSON type") 45 | ) 46 | } 47 | } 48 | 49 | public func encode(to encoder: Encoder) throws { 50 | // Safe to use `as!` becasue the `base` is just `baseCodable` 51 | try (box.base.base as! Codable).encode(to: encoder) 52 | } 53 | 54 | public func hash(into hasher: inout Hasher) { 55 | box.base.hash(into: &hasher) 56 | } 57 | 58 | public static func ==(lhs: Self, rhs: Self) -> Bool { 59 | lhs.box.base == rhs.box.base 60 | } 61 | 62 | public func base(as: T.Type) -> T { 63 | box.base.base as! T 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Sources/Utils/Serialization/AnyCodingKey.swift: -------------------------------------------------------------------------------- 1 | public struct AnyCodingKey: CodingKey, Hashable { 2 | public let stringValue: String 3 | public let intValue: Int? 4 | 5 | public init(stringValue: String) { 6 | self.stringValue = stringValue 7 | intValue = nil 8 | } 9 | 10 | public init(intValue: Int) { 11 | self.intValue = intValue 12 | stringValue = "\(intValue)" 13 | } 14 | } 15 | 16 | extension AnyCodingKey: ExpressibleByStringLiteral { 17 | public init(stringLiteral value: String) { 18 | self.init(stringValue: value) 19 | } 20 | } 21 | 22 | extension AnyCodingKey: ExpressibleByIntegerLiteral { 23 | public init(integerLiteral value: Int) { 24 | self.init(intValue: value) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Utils/Serialization/AutoSerializing.swift: -------------------------------------------------------------------------------- 1 | @preconcurrency import Logging 2 | 3 | fileprivate let log = Logger(label: "Utils.AutoSerializing") 4 | 5 | /** Wraps a value that is automatically read from/written to a file. */ 6 | @propertyWrapper 7 | public class AutoSerializing { 8 | private let serializer = DiskJsonSerializer() 9 | private let filePath: String 10 | public private(set) var storedValue: T 11 | public var wrappedValue: T { 12 | get { storedValue } 13 | set { 14 | storedValue = newValue 15 | writeToDisk() 16 | } 17 | } 18 | 19 | /// A strong reference to the value. 20 | public var projectedValue: Binding { 21 | Binding { [self] in 22 | wrappedValue 23 | } set: { [self] in 24 | wrappedValue = $0 25 | } 26 | } 27 | 28 | public init(wrappedValue: T, filePath: String) { 29 | self.filePath = filePath 30 | if let onDiskValue = try? serializer.readJson(as: T.self, fromFile: filePath) { 31 | storedValue = onDiskValue 32 | } else { 33 | storedValue = wrappedValue 34 | writeToDisk() 35 | } 36 | } 37 | 38 | private func writeToDisk() { 39 | do { 40 | log.info("Auto-serializing to \(filePath)") 41 | try serializer.write(storedValue, asJsonToFile: filePath) 42 | } catch { 43 | log.error("Error during auto-serialization: \(error)") 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Sources/Utils/Serialization/DiskJsonSerializer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public struct DiskJsonSerializer { 4 | private let encoder = JSONEncoder() 5 | private let decoder = JSONDecoder() 6 | 7 | public init() {} 8 | 9 | public func write(_ value: T, asJsonToFile filePath: String) throws { 10 | let url = URL(fileURLWithPath: filePath) 11 | let fileManager = FileManager.default 12 | let data = try encoder.encode(value) 13 | 14 | if fileManager.fileExists(atPath: url.path) { 15 | try fileManager.removeItem(at: url) 16 | } 17 | 18 | fileManager.createFile(atPath: url.path, contents: data, attributes: nil) 19 | } 20 | 21 | public func readJson(as type: T.Type, fromFile filePath: String) throws -> T { 22 | let url = URL(fileURLWithPath: filePath) 23 | let fileManager = FileManager.default 24 | guard fileManager.fileExists(atPath: url.path) else { throw DiskFileError.fileNotFound(url) } 25 | 26 | if let data = fileManager.contents(atPath: url.path) { 27 | do { 28 | return try decoder.decode(type, from: data) 29 | } catch { 30 | throw DiskFileError.decodingError(filePath, String(data: data, encoding: .utf8) ?? "", error) 31 | } 32 | } else { 33 | throw DiskFileError.noData("Could not read any data from '\(filePath)'") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Utils/Serialization/EncodeError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public enum EncodeError: Error { 4 | case couldNotEncode(String) 5 | case couldNotDecode(Data) 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Utils/Statistics/Averager.swift: -------------------------------------------------------------------------------- 1 | /** 2 | * Collects useful statistics on the fly with constant 3 | * memory consumption. 4 | */ 5 | public struct Averager { 6 | private var value: Double = 0 7 | private var count: Int = 0 8 | 9 | public private(set) var minimum: Double? = nil 10 | public private(set) var maximum: Double? = nil 11 | public var average: Double? { count == 0 ? nil : value / Double(count) } 12 | 13 | public init() {} 14 | 15 | public mutating func insert(_ value: I) where I: BinaryInteger { 16 | insert(Double(value)) 17 | } 18 | 19 | public mutating func insert(_ value: F) where F: BinaryFloatingPoint { 20 | self.value += Double(value) 21 | minimum = min(minimum ?? .infinity, Double(value)) 22 | maximum = max(maximum ?? -.infinity, Double(value)) 23 | count += 1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Utils/Statistics/CustomDiscreteDistribution.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A discrete probability distribution created from 4 | /// custom values. 5 | public struct CustomDiscreteDistribution: Distribution { 6 | public let distribution: [(T, Double)] 7 | 8 | public var entropy: Double { 9 | -distribution.map { (_, p) in p * log2(p) }.reduce(0, +) 10 | } 11 | 12 | /// Creates a probability distribution that normalizes the given 13 | /// probabilities to the unit interval. 14 | public init?(normalizing distribution: [(T, N)]) where N: BinaryInteger { 15 | let sum = Double(distribution.map { $0.1 }.reduce(0, +)) 16 | self.init(distribution.map { ($0.0, Double($0.1) / sum) }) 17 | } 18 | 19 | public init?(_ distribution: [(T, Double)]) { 20 | // Make sure that the probabilities add up to one 21 | guard !distribution.isEmpty, 22 | abs(1 - distribution.map { $0.1 }.reduce(0, +)) < 0.001 else { return nil } 23 | self.distribution = distribution 24 | } 25 | 26 | public func sample() -> T { 27 | var x = Double.random(in: 0.0..<1.0) 28 | 29 | for (value, probability) in distribution { 30 | x -= probability 31 | if x < 0 { 32 | return value 33 | } 34 | } 35 | 36 | return distribution.last!.0 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Utils/Statistics/Distribution.swift: -------------------------------------------------------------------------------- 1 | public protocol Distribution { 2 | associatedtype Element 3 | 4 | /// (Randomly) samples a value from the distribution. 5 | func sample() -> Element 6 | } 7 | -------------------------------------------------------------------------------- /Sources/Utils/Synchronization/MutexLock.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /// A basic synchronization primitive. 4 | public struct MutexLock: Sendable { 5 | private let semaphore = DispatchSemaphore(value: 1) 6 | 7 | /// Acquires the lock for the duration of the given block. 8 | /// May block the current thread. 9 | public func lock(_ action: () throws -> Void) rethrows { 10 | semaphore.wait() 11 | try action() 12 | semaphore.signal() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/Utils/Synchronization/Synchronized.swift: -------------------------------------------------------------------------------- 1 | import Dispatch 2 | 3 | /** 4 | * A value with synchronized get/set. 5 | */ 6 | @propertyWrapper 7 | public struct Synchronized: Sendable { 8 | private let semaphore = DispatchSemaphore(value: 1) 9 | nonisolated(unsafe) private var storedValue: T 10 | public var wrappedValue: T { 11 | get { 12 | semaphore.wait() 13 | let tmp = storedValue 14 | semaphore.signal() 15 | return tmp 16 | } 17 | set { 18 | semaphore.wait() 19 | storedValue = newValue 20 | semaphore.signal() 21 | } 22 | } 23 | 24 | public init(wrappedValue: T) { 25 | storedValue = wrappedValue 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/Utils/Time/Time.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /** 4 | * Represents a time, independent of the date. 5 | * Often viewed as an hour-minute-second tuple. 6 | */ 7 | public struct Time { 8 | public let hour: Int 9 | public let minute: Int 10 | public let second: Int 11 | 12 | public var secondOfDay: Int { 13 | return second + (minute * 60) + (hour * 3600) 14 | } 15 | public var minuteOfDay: Int { 16 | return minute + (hour * 60) 17 | } 18 | 19 | public init?(hour: Int, minute: Int, second: Int = 0) { 20 | if hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60 { 21 | self.hour = hour 22 | self.minute = minute 23 | self.second = second 24 | } else { 25 | return nil 26 | } 27 | } 28 | 29 | public func timeInterval(to other: Time) -> TimeInterval { 30 | return TimeInterval(other.secondOfDay - secondOfDay) 31 | } 32 | 33 | public func seconds(to other: Time) -> Int { 34 | return other.secondOfDay - secondOfDay 35 | } 36 | 37 | public func minutes(to other: Time) -> Int { 38 | return other.minuteOfDay - minuteOfDay 39 | } 40 | 41 | public func hours(to other: Time) -> Int { 42 | return other.hour - hour 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Sources/Utils/Web/DocumentToMarkdownConverter.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | @preconcurrency import SwiftSoup 3 | 4 | /** 5 | * Converts HTML documents into Markdown. 6 | */ 7 | public struct DocumentToMarkdownConverter: Sendable { 8 | private let defaultPrefix: String 9 | private let defaultPostfix: String 10 | private let useMultiLineCodeBlocks: Bool 11 | private let codeLanguage: String? 12 | 13 | public init( 14 | defaultPrefix: String = "", 15 | defaultPostfix: String = "", 16 | useMultiLineCodeBlocks: Bool = false, 17 | codeLanguage: String? = nil 18 | ) { 19 | self.defaultPrefix = defaultPrefix 20 | self.defaultPostfix = defaultPostfix 21 | self.useMultiLineCodeBlocks = useMultiLineCodeBlocks 22 | self.codeLanguage = codeLanguage 23 | } 24 | 25 | /** Parses and converts a full HTML document to Markdown. */ 26 | public func convert(htmlDocument: String, baseURL: URL? = nil) throws -> String { 27 | try convert(SwiftSoup.parse(htmlDocument), baseURL: baseURL) 28 | } 29 | 30 | /** Parses and converts an HTML snippet to Markdown. */ 31 | public func convert(htmlFragment: String, baseURL: URL? = nil) throws -> String { 32 | try convert(SwiftSoup.parseBodyFragment(htmlFragment), baseURL: baseURL) 33 | } 34 | 35 | public func plainTextOf(htmlFragment: String) throws -> String { 36 | try SwiftSoup.parseBodyFragment(htmlFragment).text() 37 | } 38 | 39 | /** Converts an HTML element to Markdown. */ 40 | public func convert(_ element: Element, baseURL: URL? = nil, usedPrefixes: Set = [], usedPostfixes: Set = []) throws -> String { 41 | var mdPrefix: String = defaultPrefix 42 | var mdPostfix: String = defaultPostfix 43 | var mdIfEmpty: String = "" 44 | 45 | var content = try element.getChildNodes().map { 46 | if let childElement = $0 as? Element { 47 | return try convert(childElement, baseURL: baseURL, usedPrefixes: usedPrefixes.union([mdPrefix]), usedPostfixes: usedPostfixes.union([mdPostfix])) 48 | } else if let childText = ($0 as? TextNode)?.getWholeText() { 49 | var trimmed = childText.trimmingCharacters(in: .whitespacesAndNewlines) 50 | if childText.hasPrefix(" ") { trimmed = " \(trimmed)" } 51 | if childText.hasSuffix(" ") { trimmed += " " } 52 | return trimmed 53 | } else { 54 | return "" 55 | } 56 | }.joined() 57 | 58 | switch element.tagName() { 59 | case "a": 60 | if let href = try? element.attr("href") { 61 | mdPrefix = "[" 62 | mdPostfix = "](\(URL(string: href, relativeTo: baseURL)?.absoluteString ?? href))" 63 | } 64 | case "b", "strong", "em": 65 | if usedPrefixes.contains("**") { 66 | mdPrefix = "*" 67 | mdPostfix = "*" 68 | } else { 69 | mdPrefix = "**" 70 | mdPostfix = "**" 71 | } 72 | case "i": 73 | mdPrefix = "*" 74 | mdPostfix = "*" 75 | case "u": 76 | mdPrefix = "__" 77 | mdPostfix = "__" 78 | case "br": 79 | mdIfEmpty = "\n" 80 | case "p": 81 | mdPrefix = "\n\n" 82 | mdPostfix = "\n\n" 83 | mdIfEmpty = "\n\n" 84 | content = content.trimmingCharacters(in: .whitespacesAndNewlines) 85 | case "pre", "tt", "code", "samp": 86 | if useMultiLineCodeBlocks && content.contains("\n") { 87 | mdPrefix = "```\(codeLanguage ?? "")\n" 88 | mdPostfix = "\n```" 89 | } else { 90 | mdPrefix = "`" 91 | mdPostfix = "`" 92 | } 93 | content = content.trimmingCharacters(in: .whitespacesAndNewlines) 94 | case "h1", "h2", "h3", "h4", "h5", "h6": 95 | mdPrefix = "\n**" 96 | mdPostfix = "**\n" 97 | case "img": 98 | mdPrefix = (try? element.attr("alt")) ?? defaultPrefix 99 | mdPostfix = defaultPostfix 100 | case "li": 101 | mdPrefix = "- " 102 | mdPostfix = "\n" 103 | default: 104 | break 105 | } 106 | 107 | if usedPrefixes.contains(mdPrefix) { 108 | mdPrefix = defaultPrefix 109 | } 110 | if usedPostfixes.contains(mdPostfix) { 111 | mdPostfix = defaultPostfix 112 | } 113 | 114 | if content.isEmpty { 115 | return mdIfEmpty 116 | } else { 117 | return "\(mdPrefix)\(content)\(mdPostfix)" 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Sources/Utils/Web/HTTPRequest.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(FoundationNetworking) 3 | import FoundationNetworking 4 | #endif 5 | #if canImport(FoundationXML) 6 | import FoundationXML 7 | #endif 8 | @preconcurrency import SwiftSoup 9 | import XMLCoder 10 | 11 | public struct HTTPRequest { 12 | private var request: URLRequest 13 | private let session: URLSession? 14 | 15 | public init(request: URLRequest, session: URLSession? = nil) { 16 | self.request = request 17 | self.session = session 18 | } 19 | 20 | public init(url: URL, session: URLSession? = nil) { 21 | self.init(request: URLRequest(url: url), session: session) 22 | } 23 | 24 | public init( 25 | scheme: String = "https", 26 | host: String, 27 | port: Int? = nil, 28 | path: String, 29 | method: String = "GET", 30 | query: [String: String] = [:], 31 | headers: [String: String] = [:], 32 | body customBody: String? = nil, 33 | session: URLSession = URLSession.shared 34 | ) throws { 35 | let isPost = method == "POST" 36 | 37 | var components = URLComponents() 38 | components.scheme = scheme 39 | components.host = host 40 | components.path = path 41 | components.queryItems = query.isEmpty ? nil : query.map { URLQueryItem(name: $0.key, value: $0.value) } 42 | 43 | if let p = port { 44 | components.port = p 45 | } 46 | 47 | let body: Data 48 | 49 | if isPost && !query.isEmpty { 50 | body = components.percentEncodedQuery?.data(using: .utf8) ?? .init() 51 | components.queryItems = [] 52 | } else { 53 | body = customBody?.data(using: .utf8) ?? .init() 54 | } 55 | 56 | guard let url = components.url else { throw NetworkError.couldNotCreateURL(components) } 57 | 58 | var request = URLRequest(url: url) 59 | request.httpMethod = method 60 | 61 | if isPost { 62 | if !query.isEmpty { 63 | request.setValue("application/x-www-form-urlencoded;charset=UTF-8", forHTTPHeaderField: "Content-Type") 64 | } 65 | request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length") 66 | request.httpBody = body 67 | } 68 | 69 | for (key, value) in headers { 70 | request.setValue(value, forHTTPHeaderField: key) 71 | } 72 | 73 | self.init(request: request, session: session) 74 | } 75 | 76 | /// Runs the request and asynchronously returns the response. 77 | @discardableResult 78 | public func run() async throws -> Data { 79 | try await runAsync().get() 80 | } 81 | 82 | /// Runs the request and returns a `Promise` with the response. 83 | public func runAsync() -> Promise { 84 | Promise { then in 85 | let session = session ?? URLSession.shared 86 | session.dataTask(with: request) { data, response, error in 87 | guard error == nil else { 88 | then(.failure(NetworkError.ioError(error!))) 89 | return 90 | } 91 | guard let data = data else { 92 | then(.failure(NetworkError.missingData)) 93 | return 94 | } 95 | 96 | then(.success(data)) 97 | }.resume() 98 | } 99 | } 100 | 101 | /// Runs the request and asynchronously returns the UTF-8-decoded response. 102 | public func fetchUTF8() async throws -> String { 103 | try await fetchUTF8Async().get() 104 | } 105 | 106 | /// Runs the request and returns a `Promise` with the UTF-8-decoded response. 107 | public func fetchUTF8Async() -> Promise { 108 | runAsync().mapCatching { 109 | if let utf8 = String(data: $0, encoding: .utf8) { 110 | return utf8 111 | } else { 112 | throw NetworkError.notUTF8($0) 113 | } 114 | } 115 | } 116 | 117 | /// Runs the request and asynchronously decodes the response as JSON. 118 | public func fetchJSON(as type: T.Type) async throws -> T where T: Decodable & Sendable { 119 | try await fetchJSONAsync(as: type).get() 120 | } 121 | 122 | /// Runs the request and returns a `Promise` with the value decoded from the 123 | /// response interpreted as JSON. 124 | public func fetchJSONAsync(as type: T.Type) -> Promise where T: Decodable & Sendable { 125 | runAsync().mapCatching { 126 | do { 127 | return try JSONDecoder().decode(type, from: $0) 128 | } catch { 129 | throw NetworkError.jsonDecodingError("\(error): \(String(data: $0, encoding: .utf8) ?? "")") 130 | } 131 | } 132 | } 133 | 134 | /// Runs the request and asynchronously decodes the response as XML. 135 | public func fetchXML(as type: T.Type) async throws -> T where T: Decodable & Sendable { 136 | try await fetchXMLAsync(as: type).get() 137 | } 138 | 139 | /// Runs the request and returns a `Promise` with the value decoded from the 140 | /// response interpreted as XML. 141 | public func fetchXMLAsync(as type: T.Type) -> Promise where T: Decodable & Sendable { 142 | runAsync().mapCatching { try XMLDecoder().decode(type, from: $0) } 143 | } 144 | 145 | /// Runs the request and interprets the response as XML via the given delegate. 146 | public func fetchXMLAsync(using delegate: any XMLParserDelegate & Sendable) { 147 | fetchXMLAsync { delegate } 148 | } 149 | 150 | /// Runs the request and interprets the response as XML via the given delegate. 151 | public func fetchXMLAsync(using delegateFactory: @Sendable @escaping () -> any XMLParserDelegate) { 152 | runAsync().listen { 153 | let delegate = delegateFactory() 154 | switch $0 { 155 | case .success(let data): 156 | let parser = XMLParser(data: data) 157 | parser.delegate = delegate 158 | _ = parser.parse() 159 | case .failure(let error): 160 | // Work around the issue that the method is marked optional on macOS but not on Linux 161 | #if os(macOS) 162 | delegate.parser?(XMLParser(data: Data()), parseErrorOccurred: error) 163 | #else 164 | delegate.parser(XMLParser(data: Data()), parseErrorOccurred: error) 165 | #endif 166 | } 167 | } 168 | } 169 | 170 | /// Runs the request and asynchronously parses the response as HTML. 171 | public func fetchHTML() async throws -> Document { 172 | try await fetchHTMLAsync().get() 173 | } 174 | 175 | /// Runs the request and returns a `Promise` with the parsed HTML document. 176 | public func fetchHTMLAsync() -> Promise { 177 | fetchUTF8Async().mapCatching { try SwiftSoup.parse($0) } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/AsyncLazyExpiring.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Unfortunately Swift (as of 5.10) does not support async property wrappers. 4 | // For clarity, the API does not use autoclosure either. 5 | 6 | /// A value that periodically expires and gets re-queried 7 | /// through a supplied getter. 8 | public actor AsyncLazyExpiring { 9 | public let expiryInterval: TimeInterval 10 | public private(set) var nextExpiry: Date? = nil 11 | private var expired: Bool { nextExpiry.map { $0.timeIntervalSinceNow < 0 } ?? true } 12 | 13 | private let getter: @Sendable () async throws -> T 14 | private var cachedValue: T? 15 | public var wrappedValue: T { 16 | get async throws { 17 | if expired || cachedValue == nil { 18 | try await update() 19 | } 20 | return cachedValue! 21 | } 22 | } 23 | 24 | public init(in expiryInterval: TimeInterval = 1.0, wrappedValue getter: @Sendable @escaping () async throws -> T) { 25 | self.getter = getter 26 | self.expiryInterval = expiryInterval 27 | } 28 | 29 | private func update() async throws { 30 | cachedValue = try await getter() 31 | nextExpiry = Date(timeInterval: expiryInterval, since: Date()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/Binding.swift: -------------------------------------------------------------------------------- 1 | /// A mutable, reference-like property wrapper that can read and write a value. 2 | @propertyWrapper 3 | public struct Binding: Sendable { 4 | private let _get: @Sendable () -> Value 5 | private let _set: @Sendable (Value) -> Void 6 | 7 | public var wrappedValue: Value { 8 | get { _get() } 9 | nonmutating set { _set(newValue) } 10 | } 11 | 12 | public var projectedValue: Binding { 13 | self 14 | } 15 | 16 | /// Creates a binding from another. 17 | public init(projectedValue: Binding) { 18 | _get = projectedValue._get 19 | _set = projectedValue._set 20 | } 21 | 22 | /// Creates a binding with the given getter and setter. 23 | public init(get: @Sendable @escaping () -> Value, set: @Sendable @escaping (Value) -> Void) { 24 | _get = get 25 | _set = set 26 | } 27 | 28 | /// Creates an immutable binding. 29 | public static func constant(_ value: Value) -> Binding { 30 | Binding { 31 | value 32 | } set: { _ in } 33 | } 34 | 35 | /// A binding to the value keyed under the given path. 36 | public subscript(dynamicMember keyPath: WritableKeyPath) -> Binding { 37 | Binding { 38 | wrappedValue[keyPath: keyPath] 39 | } set: { 40 | wrappedValue[keyPath: keyPath] = $0 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/Box.swift: -------------------------------------------------------------------------------- 1 | /// A simple property wrapper that stores a value on the heap. 2 | /// 3 | /// Useful for turning a value type into a reference type without manually 4 | /// writing a class. 5 | @propertyWrapper 6 | public class Box { 7 | public var wrappedValue: T 8 | 9 | /// A strong reference to the value. 10 | public var projectedValue: Binding { 11 | Binding { [self] in 12 | wrappedValue 13 | } set: { [self] in 14 | wrappedValue = $0 15 | } 16 | } 17 | 18 | public init(wrappedValue: T) { 19 | self.wrappedValue = wrappedValue 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/Expiring.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A value that periodically expires and gets re-queried 4 | /// through a supplied getter. 5 | @propertyWrapper 6 | public struct Expiring { 7 | public let expiryInterval: TimeInterval 8 | public private(set) var nextExpiry: Date! 9 | private var expired: Bool { nextExpiry.timeIntervalSinceNow < 0 } 10 | 11 | private let getter: () -> T 12 | private var cachedValue: T! 13 | public var wrappedValue: T { 14 | mutating get { 15 | if expired { 16 | update() 17 | } 18 | return cachedValue 19 | } 20 | } 21 | 22 | public init(wrappedValue getter: @autoclosure @escaping () -> T, in expiryInterval: TimeInterval = 1.0) { 23 | self.getter = getter 24 | self.expiryInterval = expiryInterval 25 | update() 26 | } 27 | 28 | private mutating func update() { 29 | cachedValue = getter() 30 | nextExpiry = Date(timeInterval: expiryInterval, since: Date()) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/Lazy.swift: -------------------------------------------------------------------------------- 1 | @propertyWrapper 2 | public class Lazy { 3 | private var state: State 4 | public var wrappedValue: V { 5 | switch state { 6 | case let .computed(v): 7 | return v 8 | case let .lazy(f): 9 | let v = f() 10 | state = .computed(v) 11 | return v 12 | } 13 | } 14 | 15 | public enum State { 16 | case lazy(() -> V) 17 | case computed(V) 18 | } 19 | 20 | public convenience init(wrappedValue: @autoclosure @escaping () -> V) { 21 | self.init(state: .lazy(wrappedValue)) 22 | } 23 | 24 | public init(state: State) { 25 | self.state = state 26 | } 27 | 28 | public static func lazy(_ f: @escaping () -> V) -> Lazy { .init(state: .lazy(f)) } 29 | 30 | public static func computed(_ v: V) -> Lazy { .init(state: .computed(v)) } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/Utils/Wrappers/PhantomWrapped.swift: -------------------------------------------------------------------------------- 1 | public typealias Phantom

= PhantomWrapped 2 | 3 | public func phantom

() -> Phantom

{ return Phantom(value: ()) } 4 | 5 | /** 6 | * A wrapper that pretends as if it 7 | * owns another type `P`. Mainly useful 8 | * for using generically specialized functions 9 | * without actually accepting or returning 10 | * a value of the type parameter. 11 | */ 12 | public struct PhantomWrapped { 13 | public let value: T 14 | 15 | public init(value: T) { 16 | self.value = value 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/AvlTreeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class AvlTreeTests: XCTestCase { 5 | func testAvlTree() throws { 6 | let root = AvlTree(value: 1) 7 | root.insert(-3) 8 | XCTAssertEqual(root, Tree.node(.leaf(-3), 1, .empty).avl) 9 | 10 | root.insert(0) // Tree has to double-rotate to rebalance itself 11 | XCTAssertEqual(root, Tree.node(.leaf(-3), 0, .leaf(1)).avl) 12 | 13 | root.insert(-5) // Tree still balanced 14 | XCTAssertEqual(root, Tree.node(.node(.leaf(-5), -3, .empty), 0, .leaf(1)).avl) 15 | 16 | root.insert(-6) // Single rotation necessary 17 | XCTAssertEqual(root, Tree.node( 18 | .node(.leaf(-6), -5, .leaf(-3)), 19 | 0, 20 | .leaf(1) 21 | ).avl) 22 | } 23 | 24 | private indirect enum Tree { 25 | case empty 26 | case node(Tree, Int, Tree) 27 | 28 | static func leaf(_ value: Int) -> Tree { 29 | return .node(.empty, value, .empty) 30 | } 31 | 32 | /** Returns an AVL tree representing this node. */ 33 | var avl: AvlTree? { 34 | switch self { 35 | case .empty: 36 | return nil 37 | case let .node(left, value, right): 38 | return AvlTree( 39 | value: value, 40 | left: left.avl, 41 | right: right.avl 42 | ) 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/BiDictionaryTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class BiDictionaryTests: XCTestCase { 5 | func testBiDictionary() throws { 6 | var bd = BiDictionary() 7 | XCTAssert(bd.isEmpty) 8 | 9 | bd["a"] = 1 10 | bd["b"] = 2 11 | XCTAssert(bd.count == 2) 12 | XCTAssertEqual(bd.keysToValues, ["a": 1, "b": 2]) 13 | XCTAssertEqual(bd.valuesToKeys, [1: "a", 2: "b"]) 14 | 15 | bd["a"] = 3 16 | XCTAssertEqual(bd.keysToValues, ["a": 3, "b": 2]) 17 | XCTAssertEqual(bd.valuesToKeys, [3: "a", 2: "b"]) 18 | 19 | bd[value: 2] = nil 20 | XCTAssertEqual(bd.keysToValues, ["a": 3]) 21 | XCTAssertEqual(bd.valuesToKeys, [3: "a"]) 22 | 23 | let bd2: BiDictionary = ["test": "this"] 24 | XCTAssertEqual(bd2.keysToValues, ["test": "this"]) 25 | XCTAssertEqual(bd2.valuesToKeys, ["this": "test"]) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/BinaryHeapTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class BinaryHeapTests: XCTestCase { 5 | func testBinaryHeap() throws { 6 | var heap = BinaryHeap() 7 | heap.insert(7) 8 | XCTAssert(heap.isValidHeap()) 9 | heap.insert(4) 10 | XCTAssert(heap.isValidHeap()) 11 | heap.insert(5) 12 | XCTAssert(heap.isValidHeap()) 13 | heap.insert(-20) 14 | XCTAssert(heap.isValidHeap()) 15 | heap.insert(98) 16 | XCTAssert(heap.isValidHeap()) 17 | heap.insert(0) 18 | XCTAssert(heap.isValidHeap()) 19 | heap.insert(1) 20 | XCTAssert(heap.isValidHeap()) 21 | 22 | XCTAssertEqual(heap.popMax(), 98) 23 | XCTAssert(heap.isValidHeap()) 24 | XCTAssertEqual(heap.popMax(), 7) 25 | XCTAssert(heap.isValidHeap()) 26 | XCTAssertEqual(heap.popMax(), 5) 27 | XCTAssert(heap.isValidHeap()) 28 | XCTAssertEqual(heap.popMax(), 4) 29 | XCTAssert(heap.isValidHeap()) 30 | XCTAssertEqual(heap.popMax(), 1) 31 | XCTAssert(heap.isValidHeap()) 32 | XCTAssertEqual(heap.popMax(), 0) 33 | XCTAssert(heap.isValidHeap()) 34 | XCTAssertEqual(heap.popMax(), -20) 35 | XCTAssert(heap.isValidHeap()) 36 | XCTAssert(heap.isEmpty) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/BitArrayTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class BitArrayTests: XCTestCase { 5 | func testBitArray() throws { 6 | var arr = BitArray() 7 | XCTAssertEqual(arr.bytes, []) 8 | 9 | arr.append(bit: 1) 10 | arr.append(bit: 0) 11 | arr.append(bool: true) 12 | XCTAssertEqual(arr.bytes, [0b10100000]) 13 | 14 | for _ in 0..<8 { 15 | arr.append(bit: 0) 16 | } 17 | arr.append(bit: 1) 18 | XCTAssertEqual(arr.bytes, [0b10100000, 0b00010000]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/CircularArrayTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class CircularArrayTests: XCTestCase { 5 | func testCircularArray() throws { 6 | var a = CircularArray(capacity: 5) 7 | XCTAssert(a.isEmpty, "\(a) should be empty after initialization") 8 | 9 | a.push("test") 10 | XCTAssertEqual(a.insertPos, 1) 11 | a.push("demo") 12 | XCTAssertEqual(a.insertPos, 2) 13 | XCTAssertEqual(Array(a), ["test", "demo"]) 14 | 15 | a.push("1") 16 | XCTAssertEqual(a.insertPos, 3) 17 | a.push("2") 18 | XCTAssertEqual(a.insertPos, 4) 19 | a.push("3") 20 | XCTAssertEqual(a.insertPos, 0) 21 | a.push("4") 22 | XCTAssertEqual(a.insertPos, 1) 23 | XCTAssertEqual(Array(a), ["demo", "1", "2", "3", "4"]) 24 | 25 | for _ in 0..<10 { 26 | a.push("...") 27 | } 28 | 29 | XCTAssertEqual(a.count, a.capacity) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/CollectionUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class CollectionUtilsTests: XCTestCase { 5 | func testChunks() { 6 | XCTAssertEqual("".chunks(ofLength: 0), []) 7 | XCTAssertEqual("".chunks(ofLength: 3), []) 8 | XCTAssertEqual("This is nice".chunks(ofLength: 3), ["Thi", "s i", "s n", "ice"]) 9 | XCTAssertEqual("Test".chunks(ofLength: 1), ["T", "e", "s", "t"]) 10 | XCTAssertEqual("Test".chunks(ofLength: 8), ["Test"]) 11 | XCTAssertEqual("Test this".chunks(ofLength: 4), ["Test", " thi", "s"]) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Collections/StablePriorityQueueTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class StablePriorityQueueTests: XCTestCase { 5 | private struct Item: Comparable, CustomStringConvertible { 6 | let label: String 7 | let id: Int 8 | var description: String { return label } 9 | 10 | static func <(lhs: Item, rhs: Item) -> Bool { 11 | return lhs.id < rhs.id 12 | } 13 | 14 | static func ==(lhs: Item, rhs: Item) -> Bool { 15 | return lhs.id == rhs.id 16 | } 17 | } 18 | 19 | func testStableBinaryHeap() throws { 20 | var pq = StableBinaryHeap() 21 | 22 | // Insertions 23 | 24 | pq.insert(Item(label: "a", id: 10)) 25 | pq.insert(Item(label: "d", id: 2)) 26 | pq.insert(Item(label: "b", id: 10)) 27 | assert(heap: pq, containsInOrder: ["a", "d", "b"]) 28 | pq.insert(Item(label: "e", id: 100)) 29 | assert(heap: pq, containsInOrder: ["e", "a", "b", "d"]) 30 | pq.insert(Item(label: "c", id: 10)) 31 | assert(heap: pq, containsInOrder: ["e", "a", "b", "d", "c"]) 32 | 33 | // Removals 34 | 35 | XCTAssertEqual(pq.popMax()?.label, "e") 36 | XCTAssert(pq.inner.isValidHeap()) 37 | assert(heap: pq, containsInOrder: ["a", "c", "b", "d"]) 38 | 39 | XCTAssertEqual(pq.popMax()?.label, "a") 40 | XCTAssert(pq.inner.isValidHeap()) 41 | assert(heap: pq, containsInOrder: ["b", "c", "d"]) 42 | 43 | XCTAssertEqual(pq.popMax()?.label, "b") 44 | XCTAssert(pq.inner.isValidHeap()) 45 | assert(heap: pq, containsInOrder: ["c", "d"]) 46 | 47 | XCTAssertEqual(pq.popMax()?.label, "c") 48 | XCTAssert(pq.inner.isValidHeap()) 49 | assert(heap: pq, containsInOrder: ["d"]) 50 | 51 | XCTAssertEqual(pq.popMax()?.label, "d") 52 | } 53 | 54 | private func assert(heap: StableBinaryHeap, containsInOrder labels: [String]) { 55 | XCTAssertEqual(heap.inner.elements.map { $0.inner.label }, labels) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Extensions/AsyncExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | @globalActor 5 | private actor GlobalTestActor: GlobalActor { 6 | static let shared = GlobalTestActor() 7 | } 8 | 9 | actor OtherActor { 10 | var y: Int = 12 11 | } 12 | 13 | final class AsyncExtensionsTests: XCTestCase { 14 | func testOptionalAsyncMap() async { 15 | let mapped = await Optional.some(42).asyncMap { $0 * 2 } 16 | XCTAssertEqual(mapped, 84) 17 | 18 | await Task { @GlobalTestActor in 19 | let x: Int = 42 20 | let other = OtherActor() 21 | let mapped = await Optional.some(x).asyncMap { _ in await other.y } 22 | XCTAssertEqual(mapped, 12) 23 | }.value 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Extensions/StringExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class StringExtensionsTests: XCTestCase { 5 | func testSplitPreservingQuotes() { 6 | XCTAssertEqual("this is | a string | separated by pipes".splitPreservingQuotes(by: "|"), [ 7 | "this is ", 8 | " a string ", 9 | " separated by pipes" 10 | ]) 11 | XCTAssertEqual("this string has \"quoted | regions\" | that ' should | ` | not ` | be ' | split".splitPreservingQuotes(by: "|"), [ 12 | "this string has \"quoted | regions\" ", 13 | " that ' should | ` | not ` | be ' ", 14 | " split" 15 | ]) 16 | } 17 | 18 | func testCamelHumps() { 19 | XCTAssertEqual("".camelHumps, []) 20 | XCTAssertEqual("test".camelHumps, ["test"]) 21 | XCTAssertEqual("Upper".camelHumps, ["Upper"]) 22 | XCTAssertEqual("camelCase".camelHumps, ["camel", "Case"]) 23 | XCTAssertEqual("UpperCamelCase".camelHumps, ["Upper", "Camel", "Case"]) 24 | } 25 | 26 | func testLevenshteinDistance() { 27 | XCTAssertEqual("".levenshteinDistance(to: ""), 0) 28 | XCTAssertEqual("".levenshteinDistance(to: "abc"), 3) 29 | XCTAssertEqual("abc".levenshteinDistance(to: "abc"), 0) 30 | XCTAssertEqual("cba".levenshteinDistance(to: "abc"), 2) 31 | XCTAssertEqual("bc".levenshteinDistance(to: "abc"), 1) 32 | XCTAssertEqual("kitten".levenshteinDistance(to: "sitting"), 3) 33 | } 34 | 35 | func testLcsDistance() { 36 | XCTAssertEqual("".lcsDistance(to: ""), 0) 37 | XCTAssertEqual("".lcsDistance(to: "abc"), 3) 38 | XCTAssertEqual("abc".lcsDistance(to: "abc"), 0) 39 | XCTAssertEqual("cba".lcsDistance(to: "abc"), 4) 40 | XCTAssertEqual("bc".lcsDistance(to: "abc"), 1) 41 | XCTAssertEqual("kitten".lcsDistance(to: "sitting"), 5) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Numerics/ComplexTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class ComplexTests: XCTestCase { 5 | private let eps: Double = 0.001 6 | 7 | func testComplex() throws { 8 | let a: Complex = 3 + 4 * .i 9 | let b: Complex = -5.3 - .i 10 | 11 | assertComplexEq(a * b, -11.9 - 24.2 * .i) 12 | assertComplexEq(a + b, -2.3 + 3 * .i) 13 | assertComplexEq(a / b, -0.684083878 - 0.625644551 * .i) 14 | assertComplexEq(a - b, 8.3 + 5 * .i) 15 | assertComplexEq(-a, -3 - 4 * .i) 16 | assertComplexEq(a.conjugate, 3 - 4 * .i) 17 | assertComplexEq(a.exp, -13.128783081462158 - 15.200784463067956 * .i) 18 | } 19 | 20 | private func assertComplexEq(_ lhs: Complex, _ rhs: Complex) { 21 | XCTAssertEqual(lhs.real, rhs.real, accuracy: eps, "Re(\(lhs)) does not equal Re(\(rhs))") 22 | XCTAssertEqual(lhs.imag, rhs.imag, accuracy: eps, "Im(\(lhs)) does not equal Im(\(rhs))") 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Numerics/FibonacciSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class FibonacciSequenceTests: XCTestCase { 5 | func testFibonacciSequence() { 6 | XCTAssertEqual(Array(FibonacciSequence().prefix(10)), [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Numerics/Mat2Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class Mat2Tests: XCTestCase { 5 | func testMat2() throws { 6 | let a = Mat2( 7 | ix: 3, jx: 6, 8 | iy: -6, jy: 9 9 | ) 10 | let b = Mat2( 11 | ix: 1, jx: -2, 12 | iy: 4, jy: 3 13 | ) 14 | XCTAssertEqual(a + b, Mat2( 15 | ix: 4, jx: 4, 16 | iy: -2, jy: 12 17 | )) 18 | XCTAssertEqual(a - b, Mat2( 19 | ix: 2, jx: 8, 20 | iy: -10, jy: 6 21 | )) 22 | XCTAssertEqual(a * b, Mat2( 23 | ix: 27, jx: 12, 24 | iy: 30, jy: 39 25 | )) 26 | XCTAssertEqual(Mat2( 27 | ix: 1, jx: 1, 28 | iy: 1, jy: 0 29 | ).inverse, Mat2( 30 | ix: 0, jx: 1, 31 | iy: 1, jy: -1 32 | )) 33 | XCTAssertEqual(a * Vec2(x: 4, y: 2), Vec2(x: 24, y: -6)) 34 | 35 | XCTAssertEqual(a.asMatrix, Matrix([ 36 | [3, 6], 37 | [-6, 9] 38 | ])) 39 | XCTAssertEqual(a.asMatrix + b.asMatrix, (a + b).asMatrix) 40 | XCTAssertEqual(a.asMatrix - b.asMatrix, (a - b).asMatrix) 41 | XCTAssertEqual(a.asMatrix * b.asMatrix, (a * b).asMatrix) 42 | XCTAssertEqual("\(a.asMatrix)", "((3, 6), (-6, 9))") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Numerics/MathUtilsTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class MathUtilsTests: XCTestCase { 5 | func testLog2Floor() throws { 6 | XCTAssertEqual(UInt(1).log2Floor(), 0) 7 | XCTAssertEqual(UInt(2).log2Floor(), 1) 8 | XCTAssertEqual(UInt(3).log2Floor(), 1) 9 | XCTAssertEqual(UInt(4).log2Floor(), 2) 10 | XCTAssertEqual(UInt(5).log2Floor(), 2) 11 | XCTAssertEqual(UInt(6).log2Floor(), 2) 12 | XCTAssertEqual(UInt(7).log2Floor(), 2) 13 | XCTAssertEqual(UInt(8).log2Floor(), 3) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Numerics/MatrixTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | fileprivate let eps = 0.0001 5 | 6 | final class MatrixTests: XCTestCase { 7 | func testMinor() throws { 8 | XCTAssertEqual(Matrix([ 9 | [4, 5, 6], 10 | [7, 8, 9], 11 | [1, 2, 3] 12 | ]).minor(0, 0), Matrix([ 13 | [8, 9], 14 | [2, 3] 15 | ])) 16 | XCTAssertEqual(Matrix([ 17 | [4, 5, 6], 18 | [7, 8, 9], 19 | [1, 2, 3] 20 | ]).minor(1, 1), Matrix([ 21 | [4, 6], 22 | [1, 3] 23 | ])) 24 | XCTAssertEqual(Matrix([ 25 | [4, 5, 6], 26 | [7, 8, 9], 27 | [1, 2, 3] 28 | ]).minor(1, 0), Matrix([ 29 | [5, 6], 30 | [2, 3] 31 | ])) 32 | } 33 | 34 | func testDeterminant() throws { 35 | XCTAssertEqual(Matrix([[-7]]).determinant, -7) 36 | XCTAssertEqual(Matrix([ 37 | [4, -29, -8], 38 | [0, -1, 0], 39 | [0, 0, 2] 40 | ]).determinant!.asDouble, -8, accuracy: eps) 41 | XCTAssertEqual(Matrix([ 42 | [4, 5], 43 | [-3, 6] 44 | ]).determinant!, 39, accuracy: eps) 45 | XCTAssertEqual(Matrix([ 46 | [0, 0], 47 | [0, 0] 48 | ]).determinant!, 0, accuracy: eps) 49 | XCTAssertEqual(Matrix([ 50 | [1, 0], 51 | [0, 0] 52 | ]).determinant!, 0, accuracy: eps) 53 | XCTAssertEqual(Matrix([ 54 | [4, 3, 5, 7], 55 | [1, 2, 3, 4], 56 | [3, 3, 3, 3], 57 | [9, 7, 8, 6] 58 | ]).determinant!, 27, accuracy: eps) 59 | XCTAssertEqual(Matrix([ 60 | [4, 3, 5, 7], 61 | [1, 2, 3, 4], 62 | [3, 3, 3, 3], 63 | [9, 7, 8, 6] 64 | ]).laplaceExpansionDeterminant, 27) 65 | } 66 | 67 | func testRowEcholonForm() throws { 68 | assertApproxEqual(Matrix([ 69 | [1, 3, 1], 70 | [1, 1, -1], 71 | [3, 11, 5] 72 | ]).rowEcholonForm!, Matrix([ 73 | [1, 3, 1], 74 | [0, -2, -2], 75 | [0, 0, 0] 76 | ])) 77 | } 78 | 79 | func testInverse() throws { 80 | XCTAssertEqual(Matrix([ 81 | [1, 0, 0], 82 | [0, 2, 0], 83 | [0, 0, 3] 84 | ]).inverse, Matrix([ 85 | [1, 0, 0], 86 | [0, Rational(1, 2), 0], 87 | [0, 0, Rational(1, 3)] 88 | ])) 89 | XCTAssertEqual(Matrix([ 90 | [2, 8], 91 | [8, 4] 92 | ]).inverse, Matrix([ 93 | [-Rational(1, 14), Rational(1, 7)], 94 | [Rational(1, 7), -Rational(1, 28)] 95 | ])) 96 | XCTAssertEqual(Matrix([ 97 | [2, 3, 4], 98 | [8, 9, 5], 99 | [3, 4, 4] 100 | ]).inverse, Matrix([ 101 | [16, 4, -21], 102 | [-17, -4, 22], 103 | [5, 1, -6] 104 | ])) 105 | XCTAssertEqual(Matrix([ 106 | [2, 3, 4, 6], 107 | [8, 9, 5, 3], 108 | [3, 4, 4, -2], 109 | [1, 0, 2, 3] 110 | ]).inverse, Rational(1, 237) * Matrix([ 111 | [-108, 48, -27, 150], 112 | [79, 0, 0, -158], 113 | [15, -33, 63, 45], 114 | [26, 6, -33, -1] 115 | ])) 116 | XCTAssertNil(Matrix([ 117 | [1, 0], 118 | [0, 0] 119 | ]).inverse) 120 | XCTAssertNil(Matrix([ 121 | [1, 2] 122 | ]).inverse) 123 | } 124 | 125 | private func assertApproxEqual(_ a: Matrix, _ b: Matrix) { 126 | XCTAssert(a.width == b.width && a.height == b.height) 127 | for y in 0.. -Rational(1, 10)) 21 | XCTAssert(a > 0) 22 | XCTAssert(Rational(0) >= Rational(0)) 23 | XCTAssertEqual(Rational("45"), Rational(45)) 24 | XCTAssertEqual(Rational("-30"), Rational(-30)) 25 | XCTAssertEqual(Rational("0.25"), Rational(1, 4)) 26 | XCTAssertEqual(Rational("0.1"), Rational(1, 10)) 27 | XCTAssertEqual(Rational("-0.33"), Rational(33, -100)) 28 | XCTAssertEqual(Rational("-1.2"), Rational(-6, 5)) 29 | XCTAssertEqual(Rational("-23/4"), Rational(-23, 4)) 30 | XCTAssertEqual(Rational("23/4"), Rational(23, 4)) 31 | XCTAssertEqual(Rational("-2/-4"), Rational(1, 2)) 32 | XCTAssertEqual(Rational(1, 4), 1 / 4) 33 | XCTAssertEqual(Rational(approximately: 0.25), Rational(1, 4)) 34 | XCTAssertEqual(Rational(approximately: 0.21, accuracy: 10), Rational(1, 5)) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Parsing/TokenIteratorTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class TokenIteratorTests: XCTestCase { 5 | func testTokenIterator() throws { 6 | let fruits = ["Apple", "Orange", "Banana", "Pear", "Lemon", "Grapefruit"] 7 | let iterator = TokenIterator(fruits) 8 | 9 | XCTAssertEqual(iterator.peek(), "Apple") 10 | XCTAssertEqual(iterator.peek(2), "Orange") 11 | XCTAssertEqual(iterator.peek(1), "Apple") 12 | XCTAssertEqual(iterator.next(), "Apple") 13 | XCTAssertEqual(iterator.peek(), "Orange") 14 | XCTAssertEqual(iterator.next(), "Orange") 15 | XCTAssertEqual(iterator.next(), "Banana") 16 | XCTAssertEqual(iterator.next(), "Pear") 17 | XCTAssertEqual(iterator.peek(2), "Grapefruit") 18 | XCTAssertEqual(iterator.peek(), "Lemon") 19 | XCTAssertEqual(iterator.next(), "Lemon") 20 | XCTAssertEqual(iterator.next(), "Grapefruit") 21 | XCTAssertEqual(iterator.peek(), nil) 22 | XCTAssertEqual(iterator.next(), nil) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Tests/UtilsTests/Processes/ShellTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Utils 3 | 4 | final class ShellTests: XCTestCase { 5 | func testShell() { 6 | let output = try! Shell().utf8(for: "echo", args: ["hi"]).wait() 7 | XCTAssertEqual(output, "hi\n") 8 | } 9 | } 10 | --------------------------------------------------------------------------------