深入研究功能性编程和整体快速我被多种做事方式所淹没。在这种情况下,我希望<c:set>
采用struct
,但可以有条件地切换重载运算符中正在使用的属性。
我们说我有以下内容,一个快速排序(来自Niv Yahel的Wenderlich FP教程),扩展任何类似的数组,这将轻松容纳Comparable
Collection
Student
小号
struct Student {
let name: String
let age: Int
let grades: Double
}
extension Student: Comparable {
static func <(lhs: Student, rhs: Student) -> Bool {
return lhs.grades < rhs.grades
}
static func ==(lhs: Student, rhs: Student) -> Bool {
return lhs.grades == rhs.grades
}
}
extension Array where Element: Comparable {
func quickSorted() -> [Element] {
if self.count > 1 {
let (pivot, remaining) = (self[0], dropFirst())
let lhs = remaining.filter{ $0 <= pivot }
let rhs = remaining.filter{ $0 > pivot }
return lhs.quickSorted() as [Element] + pivot + rhs.quickSorted()
}
return self
}
}
}
//Omitted, create a bunch of Students
//let bingoLittle = Student(name: "BingoLittle", age: 23, grades: 93.4)
let myStudentDirectory = [bingoLittle, studentB, ... StudentN]
let sortedStudentDirectory = myStudentDirectory.quickSorted()
但是,我想要的下一步是动态决定property
结构将按名称,等级或年龄排序,最好不必触及这个漂亮的快速排序功能。
quickSorted(by: .name)
的quickSorted吗?它似乎不再适用于数组扩展。答案 0 :(得分:0)
有几种方法可以解决这个问题:
1)使用本机排序函数,它允许您为比较指定闭包,从而提供更大的灵活性,并且不要求您的结构是可比较的:
let sortedStudentDirectory = myStudentDirectory.sorted{ $0.grade < $1.grade }
//
// This would be my recommendation given that it is standard and
// it is unlikely that the quicksorted() method would outperform it.
//
2)修改quicksorted()函数以使其与闭包一起使用:
extension Array
{
func quickSorted<T:Comparable>(_ property:@escaping (Element)->T) -> [Element]
{
guard self.count > 1 else { return self }
let (pivot, remaining) = (property(self[0]), dropFirst())
let lhs = remaining.filter{ property($0) <= pivot }
let rhs = remaining.filter{ property($0) > pivot }
return lhs.quickSorted(property) as [Element] + self[0] + rhs.quickSorted(property)
}
}
let sortedStudentDirectory = myStudentDirectory.quickSorted{$0.grades}
// this one also avoids making the struct Comparable.
// you could implement it like the standard sort with a comparison
// closure instead of merely a property accessor so that descending
// sort order and multi-field sorting can be supported.
3)向Student结构添加一个静态变量,告诉比较运算符使用哪个字段并在使用quicksorted()函数之前设置该静态变量
struct Student
{
enum SortOrder { case name, age, grades }
static var sortOrder = .grades
let name: String
let age: Int
let grades: Double
}
extension Student: Comparable
{
static func <(lhs: Student, rhs: Student) -> Bool
{
switch Student.sortOrder
{
case .grades : return lhs.grades < rhs.grades
case .age : return lhs.age < rhs.age
default : return lhs.name < rhs.name
}
}
static func ==(lhs: Student, rhs: Student) -> Bool
{
switch Student.sortOrder
{
case .grades : return lhs.grades == rhs.grades
case .age : return lhs.age == rhs.age
default : return lhs.name == rhs.name
}
}
}
Student.sortOrder = .grades
let sortedStudentDirectory = myStudentDirectory.quickSorted()
这最后一个非常糟糕并且容易出错,因为它会影响结构上可能无意对其进行排序的其他比较操作(特别是对于==运算符)。它也不是线程安全的。
答案 1 :(得分:0)
有几点想法:
首先,Student
不应该是Comparable
。这没有必要,而且在概念上令人困惑。
正如Alain所指出的那样,sorted
方法通过提供基于块的再现Comparable
来支持对本质上不是sorted(by:)
的数组进行排序:
let sortedStudents = students.sorted { $0.age < $1.age }
您可以在quickSorted
方法中使用完全相同的模式:
extension Array {
func quickSorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] {
guard count > 1 else { return self }
let (pivot, remaining) = (self[0], dropFirst())
let (lhs, rhs) = remaining.reduce(into: ([Element](), [Element]())) { result, element in
if areInIncreasingOrder(element, pivot) {
result.0.append(element)
} else {
result.1.append(element)
}
}
return lhs.quickSorted(by: areInIncreasingOrder) as [Element] + [pivot] + rhs.quickSorted(by: areInIncreasingOrder)
}
}
然后你可以这样做:
let sortedStudents = students.quickSorted { $0.age < $1.age }
此处与您的问题无关,但您方法中的两个filter
调用似乎效率低下,因此我将其替换为单个reduce
。 (a)避免循环每个数组两次; (b)如果比较算法复杂,这可能会特别成问题。
话虽如此,sorted(by:)
中的内容比quickSorted(by:)
快得多,所以你可能会坚持下去。
但我认为这更像是一个理论问题“我怎么......?”而不是“什么是最好的排序方式?”。如果是这种情况,这种封闭模式是处理这些情况的好方法。
如果您绝对需要一个排序方法来获取正在排序的字段的参数,那么在Swift 4中您可以使用KeyPath
的通用函数:
extension Array {
func quickSorted<T: Comparable>(on keyPath: KeyPath<Element, T>) -> [Element] {
guard count > 1 else { return self }
let (pivot, remaining) = (self[0], dropFirst())
let (lhs, rhs) = remaining.reduce(into: ([Element](), [Element]())) { result, element in
if element[keyPath: keyPath] < pivot[keyPath: keyPath] {
result.0.append(element)
} else {
result.1.append(element)
}
}
return lhs.quickSorted(on: keyPath) as [Element] + [pivot] + rhs.quickSorted(on: keyPath)
}
}
然后你可以这样做:
let sortedStudents = students.quickSorted(on: \.grades)
就个人而言,我坚持使用封闭模式:它更快更灵活(例如,您可以按降序排序,您可以进行复杂的比较,比较多个属性,例如按年龄,然后按名称等) 。)而不是keypath方法,但如果你真的觉得有必要通过一个属性,那就是你可能会这样做的。