Swift - Sort array of objects with multiple criteria

后端 未结 8 962
北荒
北荒 2020-11-22 11:19

I have an array of Contact objects:

var contacts:[Contact] = [Contact]()

Contact class:

Class Contact:NSOBjec         


        
8条回答
  •  失恋的感觉
    2020-11-22 12:03

    This question has already many great answers, but I want to point to an article - Sort Descriptors in Swift. We have several ways to do the multiple criteria sorting.

    1. Using NSSortDescriptor, this way has some limitations, the object should be a class and inherits from NSObject .

      class Person: NSObject {
          var first: String
          var last: String
          var yearOfBirth: Int
          init(first: String, last: String, yearOfBirth: Int) {
              self.first = first
              self.last = last
              self.yearOfBirth = yearOfBirth
          }
      
          override var description: String {
              get {
                  return "\(self.last) \(self.first) (\(self.yearOfBirth))"
              }
          }
      }
      
      let people = [
          Person(first: "Jo", last: "Smith", yearOfBirth: 1970),
          Person(first: "Joe", last: "Smith", yearOfBirth: 1970),
          Person(first: "Joe", last: "Smyth", yearOfBirth: 1970),
          Person(first: "Joanne", last: "smith", yearOfBirth: 1985),
          Person(first: "Joanne", last: "smith", yearOfBirth: 1970),
          Person(first: "Robert", last: "Jones", yearOfBirth: 1970),
      ]
      

      Here, for example, we want to sort by last name, then first name, finally by birth year. And we want do it case insensitively and using the user’s locale.

      let lastDescriptor = NSSortDescriptor(key: "last", ascending: true,
        selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
      let firstDescriptor = NSSortDescriptor(key: "first", ascending: true, 
        selector: #selector(NSString.localizedCaseInsensitiveCompare(_:)))
      let yearDescriptor = NSSortDescriptor(key: "yearOfBirth", ascending: true)
      
      
      
      (people as NSArray).sortedArray(using: [lastDescriptor, firstDescriptor, yearDescriptor]) 
      // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
      
    2. Using Swift way of sorting with last name/first name . This way should work with both class/struct. However, we don't sort by yearOfBirth here.

      let sortedPeople = people.sorted { p0, p1 in
          let left =  [p0.last, p0.first]
          let right = [p1.last, p1.first]
      
          return left.lexicographicallyPrecedes(right) {
              $0.localizedCaseInsensitiveCompare($1) == .orderedAscending
          }
      }
      sortedPeople // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1985), Joanne smith (1970), Joe Smith (1970), Joe Smyth (1970)]
      
    3. Swift way to inmitate NSSortDescriptor. This uses the concept that 'functions are a first-class type'. SortDescriptor is a function type, takes two values, returns a bool. Say sortByFirstName we take two parameters($0,$1) and compare their first names. The combine functions takes a bunch of SortDescriptors, compare all of them and give orders.

      typealias SortDescriptor = (Value, Value) -> Bool
      
      let sortByFirstName: SortDescriptor = {
          $0.first.localizedCaseInsensitiveCompare($1.first) == .orderedAscending
      }
      let sortByYear: SortDescriptor = { $0.yearOfBirth < $1.yearOfBirth }
      let sortByLastName: SortDescriptor = {
          $0.last.localizedCaseInsensitiveCompare($1.last) == .orderedAscending
      }
      
      func combine
          (sortDescriptors: [SortDescriptor]) -> SortDescriptor {
          return { lhs, rhs in
              for isOrderedBefore in sortDescriptors {
                  if isOrderedBefore(lhs,rhs) { return true }
                  if isOrderedBefore(rhs,lhs) { return false }
              }
              return false
          }
      }
      
      let combined: SortDescriptor = combine(
          sortDescriptors: [sortByLastName,sortByFirstName,sortByYear]
      )
      people.sorted(by: combined)
      // [Robert Jones (1970), Jo Smith (1970), Joanne smith (1970), Joanne smith (1985), Joe Smith (1970), Joe Smyth (1970)]
      

      This is good because you can use it with both struct and class, you can even extend it to compare with nils.

    Still, reading the original article is strongly suggested. It has much more details and well explained.

提交回复
热议问题