-
-
Save seanwoodward/44e2f5aca0dda994e9595eee58750f65 to your computer and use it in GitHub Desktop.
#if swift(>=5.9) | |
import Foundation | |
public struct Tuple<T, each U> { | |
private let head: T | |
private let tail: (repeat (each U)) | |
public var values: (T, repeat each U) { | |
(head, repeat each tail) | |
} | |
public init(_ head: T, _ tail: repeat each U) { | |
self.head = head | |
self.tail = (repeat (each tail)) | |
} | |
} | |
extension Tuple: Sendable where T: Sendable, repeat (each U): Sendable {} | |
extension Tuple: Hashable where T: Hashable, repeat (each U): Hashable { | |
public func hash(into hasher: inout Hasher) { | |
hasher.combine(self.head) | |
repeat (hasher.combine(each self.tail)) | |
} | |
} | |
extension Tuple: Equatable where T: Equatable, repeat (each U): Equatable { | |
private struct NotEqual: Error {} | |
public static func == (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool { | |
guard lhs.head == rhs.head else { return false } | |
func isEqual<V: Equatable>(_ left: V, _ right: V) throws { | |
guard left == right else { throw NotEqual() } | |
return | |
} | |
do { | |
repeat try isEqual(each lhs.tail, each rhs.tail) | |
} catch { | |
return false | |
} | |
return true | |
} | |
} | |
extension Tuple: Comparable where T: Comparable, repeat (each U): Comparable { | |
// https://github.com/apple/swift-evolution/blob/1b0b339bc3072a83b5a6a529ae405a0f076c7d5d/proposals/0015-tuple-comparison-operators.md | |
private struct NotOrderBefore: Error { } | |
// throwing short circuits the checks | |
public static func < (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool { | |
func isOrderedBeforeThrows<V: Comparable>(_ left: V, _ right: V) throws { | |
if left != right { | |
guard left < right else { throw NotOrderBefore() } | |
} | |
} | |
do { | |
try isOrderedBeforeThrows(lhs.head, rhs.head) | |
repeat try isOrderedBeforeThrows(each lhs.tail, each rhs.tail) | |
} catch { | |
return false | |
} | |
return true | |
} | |
// exhaustive | |
// public static func < (lhs: Tuple<T, repeat (each U)>, rhs: Tuple<T, repeat (each U)>) -> Bool { | |
// var results: [(notEqual: Bool, orderedBefore: Bool)] = [ (lhs.head != rhs.head, lhs.head < rhs.head)] | |
// | |
// func isOrderedBefore<V: Comparable>(_ left: V, _ right: V) { | |
// results.append((left != right, left < right)) | |
// } | |
// | |
// repeat isOrderedBefore(each lhs.tail, each rhs.tail) | |
// | |
// return results.first(where: { $0.notEqual})?.orderedBefore ?? results.last!.orderedBefore | |
// } | |
} | |
extension Tuple: CustomStringConvertible { | |
public var description: String { | |
func describe<V>(_ value: V) { | |
if let _ = value as? any StringProtocol { | |
let valueString = String(describing: value) | |
// allows the presentation of a string value with quotes | |
// e.g head = "red", the description will be "red" with quotes | |
// not just, red, without quotes. | |
descriptions.append("\(valueString.debugDescription)") | |
} else { | |
descriptions.append("\(String(describing: value))") | |
} | |
} | |
var descriptions: [String] = [] | |
describe(self.head) | |
repeat (describe(each tail)) | |
return descriptions.count == 1 ? "\(descriptions[0])" : "(\(descriptions.joined(separator: ", ")))" | |
} | |
} | |
extension Tuple: CustomDebugStringConvertible { | |
public var debugDescription: String { | |
"values = \(self.description)" | |
} | |
} | |
#endif |
@randomeizer, use it as an intermediary to allow an ad hoc tuple of values to either be a value in a set, a key in a dictionary, or conform to a protocol like Comparable
. Consider the interview question regarding finding the maximized capital.
func findMaximizedCaptial(_ k: Int, _ w: Int, _ profits: [Int], _ capital: [Int]) -> Int {
let getCapital: (Tuple<Int, Int>) -> Int = { $0.values.0 }
let getProfit: (Tuple<Int, Int>) -> Int = { $0.values.1 }
var maxProfit: Heap<Int> = []
var minCapital: Heap<Tuple<Int, Int>> = .init(zip(capital, profits).map(Tuple.init) )
var w = w
for _ in 1...k {
while !minCapital.isEmpty, let minC = minCapital.min, getCapital(minC) <= w {
maxProfit.insert(getProfit(minCapital.removeMin()))
}
guard !maxProfit.isEmpty else { break }
w += maxProfit.removeMax()
}
return w
}
Here, the zip
function combines the elements from both arrays into a Swift tuple, (profit, capital). While you can use the comparison operator <
with a Swift tuple, it cannot not conform to the Comparable
protocol and cannot be evaluated as an element in a Heap
. Instead of creating a specific struct to handle the combined values, the Tuple
struct can take the values and allow them to be evaluated appropriately.
Are you using Parameter Packs in any interesting ways in your code?
I haven't really looked into exactly what they allow, to be honest. Can it support an arbitrary number of mixed types in the signature, like "real" tuples? Or is U
constrained to a single type?
@randomeizer, yes parameter packs allow for arbitrary mixed types like built-in tuples. U types are not constrained, but they are not dynamic. You could not change an instance of one Tuple(1 as Int, 2.0 as Double)
to an instance of Tuple("John", 490 as Int)
.
Neat. I need to dig into them a bit I think.
How do you actually it use it?