在Swift中通过属性对类或结构体的数组进行排序的通用函数

4

我希望创建一个通用函数,基于传递的属性对类数组进行排序。

例如,我有这些类:

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()方法中将属性名称作为字符串传递。然后在您的类中添加以下内容:https://stackoverflow.com/a/24919834/6638533 - samAlvin
1
@samAlvin 他在询问关于Swift的问题,而不是C#。 - Alexander
@Alexander 哎呀,我的错,我以后应该更加小心。 - samAlvin
您是否也对Swift 4的解决方案感兴趣? - Martin R
当然可以,@MartinR。这可能也会对其他人有所帮助。 - avrospirit
3个回答

6

请看下面的内容:

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指出的那样,您可以将这个扩展推广到任何同时也是RandomAccessCollection的MutableCollection:
extension MutableCollection where Self : RandomAccessCollection {
    ...

1
@LeoDabus 谢谢。已编辑。 - Sweeper

4

从Swift 4开始,您可以定义一个排序方法,该方法以键路径表达式作为参数。正如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智能键路径:更好的Swift键值编码


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

4

编辑/更新:

对于Xcode 13.0+,iOS 15.0+,iPadOS 15.0+,macOS 12.0+,Mac Catalyst 15.0+,tvOS 15.0+和watchOS 8.0+,您可以使用KeyPathComparator

let sortedPeople1 = people.sorted(using: KeyPathComparator(\.age))  // [{id 4, name "Rihanna", age 25, female}, {id 1, name "Ed Sheeran", age 26, male}, {id 3, name "Shakira", age 40, female}, {id 5, name "Bono", age 57, male}, {id 2, name "Phil Collins", age 66, male}]
let sortedPeople2 = people.sorted(using: KeyPathComparator(\.age, order: .reverse))  // [{id 2, name "Phil Collins", age 66, male}, {id 5, name "Bono", age 57, male}, {id 3, name "Shakira", age 40, female}, {id 1, name "Ed Sheeran", age 26, male}, {id 4, name "Rihanna", age 25, female}]

您还可以使用多个排序条件和顺序:

let sortedPeople3 = people.sorted(using: [KeyPathComparator(\.age, order: .reverse), KeyPathComparator(\.name)])  // [{id 2, name "Phil Collins", age 66, male}, {id 5, name "Bono", age 57, male}, {id 3, name "Shakira", age 40, female}, {id 1, name "Ed Sheeran", age 26, male}, {id 4, name "Rihanna", age 25, female}]
let sortedPeople4 = people.sorted(using: [KeyPathComparator(\.age, order: .reverse), KeyPathComparator(\.name)])  // [{id 2, name "Phil Collins", age 66, male}, {id 5, name "Bono", age 57, male}, {id 3, name "Shakira", age 40, female}, {id 1, name "Ed Sheeran", age 26, male}, {id 4, name "Rihanna", age 25, female}]

扩展 @MartinR 答案 和 @Sweeper 答案,允许增加(<)或减少(>)排序,以及抛出和默认排序升序方法:
extension MutableCollection where Self: RandomAccessCollection {
    mutating func sort<T: Comparable>(_ predicate: (Element) throws -> T) rethrows {
        try sort(predicate, by: <)
    }
    mutating func sort<T: Comparable>(_ predicate: (Element) throws -> T, by areInIncreasingOrder: ((T, T) throws -> Bool)) rethrows {
        try sort { try areInIncreasingOrder(predicate($0), predicate($1)) }
    }
}

extension Sequence {
    func sorted<T: Comparable>(_ predicate: (Element) throws -> T) rethrows -> [Element] {
        try sorted(predicate, by: <)
    }
    func sorted<T: Comparable>(_ predicate: (Element) throws -> T, by areInIncreasingOrder: ((T,T) throws -> Bool)) rethrows -> [Element] {
        try sorted { try areInIncreasingOrder(predicate($0), predicate($1)) }
    }
}

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>(_ predicate: (Element) throws -> T?) rethrows {
        try sort(predicate, by: <)
    }

    mutating func sort<T: Comparable>(_ predicate: (Element) throws -> T?, by areInIncreasingOrder: ((T, T) throws -> Bool)) rethrows {
        try sort(by: {
            switch try (predicate($0), predicate($1)) {
            case let (lhs?, rhs?): return try areInIncreasingOrder(lhs, rhs)
            case (.none, _): return false
            case (_, .none): return true
            }
        })
    }
}

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

使用方法:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接