如何在协议扩展方法实现之间有条件地切换?

时间:2017-10-10 21:29:43

标签: swift protocols swift-protocols

深入研究功能性编程和整体快速我被多种做事方式所淹没。在这种情况下,我希望<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结构将按名称,等级或年龄排序,最好不必触及这个漂亮的快速排序功能。

  1. 应该将quicksort变成通用函数吗?
  2. 我应该查看类型约束吗?
  3. 我是否应该在学生中拥有一个属性,它应该对哪个属性进行排序?看起来很难看。
  4. 我应该使用类似quickSorted(by: .name)的quickSorted吗?它似乎不再适用于数组扩展。

2 个答案:

答案 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)

有几点想法:

  1. 首先,Student不应该是Comparable。这没有必要,而且在概念上令人困惑。

  2. 正如Alain所指出的那样,sorted方法通过提供基于块的再现Comparable来支持对本质上不是sorted(by:)的数组进行排序:

    let sortedStudents = students.sorted { $0.age < $1.age }
    
  3. 您可以在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:)快得多,所以你可能会坚持下去。

    但我认为这更像是一个理论问题“我怎么......?”而不是“什么是最好的排序方式?”。如果是这种情况,这种封闭模式是处理这些情况的好方法。

  4. 如果您绝对需要一个排序方法来获取正在排序的字段的参数,那么在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)
    
  5. 就个人而言,我坚持使用封闭模式:它更快更灵活(例如,您可以按降序排序,您可以进行复杂的比较,比较多个属性,例如按年龄,然后按名称等) 。)而不是keypath方法,但如果你真的觉得有必要通过一个属性,那就是你可能会这样做的。