Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//: ## Demo
struct User: CustomStringConvertible {
let name: String
let age: Int
var description: String { "\(name) (\(age))" }
}
let users = [
User(name: "Bob", age: 22),
User(name: "Alice", age: 11),
User(name: "Bob", age: 12),
User(name: "Camille", age: 18),
User(name: "Bob", age: 16),
User(name: "Anthony", age: 8),
]
// Sort by name, ascending
users.sorted(by: \.name)
// Sort by age, descending
users.sorted(by: \.age, .descending)
// Sort by name ascending, then by age descending
users.sorted(by: .ascending(\.name), .descending(\.age))
// Combine SortDescriptors
let sortByName = SortDescriptor.ascending(\User.name)
let oldestFirst = SortDescriptor.descending(\User.age)
let mySort = sortByName.then(oldestFirst)
let mySort2 = SortDescriptor([sortByName, oldestFirst]) // strictly equivalent to the above
let mySort3 = SortDescriptor<User>(.ascending(\.name), .descending(\.age)) // likewise
users.sorted(by: mySort)
mySort.apply(to: users) // strictly equivalent to the above
// Thanks to callAsFunction / SE-0253
#if swift(>=5.2)
sortByName(users)
mySort(users)
#endif
// MARK: SortDescriptor
struct SortDescriptor<Element> {
let compare: (Element, Element) -> Direction?
enum Direction {
case ascending
case descending
func inverted() -> SortDescriptor.Direction {
self == .ascending ? .descending : .ascending
}
}
}
extension SortDescriptor {
init<T: Comparable>(_ lens: @escaping (Element) -> T, direction: Direction = .ascending) {
self.compare = { lhs, rhs in
let (ll,lr) = (lens(lhs), lens(rhs))
if ll < lr { return direction }
if ll > lr { return direction.inverted() }
return nil
}
}
// Note: We also get that for free in Swift 5.2 thanks to SE-249
// (though explicit declaration allows to also use non-literal KeyPaths)
init<T: Comparable>(_ keyPath: KeyPath<Element,T>, direction: Direction = .ascending) {
self.init({ $0[keyPath: keyPath] }, direction: direction)
}
}
// Convenience constructors
extension SortDescriptor {
static func ascending<T: Comparable>(_ keyPath: KeyPath<Element,T>) -> SortDescriptor<Element> {
SortDescriptor(keyPath, direction: .ascending)
}
static func descending<T: Comparable>(_ keyPath: KeyPath<Element,T>) -> SortDescriptor<Element> {
SortDescriptor(keyPath, direction: .descending)
}
}
// Combine SortDescriptors
extension SortDescriptor {
init<C: Collection>(_ descriptors: C) where C.Element == SortDescriptor {
self.compare = { lhs, rhs in
for desc in descriptors {
if let res = desc.compare(lhs, rhs) { return res }
}
return nil
}
}
init(_ descriptors: SortDescriptor...) {
self.init(descriptors) // transform varargs to array and call above function
}
func then(_ other: SortDescriptor) -> SortDescriptor {
SortDescriptor([self, other])
}
}
// apply(to:) / SE-0253 callAsFunction support
extension SortDescriptor {
func apply<C: Collection>(to list: C) -> [Element] where C.Element == Element {
return list.sorted(by: self)
}
#if swift(>=5.2)
// Support for SE-0253 in Swift 5.2
func callAsFunction<C: Collection>(_ list: C) -> [Element] where C.Element == Element {
return self.apply(to: list)
}
#endif
}
// MARK: Sorting collections
extension Collection {
typealias SortDirection = SortDescriptor<Element>.Direction
// MARK: Convenience methods
func sorted<T: Comparable>(by closure: (Element) -> T, _ direction: SortDirection = .ascending) -> [Element] {
withoutActuallyEscaping(closure) {
sorted(by: SortDescriptor($0, direction: direction))
}
}
// Note: We also get that for free in Swift 5.2 thanks to SE-249
// (though explicit declaration allows to also use non-literal KeyPaths)
func sorted<T: Comparable>(by keyPath: KeyPath<Element,T>, _ direction: SortDirection = .ascending) -> [Element] {
sorted(by: SortDescriptor(keyPath, direction: direction))
}
func sorted(by descriptors: SortDescriptor<Element>...) -> [Element] {
sorted(by: descriptors) // convert varargs to array and call the one with array
}
func sorted(by descriptors: [SortDescriptor<Element>]) -> [Element] {
return sorted(by: SortDescriptor(descriptors))
}
// MARK: Main logic
func sorted(by descriptor: SortDescriptor<Element>) -> [Element] {
return sorted(by: { (lhs: Element, rhs: Element) -> Bool in
descriptor.compare(lhs, rhs) == .ascending
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment