Dynamically passing closure with keypaths to a sorting function - ios

I have this method that I use for sorting based on object properties:
extension Sequence {
mutating func sorted(
by predicates: [(Element, Element) -> Bool]
) -> [Element] {
return sorted(by:) { lhs, rhs in
for predicate in predicates {
if predicate(lhs, rhs) { return true }
if predicate(rhs, lhs) { return false }
}
return false
}
}
}
And I can use it like this on myArray of a MyClass type:
myArray.sorted(by: [{$0.propertyA > $1.propertyA}, {$0.propertyB > $1.propertyB}, {$0.propertyC < $1.propertyC}])
but I would like to build these predicates dynamically, so that properties used for sorting are not predefined.
I guess I should be using keyPaths (to store something like KeyPath<MyModel, MyComparableType>, but I had no luck with that.
How would I pass correct operator (less than, bigger than) along with property I want to use for sorting?

You can simply pass a predicate to return a comparable property from the element of a sequence and another one to check if both elements are in increasing other:
extension Sequence {
public func sorted<T: Comparable>(
_ predicate: (Element) throws -> T, by areInIncreasingOrder: (T, T) throws -> Bool
) rethrows -> [Element] {
try sorted { try areInIncreasingOrder(predicate($0), predicate($1)) }
}
func sorted<T: Comparable>(_ predicate: (Element) throws -> T) rethrows -> [Element] {
try sorted(predicate, by: <)
}
}
extension MutableCollection where Self: RandomAccessCollection {
public mutating func sort<T: Comparable>(
_ predicate: (Element) throws -> T, by areInIncreasingOrder: (T, T) throws -> Bool
) rethrows {
try sort { try areInIncreasingOrder(predicate($0), predicate($1)) }
}
public mutating func sort<T: Comparable>(_ predicate: (Element) throws -> T) rethrows {
try sort(predicate, by: <)
}
}
To suport multiple criteria (secondary, tertiary, and quaternary) you just need to add more generic types to your method:
extension Sequence {
public func sorted<T: Comparable, U: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool)
) rethrows -> [Element] {
try sorted {
let lhs = try primary.0($0)
let rhs = try primary.0($1)
guard lhs == rhs else {
return try primary.1(lhs, rhs)
}
return try secondary.1(secondary.0($0), secondary.0($1))
}
}
public func sorted<T: Comparable, U: Comparable, V: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool),
_ terciary: ((Element) throws -> V, (V, V) throws -> Bool)
) rethrows -> [Element] {
try sorted {
let lhs1 = try primary.0($0)
let rhs1 = try primary.0($1)
guard lhs1 == rhs1 else {
return try primary.1(lhs1, rhs1)
}
let lhs2 = try secondary.0($0)
let rhs2 = try secondary.0($1)
guard lhs2 == rhs2 else {
return try secondary.1(lhs2, rhs2)
}
return try terciary.1(terciary.0($0), terciary.0($1))
}
}
public func sorted<T: Comparable, U: Comparable, V: Comparable, W: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool),
_ terciary: ((Element) throws -> V, (V, V) throws -> Bool),
_ quaternary: ((Element) throws -> W, (W, W) throws -> Bool)
) rethrows -> [Element] {
try sorted {
let lhs1 = try primary.0($0)
let rhs1 = try primary.0($1)
guard lhs1 == rhs1 else {
return try primary.1(lhs1, rhs1)
}
let lhs2 = try secondary.0($0)
let rhs2 = try secondary.0($1)
guard lhs2 == rhs2 else {
return try secondary.1(lhs2, rhs2)
}
let lhs3 = try terciary.0($0)
let rhs3 = try terciary.0($1)
guard lhs3 == rhs3 else {
return try terciary.1(lhs3, rhs3)
}
return try quaternary.1(quaternary.0($0), quaternary.0($1))
}
}
}
Now if you would like to create the mutating version of those methods you need to extend MutableCollection and constrain Selfto RandomAccessCollection:
extension MutableCollection where Self: RandomAccessCollection {
public mutating func sort<T: Comparable, U: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool)
) rethrows {
try sort {
let lhs = try primary.0($0)
let rhs = try primary.0($1)
guard lhs == rhs else {
return try primary.1(lhs, rhs)
}
return try secondary.1(secondary.0($0), secondary.0($1))
}
}
public mutating func sort<T: Comparable, U: Comparable, V: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool),
_ terciary: ((Element) throws -> V, (V, V) throws -> Bool)
) rethrows {
try sort {
let lhs1 = try primary.0($0)
let rhs1 = try primary.0($1)
guard lhs1 == rhs1 else {
return try primary.1(lhs1, rhs1)
}
let lhs2 = try secondary.0($0)
let rhs2 = try secondary.0($1)
guard lhs2 == rhs2 else {
return try secondary.1(lhs2, rhs2)
}
return try terciary.1(terciary.0($0), terciary.0($1))
}
}
public mutating func sort<T: Comparable, U: Comparable, V: Comparable, W: Comparable>(
_ primary: ((Element) throws -> T, (T, T) throws -> Bool),
_ secondary: ((Element) throws -> U, (U, U) throws -> Bool),
_ terciary: ((Element) throws -> V, (V, V) throws -> Bool),
_ quaternary: ((Element) throws -> W, (W, W) throws -> Bool)
) rethrows {
try sort {
let lhs1 = try primary.0($0)
let rhs1 = try primary.0($1)
guard lhs1 == rhs1 else {
return try primary.1(lhs1, rhs1)
}
let lhs2 = try secondary.0($0)
let rhs2 = try secondary.0($1)
guard lhs2 == rhs2 else {
return try secondary.1(lhs2, rhs2)
}
let lhs3 = try terciary.0($0)
let rhs3 = try terciary.0($1)
guard lhs3 == rhs3 else {
return try terciary.1(lhs3, rhs3)
}
return try quaternary.1(quaternary.0($0), quaternary.0($1))
}
}
}
Playground testing:
struct User: Equatable {
let name: String
let age: Int
}
var users: [User] = [
.init(name: "Liza", age: 19),
.init(name: "John", age: 19),
.init(name: "Steve", age: 51)
]
Single property criteria sort:
let sorted = users.sorted(\.age) // [{name "Liza", age 19}, {name "John", age 19}, {name "Steve", age 51}]
users.sort(\.age) // [{name "Liza", age 19}, {name "John", age 19}, {name "Steve", age 51}]
users == sorted // true
Multiple property criteria sort:
let sorted = users.sorted((\.age, >),(\.name, <)) // [{name "Steve", age 51}, {name "John", age 19}, {name "Liza", age 19}]
users.sort((\.age, >),(\.name, <)) // [{name "Steve", age 51}, {name "John", age 19}, {name "Liza", age 19}]
users == sorted // true
For Xcode 13.0+, iOS 15.0+, iPadOS 15.0+, macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+ you can use KeyPathComparator:
Usage:
let sorted1 = users.sorted(using: [KeyPathComparator(\.age)]) // [{name "Liza", age 19}, {name "John", age 19}, {name "Steve", age 51}]
let sorted2 = users.sorted(using: [KeyPathComparator(\.age), KeyPathComparator(\.name)]) // [{name "John", age 19}, {name "Liza", age 19}, {name "Steve", age
let sorted3 = users.sorted(using: [KeyPathComparator(\.age, order: .reverse), KeyPathComparator(\.name)]) // [{name "Steve", age 51}, {name "John", age 19}, {name "Liza", age 19}]

Related

Error after update PromiseKit: Cannot convert value of type 'PMKFinalizer' to expected argument type 'Promise<Void>'

This code worked with PromiseKit v.4.5.2.
func getReminderIds(idArray: [Int]) {
var reminderPromises: [Promise<Void>] = []
for id in idArray {
if let prom = self.delegate?.getReminders(id).then({ (reminderArray) -> Promise<Void> in
Utils.logMessage("Reminders for asset \(id): \(reminderArray)")
self.reminders[String(id)] = reminderArray
}).catch({ (err) in
self.reminders[String(id)] = nil
Utils.logMessage("Error getting reminders for asset \(id): \(err)")
}){
reminderPromises.append(prom)
}
}
_ = when(fulfilled: reminderPromises).done { results -> Void in
self.collectionView?.refreshCollection(collection: 0)
}
}
But after updating to PromiseKit v.6.8.4 I get the error
"Cannot convert value of type 'PMKFinalizer' to expected argument type 'Promise'"
in this line:
reminderPromises.append(prom)
struct Reminder {
let id: Int
let value: [String: Any]
}
func getReminderIds(idArray: [Int]) {
var reminderPromises: [Promise<Reminder>] = []
for id in idArray {
reminderPromises.append(getReminders(id))
}
_ = when(fulfilled: reminderPromises).done { results -> Void in
for item in results {
print(item.id)
print(item.value)
}
}
}
func getReminders(_ id: Int) -> Promise<Reminder> {
// TODO network request or database request
return Promise { $0.fulfill(Reminder(id: id, value: [:])) }
}

iOS Swift 3: Comparing One all elements of Array in another array

I have struct model like
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {}
func ==(lhs: ModelA, rhs: ModelA) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
i have created arrays of this model
let a1:[ModelA] = [ModelA(text: "10", id: 11), ModelA(text: "11", id: 12)]
let a2:[ModelA] = [ModelA(text: "11", id: 12)]
and having a comparing function
func isEqualArray(first array1: [Any], second array2: [Any]) -> Bool {
let set1 = NSSet(array: array1)
let set2 = NSSet(array: array2)
return set1.isSubset(of: set2 as! Set<AnyHashable>)
}
so when i'm trying to cross check
let flag = isEqualArray(first: a1, second: a2)
print("### \(flag)")
It crashes on function return
What am I doing wrong?
Your struct needs to conform to both equatable and hashable in order to be used in a Set. It seems that you only care about the id, so a simple implementation would be:
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {
static func ==(lhs: ModelA, rhs: ModelA) -> Bool {
return lhs.id == rhs.id
}
}
extension ModelA: Hashable {
var hashValue: Int {
return id
}
}
Now, you can use Swift sets in your isEqualArray function; you also need to consider which set is smaller since you are using isSubSet(of:):
func isEqualArray(first array1: [AnyHashable], second array2: [AnyHashable]) -> Bool {
let set1: Set<AnyHashable>
let set2: Set<AnyHashable>
if array1.count > array2.count {
set1 = Set(array1)
set2 = Set(array2)
} else {
set1 = Set(array2)
set2 = Set(array1)
}
return set2.isSubset(of: set1)
}
Your code actually determines if one array is a subset of another, not if the arrays are equal, so I am not sure if that is what you want.

How to compare two array of objects?

I have a class A:
class A {
var identifier: String?
var quantity: Int = 0
}
Two arrays of A instances:
var array1: [A] = [a1, a2, a3, a4]
var array2: [A] = [a5, a6, a7, a8]
I don't know which is the best way to check:
array1==array2 if a1.identifier == a5.identifier, a2.identifier == a6.identifier, a3.identifier==a7.identifier, a4.identifier==a8.identifier in Swift.
Please help me...
You can try like this:
let result = zip(array1, array2).enumerated().filter() {
$1.0 == $1.1
}.map{$0.0}
Swift 4
The following method makes it much more easy.
Method 1 - Using Equatable Protocol
Step1 - Make your class 'A' equatable as follows
extension A: Equatable {
static func ==(lhs: A, rhs: A) -> Bool {
// Using "identifier" property for comparison
return lhs.identifier == rhs.identifier
}
}
Step2 - Sort your arrays in ascending or descending order
let lhsArray = array1.sorted(by: { $0.identifier < $1.identifier })
let rhsArray = array2.sorted(by: { $0.identifier < $1.identifier })
Step3 - Use == or elementsEqual comparison
let isEqual = lhsArray == rhsArray
OR
let isEqual = lhsArray.elementsEqual(rhsArray, by: { $0 == $1} )
Method 2 (Without Equatable Protocol)
Step 1 - Sort Array as described in Method1, step 2
Step 2 - Use elementsEqual
lhsArray.elementsEqual(rhsArray, by: { $0.identifier == $1.identifier })
Read more about Array Comparison here
Assume your data like that:
struct Person
{
let name: String
let id: Int
}
var people1 = [
Person(name: "Quang Hà", id: 42),
Person(name: "Lý Hải", id: 23),
Person(name: "Maria", id: 99)
]
var people2 = [
Person(name: "Maria yyy", id: 99),
Person(name: "Billy", id: 42),
Person(name: "David", id: 23)
]
This is the method to compare two arrays of people with id:
func areArrayPeopleEqual(people1:[Person], people2: [Person]) -> Bool {
var array1 = people1
var array2 = people2
// Don't equal size => false
if array1.count != array2.count {
return false
}
// sort two arrays
array1.sortInPlace() { $0.id > $1.id }
array2.sortInPlace() {$0.id > $1.id }
// get count of the matched items
let result = zip(array1, array2).enumerate().filter() {
$1.0.id == $1.1.id
}.count
if result == array1.count {
return true
}
return false
}
This method could be used if you have some parameter to compare:
let difference = currentObjects
.filter({ currentObject in
!(newObjects
.contains(where: { $0.identifier == currentObject.identifier }))
})
I found this really easy solution at https://www.hackingwithswift.com/example-code/language/how-to-find-the-difference-between-two-arrays
extension Array where Element: Hashable {
func difference(from other: [Element]) -> [Element] {
let thisSet = Set(self)
let otherSet = Set(other)
return Array(thisSet.symmetricDifference(otherSet))
}
}
let names1 = ["a1", "A4", "a3", "a4"]//["John", "Paul", "Ringo"]
let names2 = ["a1", "a5", "a4","a1.1"]//["Ringo", "George"]
let difference = names1.difference(from: names2)
First we extend Equatable class, to have a DRY code, than if the 2 arrays are always of the same size, or if at least the first one is <= than the second you can go with this solution.
Pay attention that you are working with optionals, you may have to unwrap them before.
class A {
var identifier: String?
var quantity: Int = 0
init(identifier: String, quantity: Int) {
self.identifier = identifier
self.quantity = quantity
}
}
let a1: A = A(identifier: "1", quantity: 1)
let a2: A = A(identifier: "2", quantity: 2)
let a3: A = A(identifier: "3", quantity: 3)
let a4: A = A(identifier: "4", quantity: 4)
let a5: A = A(identifier: "1", quantity: 1)
let a6: A = A(identifier: "2", quantity: 2)
let a7: A = A(identifier: "3", quantity: 3)
let a8: A = A(identifier: "4", quantity: 4)
var array1: [A] = [a1, a2, a3, a4]
var array2: [A] = [a5, a6, a7, a8]
func areEquals(array1: [A], array2: [A]) -> Bool {
if array1.count < array2.count {
return false
}
for i in 0...array2.count - 1 {
if array1[i] != array2[i] {
return false
}
}
return true
}
extension A: Equatable {
static func ==(lhs: A, rhs: A) -> Bool {
//you can choose how and when they should be equals
return lhs.identifier == rhs.identifier
}
}
try this code, let me know if it works
func toDictionary<E, K, V>(
array: [E],
transformer: (element: E) -> (key: K, value: V)?)
-> Dictionary<K, V>
{
return array.reduce([:]) {
(var dict, e) in
if let (key, value) = transformer(element: e)
{
dict[key] = value
}
return dict
}
}
then you can execute a check like below
let areEqual = array1.count == array2.count;
if areEqual {
let dict1 = toDictionary(array1) { ($0.identifier, $0.quantity) }
let dict2 = toDictionary(array2) { ($0.identifier, $0.quantity) }
areEqual = NSDictionary(dictionary: dict1).isEqualToDictionary(dict2)
}
print(areEqual)
disclaimer: function toDictionary has been took form here

Using filter on Objects in an Array to find max? [swift]

I have a bunch of Objects stored in an Array.
They all have the property:
distanceInSeconds: Int
I was wondering if there's a way to find the max of this property between all objects in the array using filter or another array method?
For instance:
var distances: [Distance] = []
var maxDistance = distances.filter(find max)
This would be the Swifty way (by implementing Comparable):
class Route : Comparable {
let distance: Int
init(distance: Int) {
self.distance = distance
}
}
func ==(lhs: Route, rhs: Route) -> Bool {
return lhs.distance == rhs.distance
}
func <(lhs: Route, rhs: Route) -> Bool {
return lhs.distance < rhs.distance
}
let routes = [
Route(distance: 4),
Route(distance: 8),
Route(distance: 2),
Route(distance: 7)
]
print(routes.maxElement()?.distance)
output:
"8"
This works with Swift 2. If you're using Swift 1.2, maxElement(routes) should work
In Swift 2.0 minElement and maxElement now return optionals in case of empty sequences, and also now have versions that take isOrderedBefore closures.
let maxDistance = distances.maxElement { (a, b) -> Bool in
a.distanceInSeconds < b.distanceInSeconds
}
#1. The element type inside your sequence conforms to Comparable protocol
With Swift 4, if the element type inside your sequence conforms to Comparable protocol, you will be able to use the max() method that has the following declaration:
func max() -> Self.Element?
Returns the maximum element in the sequence.
Usage:
class Distance: Comparable, CustomStringConvertible {
let distanceInSeconds: Int
var description: String { return "Distance in Int: \(distanceInSeconds)" }
init(distanceInSeconds: Int) {
self.distanceInSeconds = distanceInSeconds
}
static func ==(lhs: Distance, rhs: Distance) -> Bool {
return lhs.distanceInSeconds == rhs.distanceInSeconds
}
static func <(lhs: Distance, rhs: Distance) -> Bool {
return lhs.distanceInSeconds < rhs.distanceInSeconds
}
}
let distances = [
Distance(distanceInSeconds: 20),
Distance(distanceInSeconds: 30),
Distance(distanceInSeconds: 10)
]
let maxDistance = distances.max()
print(String(describing: maxDistance)) // prints: Optional(Distance in Int: 30)
#2. The element type inside your sequence does not conform to Comparable protocol
With Swift 4, if the element type inside your sequence does not conform to Comparable protocol, you will have to use the max(by:) method that has the following declaration:
func max(by areInIncreasingOrder: ((offset: Int, element: Base.Element), (offset: Int, element: Base.Element)) throws -> Bool) rethrows -> (offset: Int, element: Base.Element)?
Returns the maximum element in the sequence, using the given predicate as the comparison between elements.
Usage:
class Distance: CustomStringConvertible {
let distanceInSeconds: Int
var description: String { return "Distance in Int: \(distanceInSeconds)" }
init(distanceInSeconds: Int) {
self.distanceInSeconds = distanceInSeconds
}
}
let distances = [
Distance(distanceInSeconds: 20),
Distance(distanceInSeconds: 30),
Distance(distanceInSeconds: 10)
]
let maxDistance = distances.max (by: { (a, b) -> Bool in
return a.distanceInSeconds < b.distanceInSeconds
})
print(String(describing: maxDistance)) // prints: Optional(Distance in Int: 30)
I think you want reduce:
Setup:
struct Distance {
var distanceInSeconds: Int
}
var distances: [Distance] = []
for _ in 1...10 {
distances += [Distance(distanceInSeconds: Int(arc4random_uniform(100)))]
}
Implementation:
let max = distances.reduce(distances.first) {
if let left = $0 where left.distanceInSeconds > $1.distanceInSeconds {
return $0
} else {
return $1
}
}

Order array of objects into every possible sequence in Swift

Wondering if there is a clean way of doing this in Swift. Maybe using one or a couple of the global functions, ie Map / Reduce etc
The array contains unique custom objects of n quantity.
For example, with 3 items. But could have more or less. [1,2,3]
Would return an Array of Arrays
[
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]
]
Here is a way in Java to complete the task. Just need to get into Swift form.
https://gist.github.com/JadenGeller/5d49e46d4084fc493e72
He created structs to handle permutations:
var greetingPermutations = PermutationSequenceGenerator(elements: ["hi", "hey", "hello"])
while let greetingSequence = greetingPermutations.next(){
for greeting in greetingSequence {
print("\(greeting) ")
}
println()
}
or:
var numberSpace = PermutationSpaceGenerator(objects: Array(1...4))
while let numberArray = numberSpace.next() {
println(numberArray)
}
EDIT:
Here is a simpler way found on objc.io
Add Extension
extension Array {
var decompose : (head: T, tail: [T])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
}
Add outside your extension / and class
infix operator >>= {}
func >>=<A, B>(xs: [A], f: A -> [B]) -> [B] {
return xs.map(f).reduce([], combine: +)
}
Normal Class Functions
func between<T>(x: T, ys: [T]) -> [[T]] {
if let (head, tail) = ys.decompose {
return [[x] + ys] + between(x, ys: tail).map { [head] + $0 }
} else {
return [[x]]
}
}
func permutations<T>(xs: [T]) -> [[T]] {
if let (head, tail) = xs.decompose {
return permutations(tail) >>= { permTail in
self.between(head, ys: permTail)
}
} else {
return [[]]
}
}
Testing
let example = permutations([1,2,3,5,6,7,8])
println(example)
This code extends Array with decompose function and also adds >>== operator (flattening) More about flattening: http://www.objc.io/snippets/4.html
Probably too c-ish, but here is an alternative to the already posted examples.
var a = [1, 2, 3, 4, 5]
var b = [[Int]]()
func perms<T>(n: Int, inout a: [T], inout b: [[T]]) {
if n == 0 {
b.append(a)
} else {
for i in 0..<n {
perms(n - 1, &a, &b)
var j = 0
if n % 2 == 0 {
j = i
}
swap(&a[j], &a[n - 1])
}
}
}
perms(a.count, &a, &b)
println(b)
Swift 5
Updated version of #DogCoffee for swift 5.x, all within an array extension :
extension Array {
private var decompose : (head: Element, tail: [Element])? {
return (count > 0) ? (self[0], Array(self[1..<count])) : nil
}
private func between<T>(x: T, ys: [T]) -> [[T]] {
if let (head, tail) = ys.decompose {
return [[x] + ys] + between(x: x, ys: tail).map { [head] + $0 }
} else {
return [[x]]
}
}
private func permutations<T>(xs: [T]) -> [[T]] {
if let (head, tail) = xs.decompose {
return permutations(xs: tail) >>= { permTail in
self.between(x: head, ys: permTail)
}
} else {
return [[]]
}
}
func allPermutations() -> [[Element]] {
return permutations(xs: self)
}
}
infix operator >>=
func >>=<A, B>(xs: [A], f: (A) -> [B]) -> [B] {
return xs.map(f).reduce([], +)
}

Resources