【问题标题】:Generic function to sort array of class or struct by properties in Swift在 Swift 中按属性对类或结构数组进行排序的通用函数
【发布时间】:2018-03-17 20:48:05
【问题描述】:

我想创建一个通用函数来根据传递的属性对类数组进行排序。

例如,我有这些类

public class Car {
    var id: Int
    var manufacturer: String
    var variant: String

    init(id: Int, manufacturer: String, variant: String) {
        self.id = id
        self.manufacturer = manufacturer
        self.variant = variant
    }
}

enum Gender {
    case male
    case female
}

public class Person {
    var id: Int
    var name: String
    var age: Int
    var gender: Gender

    init(id: Int, name: String, age: Int, gender: Gender) {
        self.id = id
        self.name = name
        self.age = age
        self.gender = gender
    }
}

还有这些数组,

let cars = [
    Car(id: 1, manufacturer: "Ford", variant: "Focus"),
    Car(id: 2, manufacturer: "Nissan", variant: "Skyline"),
    Car(id: 3, manufacturer: "Dodge", variant: "Charger"),
    Car(id: 4, manufacturer: "Chevrolet", variant: "Camaro"),
    Car(id: 5, manufacturer: "Ford", variant: "Shelby")
]

let persons = [
    Person(id: 1, name: "Ed Sheeran", age: 26, gender: .male),
    Person(id: 2, name: "Phil Collins", age: 66, gender: .male),
    Person(id: 3, name: "Shakira", age: 40, gender: .female),
    Person(id: 4, name: "Rihanna", age: 25, gender: .female),
    Person(id: 5, name: "Bono", age: 57, gender: .male)
]

如何为数组编写通用扩展,根据传递的属性对其进行排序? (例如,persons.sort(name) 或 cars.sort(manufacturer))

谢谢!

【问题讨论】:

  • 您可以在sort() 方法中将属性名称作为字符串传递。然后将其添加到您的课程中:stackoverflow.com/a/24919834/6638533
  • @samAlvin 他问的是 Swift,而不是 C#。
  • @Alexander 我的错,我以后应该更加小心
  • 您是否也对 Swift 4 解决方案感兴趣?
  • 确定@MartinR。也可以帮助别人。

标签: arrays swift sorting generics


【解决方案1】:

给你:

extension Array {
    mutating func propertySort<T: Comparable>(_ property: (Element) -> T) {
        sort(by: { property($0) < property($1) })
    }
}

用法:

persons.propertySort({$0.name})

这是一个非变异版本:

func propertySorted<T: Comparable>(_ property: (Element) -> T) -> [Element] {
    return sorted(by: {property($0) < property($1)})
}

正如 Leo Dabus 指出的那样,您可以将扩展推广到任何 MutableCollection 也是 RandomAccessCollection

extension MutableCollection where Self : RandomAccessCollection {
    ...

【讨论】:

  • extension MutableCollection where Self: RandomAccessCollection {
  • @LeoDabus 谢谢。已编辑。
【解决方案2】:

从 Swift 4 开始,你可以定义一个排序方法,它需要 Key-Path Expression 作为参数。正如 Leo 所指出的,这些方法可以更一般地定义为协议扩展方法(分别用于可变集合和序列):

extension MutableCollection where Self: RandomAccessCollection {
    // Mutating in-place sort:
    mutating func sort<T: Comparable>(byKeyPath keyPath: KeyPath<Element, T>) {
        sort(by: { $0[keyPath: keyPath] < $1[keyPath: keyPath] })
    }
}

extension Sequence {
    // Non-mutating sort, returning a new array:
    func sorted<T: Comparable>(byKeyPath keyPath: KeyPath<Element, T>) -> [Element] {
        return sorted(by: { $0[keyPath: keyPath] < $1[keyPath: keyPath] })
    }
}

示例用法:

persons.sort(byKeyPath: \.name)
cars.sort(byKeyPath: \.manufacturer)

有关键路径表达式的详细信息,请参阅SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift

【讨论】:

  • 最好扩展MutableCollection 并将Self 约束为RandomAccessCollectionextension MutableCollection where Self: RandomAccessCollection {
  • extension RangeReplaceableCollection { func sorted&lt;T: Comparable&gt;(by keyPath: KeyPath&lt;Element, T&gt;) -&gt; Self { return Self( sorted(by: { $0[keyPath: keyPath] &lt; $1[keyPath: keyPath] }) ) } }
  • 最后一个问题,如果我扩展StringProtocol 并将Self 限制为RangeReplaceableColletion,过滤器方法声明将返回类型更改为Self 声明func filter(_ isIncluded: (Character) throws -&gt; Bool) rethrows -&gt; Self。如果我删除约束,它会抛出“无法返回[Character] 而不是Self。过滤器声明现在显示[Self.Element]。我试图用排序方法实现相同的效果。是否有任何特殊的约束来制作排序方法返回Self 而不是[Self.Element]?我上面的实现是否正确?
  • 我也考虑过下面的做法extension MutableCollection where Self: RandomAccessCollection { func sorted&lt;T: Comparable&gt;(by keyPath: KeyPath&lt;Element, T&gt;) -&gt; Self { var source = self source.sort { $0[keyPath: keyPath] &lt; $1[keyPath: keyPath] } return source } }
  • @LeoDabus:我根据标准库中的现有方法建模了我的建议。只有MutableCollection.sort()Sequence.sorted() -&gt; [Element],但没有MutableCollection.sorted() -&gt; Self 方法。一个可以定义这样的方法(您的第二种方法似乎更好,第一种方法创建一个中间数组)。
【解决方案3】:

扩展@MartinR answer 以允许递增 () 排序:


extension MutableCollection where Self: RandomAccessCollection {
    mutating func sort<T: Comparable>(_ keyPath: KeyPath<Element, T>, by areInIncreasingOrder: ((T, T) -> Bool) = (<)) {
        sort(by: { areInIncreasingOrder($0[keyPath: keyPath], $1[keyPath: keyPath]) })
    }
}

extension Sequence {
    func sorted<T: Comparable>(_ keyPath: KeyPath<Element, T>, by areInIncreasingOrder: ((T,T)-> Bool) = (<)) -> [Element] {
        sorted(by: { areInIncreasingOrder($0[keyPath: keyPath], $1[keyPath: keyPath]) })
    }
}

people.sorted(\.age)
people.sorted(\.age, by: >)

cars.sorted(\.manufacturer)
cars.sorted(\.manufacturer, by: >)

编辑/更新:

支持通过符合Comparable 协议的可选属性对自定义对象进行排序:


extension MutableCollection where Self: RandomAccessCollection {
    mutating func sort<T: Comparable>(_ keyPath: KeyPath<Element, Optional<T>>, by areInIncreasingOrder: ((T, T) -> Bool) = (<)) {
        sort(by: {
            switch ($0[keyPath: keyPath], $1[keyPath: keyPath]) {
            case let (lhs?, rhs?): return areInIncreasingOrder(lhs, rhs)
            case (.none, _): return false
            case (_, .none): return true
            }
        })
    }
}

extension Sequence {
    func sorted<T: Comparable>(_ keyPath: KeyPath<Element, Optional<T>>, by areInIncreasingOrder: ((T,T)-> Bool) = (<)) -> [Element]  {
        sorted(by: {
            switch ($0[keyPath: keyPath], $1[keyPath: keyPath]) {
            case let (lhs?, rhs?): return areInIncreasingOrder(lhs, rhs)
            case (.none, _): return false
            case (_, .none): return true
            }
        })
    }
}

用法:

array.sort(\.optionalStringProperty) {
    $0.localizedStandardCompare($1) == .orderedAscending
}
print(array)

【讨论】:

    猜你喜欢
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    • 2021-08-04
    • 2015-06-13
    • 2015-02-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多