使用部分键路径数组的问题在于您无法保证属性类型是Comparable
. 一种可能的解决方案是使用类型擦除包装器来擦除键路径的值类型,同时确保它是Comparable
:
struct PartialComparableKeyPath<Root> {
private let _isEqual: (Root, Root) -> Bool
private let _isLessThan: (Root, Root) -> Bool
init<Value : Comparable>(_ keyPath: KeyPath<Root, Value>) {
self._isEqual = { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
self._isLessThan = { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
func isEqual(_ lhs: Root, _ rhs: Root) -> Bool {
return _isEqual(lhs, rhs)
}
func isLessThan(_ lhs: Root, _ rhs: Root) -> Bool {
return _isLessThan(lhs, rhs)
}
}
然后你可以实现你的排序功能:
extension Sequence {
func sorted(by keyPaths: PartialComparableKeyPath<Element>...) -> [Element] {
return sorted { lhs, rhs in
for keyPath in keyPaths {
if !keyPath.isEqual(lhs, rhs) {
return keyPath.isLessThan(lhs, rhs)
}
}
return false
}
}
}
然后像这样使用:
struct Stuff {
let value: Int
let value2: Int
let doubleValue: Double
}
var stuff = [Stuff]()
for _ in 1 ... 20 {
stuff.append(Stuff(value: Int(arc4random_uniform(5)),
value2: Int(arc4random_uniform(5)),
doubleValue: Double(arc4random_uniform(10))))
}
let sortedStuff = stuff.sorted(by: PartialComparableKeyPath(\.value),
PartialComparableKeyPath(\.value2))
sortedStuff.forEach { print($0) }
let moreSortedStuff = stuff.sorted(by: PartialComparableKeyPath(\.value),
PartialComparableKeyPath(\.doubleValue))
moreSortedStuff.forEach { print($0) }
虽然不幸的是,这需要将每个单独的键路径包装在一个PartialComparableKeyPath
值中,以便捕获和擦除键路径的值类型,这并不是特别漂亮。
我们真正需要的特性是variadic generics,它可以让你在可变数量的通用占位符上定义你的函数,用于你的键路径的值类型,每个都被限制为Comparable
.
在那之前,另一种选择是只为不同数量的键路径编写给定数量的重载以进行比较:
extension Sequence {
func sorted<A : Comparable>(by keyPathA: KeyPath<Element, A>) -> [Element] {
return sorted { lhs, rhs in
lhs[keyPath: keyPathA] < rhs[keyPath: keyPathA]
}
}
func sorted<A : Comparable, B : Comparable>
(by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>) -> [Element] {
return sorted { lhs, rhs in
(lhs[keyPath: keyPathA], lhs[keyPath: keyPathB]) <
(rhs[keyPath: keyPathA], rhs[keyPath: keyPathB])
}
}
func sorted<A : Comparable, B : Comparable, C : Comparable>
(by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>) -> [Element] {
return sorted { lhs, rhs in
(lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC]) <
(rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC])
}
}
func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable>
(by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>) -> [Element] {
return sorted { lhs, rhs in
(lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD]) <
(rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD])
}
}
func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable, E : Comparable>
(by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>, _ keyPathE: KeyPath<Element, E>) -> [Element] {
return sorted { lhs, rhs in
(lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD], lhs[keyPath: keyPathE]) <
(rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD], rhs[keyPath: keyPathE])
}
}
func sorted<A : Comparable, B : Comparable, C : Comparable, D : Comparable, E : Comparable, F : Comparable>
(by keyPathA: KeyPath<Element, A>, _ keyPathB: KeyPath<Element, B>, _ keyPathC: KeyPath<Element, C>, _ keyPathD: KeyPath<Element, D>, _ keyPathE: KeyPath<Element, E>, _ keyPathF: KeyPath<Element, F>) -> [Element] {
return sorted { lhs, rhs in
(lhs[keyPath: keyPathA], lhs[keyPath: keyPathB], lhs[keyPath: keyPathC], lhs[keyPath: keyPathD], lhs[keyPath: keyPathE], lhs[keyPath: keyPathF]) <
(rhs[keyPath: keyPathA], rhs[keyPath: keyPathB], rhs[keyPath: keyPathC], rhs[keyPath: keyPathD], rhs[keyPath: keyPathE], rhs[keyPath: keyPathF])
}
}
}
我已经为它们定义了最多 6 个键路径,这对于大多数排序情况来说应该足够了。我们正在利用这里的字典元组比较重载<
,如这里所示。
虽然实现并不漂亮,但调用站点现在看起来好多了,因为它可以让您说:
let sortedStuff = stuff.sorted(by: \.value, \.value2)
sortedStuff.forEach { print($0) }
let moreSortedStuff = stuff.sorted(by: \.value, \.doubleValue)
moreSortedStuff.forEach { print($0) }