Swift中四种基本排序方法
- 不可变版本的 sorted(by:)
- 可变的 sort(by:)
- 两者在待排序对象遵守 Comparable 时进行升序排序的重载方法
- 提供不同于默认升序的顺序的排序函数进行排序
第一种:
let myArray1 = [3, 1, 2]
myArray1.sorted() // [1, 2, 3]
第二种:
var myArray2 = [3, 1, 2]
myArray2.sort() // [1, 2, 3]
第三种:
struct Person: Comparable {
let first: String
let last: String
static func < (lhs: Person, rhs: Person) -> Bool {
return lhs.last < rhs.last
}
}
let myArray3 = [
Person(first: "Coco", last: "Smith"),
Person(first: "Bobo", last: "Williams"),
Person(first: "Alice", last: "Brown")
]
print(myArray3.sorted())
第四种:
let myArray4 = [3, 1, 2]
myArray4.sorted(by: >) // [3, 2, 1]
自定义排序规则
在一些时候,我们可能会对一个类或者结构体排序,需要一个自定义的排序规则(即排序的优先处理方式)。上面的那种基本的排序方式就不能满足我们的需求了。
如果你对Objective-C熟悉,这时候会想到使用NSArray
的sortedArray(using:)
方法。这个方法可以接受一系列排序描述符。为了确定两个元素的顺序,它会先使用第一个描述符,并检查其结果。如果两个元素在第一个描述符下相同,那么它将使用第二个描述符,以此类推。排序描述符NSSortDescriptor
用到了 Objective-C 的两个运行时特性:首先是键路径 key 以及键值编程 (key-value-coding)。我们可以通过键值编程的方式在运行时通过键查找一个对象上的对应值。其次是 selector 参数,它接受一个 selector (实际上就是一个用来描述方法名字的 String),在运行时,这个 selector 将被转变为用来比较两个对象的函数,对象上指定键所对应的值将被用来进行比较。这是运行时编程的一个很酷的用例,排序描述符的数组可以在运行时构建,这一点在实现比如用户点击某一列时按照该列进行排序这种需求时会特别有用。
既然是用的是Swift,我们是否够可以在不用运行时的特性,而使用一种类型安全的方式去复制上述OC的这种功能呢?下面就使用Swift的特性来实现吧。
step 1:定义一个函数来描述对象的顺序
typealias SortDescriptor<Value> = (Value, Value) -> Bool
step 2:定义一个函数在不涉及运行时的情况下模仿NSSortDescriptor
func sortDescriptor<Value, Key>(
key: @escaping (Value) -> Key,
_ areInIncreasingOrder: @escaping (Key, Key) -> Bool)
-> SortDescriptor<Value>
{
return { areInIncreasingOrder(key($0), key($1)) }
}
// 还可以为所有的 Comparable 类型定义一个重载版本的函数
func sortDescriptor<Value, Key>(key: @escaping (Value) -> Key)
-> SortDescriptor<Value> where Key: Comparable
{
return { key($0) < key($1) }
}
// 还可以增加对类似 localizedCaseInsensitiveCompare 这样的比较函数的支持
func sortDescriptor<Value, Key>(
key: @escaping (Value) -> Key,
ascending: Bool = true,
_ comparator: @escaping (Key) -> (Key) -> ComparisonResult)
-> SortDescriptor<Value>
{
return { lhs, rhs in
let order: ComparisonResult = ascending
? .orderedAscending
: .orderedDescending
return comparator(key(lhs))(key(rhs)) == order
}
}
step 3:定义一个函数模仿NSArray.sortedArray(using:)
方法来用一组比较运算符对数组进行排序。这样,我们可以很容易地为 Array,甚至是 Sequence 协议添加一个相似的方法。
extension Array {
func sortedArray(using sortDescriptors: [SortDescriptor<Element>]) -> Array {
let combined = combine(sortDescriptors: sortDescriptors)
return sorted(by: combined)
}
mutating func sortedArray(using sortDescriptors: [SortDescriptor<Element>]) {
let combined = combine(sortDescriptors: sortDescriptors)
sort(by: combined)
}
private func combine<Value>
(sortDescriptors: [SortDescriptor<Value>]) -> SortDescriptor<Value> {
return { lhs, rhs in
for areInIncreasingOrder in sortDescriptors {
if areInIncreasingOrder(lhs,rhs) { return true }
if areInIncreasingOrder(rhs,lhs) { return false }
}
return false
}
}
}
Example:
struct Person {
let first: String
let last: String
let yearOfBirth: Int
}
let people = [
Person(first: "Jo", last: "Smith", yearOfBirth: 1970),
Person(first: "Joe", last: "Smith", yearOfBirth: 1970),
Person(first: "Joe", last: "Smyth", yearOfBirth: 1970),
Person(first: "Joanne", last: "smith", yearOfBirth: 1985),
Person(first: "Joanne", last: "smith", yearOfBirth: 1970),
Person(first: "Robert", last: "Jones", yearOfBirth: 1970),
]
let sortByLastName: SortDescriptor<Person> = {
$0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending
}
let sortByFirstName: SortDescriptor<Person> =
sortDescriptor(key: { $0.first }, String.localizedCaseInsensitiveCompare)
let sortByYear: SortDescriptor<Person> = { $0.yearOfBirth < $1.yearOfBirth }
people.sortedArray(using: [sortByLastName,sortByFirstName,sortByYear])
/*
[
Robert Jones (1970),
Jo Smith (1970),
Joanne smith (1970),
Joanne smith (1985),
Joe Smith (1970),
Joe Smyth (1970)
]
*/
最终:我们得到了一个与 Foundation 中的版本在行为和功能上等价的实现方法,但是这个方法要更安全,也更符合 Swift 的语言习惯。因为 Swift 的版本不依赖于运行时,所以编译器有机会对它进行更好的优化。另外,我们也可以使用它来对结构体或是非 Objective-C 的对象进行排序。这种方式的实质是将函数用作数据,我们将这些函数存储在数组里,并在运行时构建这个数组。这将动态特性带到了一个新的高度,这也是像 Swift 这样的编译时就确定了静态类型的语言仍然能实现像是 Objective-C 或者 Ruby 的部分动态行为的一种方式。我们也看到了合并其他函数的函数的用武之地。比如,combine(sortDescriptors:) 函数接受一个排序描述符的数组,并将它们合并成了单个的排序描述符。在很多不同的应用场景下,这项技术都非常强大。
参考:《Swift 进阶》
网友评论