I want to create a generic function to sort an array of classes based on a property passed.
For example, I have these classes
public class Car {
Here you go:
extension Array {
mutating func propertySort<T: Comparable>(_ property: (Element) -> T) {
sort(by: { property($0) < property($1) })
}
}
Usage:
persons.propertySort({$0.name})
And here is a non-mutating version:
func propertySorted<T: Comparable>(_ property: (Element) -> T) -> [Element] {
return sorted(by: {property($0) < property($1)})
}
As Leo Dabus pointed out, you can generalise the extension to any MutableCollection that is also a RandomAccessCollection:
extension MutableCollection where Self : RandomAccessCollection {
...
Expanding on @MartinR answer to allow increasing (<) or decreasing (>) sort:
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: >)
edit/update:
To suport sorting a custom object by an optional property that conforms to Comparable protocol:
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
}
})
}
}
Usage:
array.sort(\.optionalStringProperty) {
$0.localizedStandardCompare($1) == .orderedAscending
}
print(array)
Starting with Swift 4 you can define a sorting method which takes a Key-Path Expression as argument. As Leo points out, these methods can be defined more generally as protocols extension methods (for mutable collections and sequences, respectively):
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] })
}
}
Example usage:
persons.sort(byKeyPath: \.name)
cars.sort(byKeyPath: \.manufacturer)
For more information about key-path expressions, see SE-0161 Smart KeyPaths: Better Key-Value Coding for Swift.