Created
July 20, 2018 00:45
-
-
Save calda/792255d239863ab9652cdd4ae7e42ab1 to your computer and use it in GitHub Desktop.
SortDescriptor.playground
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
// MARK: SortDescriptor | |
/// Type-erased Sort Descriptor (can store multiple in the same array | |
/// regardless of the underlying KeyPath | |
public struct SortDescriptor<Element> { | |
private let comparator: (Any, Any) -> Bool | |
// Initialize the `SortDescriptor` with just a Comparator | |
public init(using comparator: (Element, Element) -> Bool) { | |
self.comparator { anyLhs, anyRhs in | |
// un-erase the (Any, Any) | |
guard let lhs = anyLhs as? Element, | |
let rhs = anyRhs as? Element else | |
{ | |
return false | |
} | |
return comparator(lhs, rhs) | |
} | |
} | |
// For abitrary `Value` types, the consumer has to provide the entire Comparator | |
public init<Value>( | |
_ keyPath: KeyPath<Element, Value>, | |
using comparator: @escaping (Value, Value) -> Bool) | |
{ | |
self.init(using: { lhs, rhs in | |
return comparator(lhs[keyPath: keyPath], rhs[keyPath: keyPath]) | |
}) | |
} | |
// `Comparable` types have all of the pieces we need to build a SortDescriptor straight from a `KeyPath` | |
public init<Value: Comparable>(_ keyPath: KeyPath<Element, Value>) { | |
self.init(keyPath, using: <) | |
} | |
// This initializer specifically lets us use methods like `String.caseInsensitiveCompare` in an ergonomic way. | |
// FIXME: Can we add this in an extension that only gets applied if Foundation is importable? | |
// It may be better to just have a `(Value, Value) -> ComparisonResult` in case something like SE-42 is ever implemented. | |
public init<Value>( | |
_ keyPath: KeyPath<Element, Value>, | |
using curriedComparator: @escaping (Value) -> (Value) -> ComparisonResult) | |
{ | |
self.init(keyPath, using: { lhs, rhs -> Bool in | |
return curriedComparator(lhs)(rhs) == .orderedAscending | |
}) | |
} | |
/// Compares the provided elements using the provided KeyPath and comparison procedure | |
public func orders(_ a: Element, before b: Element) -> Bool { | |
return self.comparator(a, b) | |
} | |
/// Creates a new Sort Descriptor that represents the reverse of this Sort Descriptor | |
public var reversed: SortDescriptor { | |
return SortDescriptor(using: { lhs, rhs in | |
if self.comparator(lhs, rhs) { | |
return false | |
} else if self.comparator(rhs, lhs) { | |
return true | |
} else { | |
// this is the `orderedSame` / `==` case. | |
return false | |
} | |
}) | |
} | |
} | |
// MARK: Collection + SortDescriptor | |
extension Collection { | |
public func sorted<Value: Comparable>(by keyPath: KeyPath<Self.Element, Value>) -> [Self.Element] { | |
return self.sorted(by: SortDescriptor(keyPath)) | |
} | |
public func sorted(by sortDescriptor: SortDescriptor<Self.Element>) -> [Self.Element] { | |
return self.sorted(by: [sortDescriptor]) | |
} | |
public func sorted(by descriptors: [SortDescriptor<Self.Element>]) -> [Self.Element] { | |
return self.sorted(by: { lhs, rhs in | |
for descriptor in descriptors { | |
if descriptor.orders(lhs, before: rhs) { | |
return true | |
} | |
else if descriptor.orders(rhs, before: lhs) { | |
return false | |
} | |
// otherwise, a == b. Fall through to the next descriptor. | |
} | |
return false | |
}) | |
} | |
} | |
// MARK: Examples | |
struct Person { | |
let name: String | |
let age: Int | |
} | |
let people = [ | |
Person(name: "Alice", age: 34), | |
Person(name: "Bob", age: 28), | |
Person(name: "Bob", age: 34), | |
Person(name: "Eve", age: 30)] | |
people.sorted(by: \.name) | |
people.sorted(by: SortDescriptor(\.age).reversed) | |
people.sorted(by: [ | |
SortDescriptor(\.age).reversed, | |
SortDescriptor(\.name, using: String.caseInsensitiveCompare)]) |
Hi, this is great! Thanks for sharing. Could you apply a license like MIT or CC0 so we can use it?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Amazing!