似乎应该可以使用KeyPath数组作为排序键,使用任意数量的排序键对Swift结构数组进行排序。从概念上讲,很简单。将KeyPaths数组定义为泛型对象,其中唯一的限制是键盘中的属性为Comparable。
只要所有的KeyPaths指向相同类型的属性,一切都是好的。但是,一旦尝试使用指向数组中不同类型的元素的KeyPaths,它就会停止工作。
请看下面的代码。我创建了一个具有2个Int属性和一个double属性的简单结构。我创建了一个数组扩展,它实现了一个函数sortedByKeypaths(_:),该函数指定一个可比较的泛型类型属性。它将向指定类型属性属性的某个对象元素提供一个kepath数组。(可比较的属性)
只要您使用KeyPaths数组对所有相同类型的属性调用该函数,它就能完美地工作。
但是,如果尝试将键盘数组传递给不同类型的属性,则会引发“无法将'PartialKeyPath‘类型的值转换为预期的参数类型'KeyPath'”的错误。
由于数组包含不同的键类型,因此由于类型擦除,数组将转到“PartialKeyPath”类型,因此不能使用PartialKeyPath从数组中获取元素。
这个问题有解决办法吗?无法使用异构的KeyPaths数组似乎严重限制了Swift KeyPaths的有用性
import UIKit
struct Stuff {
let value: Int
let value2: Int
let doubleValue: Double
}
extension Array {
func sortedByKeypaths<PROPERTY: Comparable>(_ keypaths: [KeyPath<Element, PROPERTY>]) -> [Element] {
return self.sorted { lhs, rhs in
var keypaths = keypaths
while !keypaths.isEmpty {
let keypath = keypaths.removeFirst()
if lhs[keyPath: keypath] != rhs[keyPath: keypath] {
return lhs[keyPath: keypath] < rhs[keyPath: keypath]
}
}
return true
}
}
}
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.sortedByKeypaths([\Stuff.value, \Stuff.value2]) //This works
sortedStuff.forEach { print($0) }
let moreSortedStuff = stuff.sortedByKeypaths([\Stuff.value, \Stuff.doubleValue]) //This throws a compiler error
moreSortedStuff.forEach { print($0) }发布于 2018-05-02 14:53:32
使用部分键盘数组的问题是,您无法保证属性类型是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值中,以便捕获和擦除键盘的值类型,而这个类型并不特别漂亮。
这里我们真正需要的特性是变种泛型,它将允许您为键类型的值类型定义一个可变数量的通用占位符,每个键类型都受限于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) }https://stackoverflow.com/questions/50136303
复制相似问题