// // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation // A Refinery helps you iteratively assign values to keys. Each iteration provides the keys without values yet and // through successive calls to refine(_:) values are provided by the caller. It's more useful than map when you can // look up a collection of keys at once, such as in a SQL query because you don't need to worry about values that were // resolved in a previous step. // // Example: // // Refinery(phoneNumbers).refine { phoneNumbers in // return NamesForPhoneNumbersFromAddressBook(phoneNumbers) // returns nil if not in address book // }.refine { phoneNumbers in // return NamesForPhoneNumbersFromDatabase(phoneNumbers) // returns nil if not in database // }.refine { phoneNumbers in // return phoneNumbers.map { formatPhoneNumber($0) } // }.values // // The `values` array is parallel to the `keys` the Refinery was initialized with. public class Refinery { // Keys whose values the client wants to compute. public let keys: [Key] // Parallel to `keys`. Each call to `refine` may change some of these from nil to nonnil. private(set) public var values: [Value?] // Indexes in `values` that are nil. private var indexes: IndexSet public init(_ keys: [Key]) { self.keys = keys values = Array(repeating: nil, count: keys.count) indexes = IndexSet(0..(_ closure: (AnySequence) -> Result) -> Self where Result: Sequence, Result.Element == Value? { internalRefine(indexes, closure: closure) return self } // This helps you partition the keys so that they can be refined differently based on some precondition. // Keys meeting the condition are sent to the `then` closure; all others go to `otherwise`. public func refine(condition: (Key) -> Bool, then: (AnySequence) -> Result, otherwise: (AnySequence) -> Result) -> Self where Result: Sequence, Result.Element == Value? { let (matching, nonMatching) = partitionIndexes { condition(keys[$0]) } internalRefine(matching, closure: then) internalRefine(nonMatching, closure: otherwise) return self } // When the key type is optional, use this to operate on only the nonnil keys. public func refineNonnilKeys(_ closure: (AnySequence) -> Result) -> Self where Key == NonNilKey?, Result: Sequence, Result.Element == Value? { var nonNilKeys = [NonNilKey]() var nonNilIndexes = IndexSet() for index in indexes { guard let key = keys[index] else { continue } nonNilKeys.append(key) nonNilIndexes.insert(index) } guard !nonNilIndexes.isEmpty else { return self } let refinedValues = closure(AnySequence(nonNilKeys)) handleResult(indexes: nonNilIndexes, values: refinedValues) return self } // `indexes` gives the indexes of `self.keys` to try to get values for from `closure`. // As a side-effect, it assigns to self.values and removes from self.indexes when a value is assigned. private func internalRefine(_ indexes: IndexSet, closure: (AnySequence) -> Result) where Result: Sequence, Result.Element == Value? { guard !indexes.isEmpty else { return } let refinedValues = closure(keys(at: indexes)) handleResult(indexes: indexes, values: refinedValues) } private func handleResult(indexes: IndexSet, values refinedValues: Result) where Result: Sequence, Result.Element == Value? { for (index, maybeValue) in zip(indexes, refinedValues) { guard let value = maybeValue else { continue } self.indexes.remove(index) values[index] = value } } private func partitionIndexes(_ closure: (Int) -> Bool) -> (IndexSet, IndexSet) { var lhs = IndexSet() var rhs = IndexSet() for index in indexes { if closure(index) { lhs.insert(index) } else { rhs.insert(index) } } return (lhs, rhs) } private func keys(at indexes: IndexSet) -> AnySequence { let keys = self.keys return AnySequence(indexes.lazy.map { keys[$0] }) } } public extension Dictionary { init>(_ refinery: T) { let keysAndValues: [(Key, Value)] = zip(refinery.keys, refinery.values).compactMap { key, value in guard let value = value else { return nil } return (key, value) } self.init(uniqueKeysWithValues: keysAndValues) } }