Home > Mobile >  Swift. Sort array of structs using property name from string variable
Swift. Sort array of structs using property name from string variable

Time:05-27

I have simple code:

struct User {
    let id: Int
    let name: String
}

var users: [User] = [User(id:1, name:"Alpha"), User(id:2, name:"Beta"), User(id:3, name:"Gamma")]

print(users)
users.sort { $0.name > $1.name }
print(users)

What is the best way of changing the sorting field based on variable? I mean, I want to have some variable like "sortBy" which contains value of sorting field ("id", "name" etc, struct may contain dozens of fields). And I can't find out the proper way of doing so in Swift.

Pseudo-code:

var sortBy = "name"
users.sort { $0.{sortBy} > $1.{sortBy} }

CodePudding user response:

Use key paths and methods.

users.sort(by: \.name)
users.sort(by: \.id)
extension Array where Element == User {
  mutating func sort<Comparable: Swift.Comparable>(
    by comparable: (Element) throws -> Comparable
  ) rethrows {
    self = try sorted(by: comparable, >)
  }
}
public extension Sequence {
  /// Sorted by a common `Comparable` value, and sorting closure.
  func sorted<Comparable: Swift.Comparable>(
    by comparable: (Element) throws -> Comparable,
    _ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
  ) rethrows -> [Element] {
    try sorted {
      try areInIncreasingOrder(comparable($0), comparable($1))
    }
  }
}

CodePudding user response:

You could define a array of predicates to sort over in the order you want:

import Foundation

struct User {
    let id: Int
    let name: String
}

var users: [User] = [User(id:3, name:"Gamma"), User(id:1, name:"Zeta"), User(id:1, name:"Alpha"), User(id:4, name:"Alpha"), User(id:2, name:"Beta")]

print("Before Sorting: \(users)")

typealias AreInIncreasingOrder = (User, User) -> Bool
users.sort { (lhs, rhs) in 
    let predicates: [AreInIncreasingOrder] = [
        { $0.id < $1.id },
        { $0.name < $1.name}
    ]
    for predicate in predicates {
        if !predicate(lhs, rhs) && !predicate(rhs, lhs) {
            continue
        }
        return predicate(lhs, rhs)
    }
    return false
}

print("After Sorting: \(users)")

Output:

Before Sorting: [Page_Contents.User(id: 3, name: "Gamma"), Page_Contents.User(id: 1, name: "Zeta"), Page_Contents.User(id: 1, name: "Alpha"), Page_Contents.User(id: 4, name: "Alpha"), Page_Contents.User(id: 2, name: "Beta")]
After Sorting: [Page_Contents.User(id: 1, name: "Alpha"), Page_Contents.User(id: 1, name: "Zeta"), Page_Contents.User(id: 2, name: "Beta"), Page_Contents.User(id: 3, name: "Gamma"), Page_Contents.User(id: 4, name: "Alpha")]
  • Related