Swift - Sort array of objects with multiple criteria

后端 未结 8 1123
北荒
北荒 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 11:57

    I'd recommend using Hamish's tuple solution since it doesn't require extra code.


    If you want something that behaves like if statements but simplifies the branching logic, you can use this solution, which allows you to do the following:

    animals.sort {
      return comparisons(
        compare($0.family, $1.family, ascending: false),
        compare($0.name, $1.name))
    }
    

    Here are the functions that allow you to do this:

    func compare(_ value1Closure: @autoclosure @escaping () -> C, _ value2Closure: @autoclosure @escaping () -> C, ascending: Bool = true) -> () -> ComparisonResult {
      return {
        let value1 = value1Closure()
        let value2 = value2Closure()
        if value1 == value2 {
          return .orderedSame
        } else if ascending {
          return value1 < value2 ? .orderedAscending : .orderedDescending
        } else {
          return value1 > value2 ? .orderedAscending : .orderedDescending
        }
      }
    }
    
    func comparisons(_ comparisons: (() -> ComparisonResult)...) -> Bool {
      for comparison in comparisons {
        switch comparison() {
        case .orderedSame:
          continue // go on to the next property
        case .orderedAscending:
          return true
        case .orderedDescending:
          return false
        }
      }
      return false // all of them were equal
    }
    

    If you want to test it out, you can use this extra code:

    enum Family: Int, Comparable {
      case bird
      case cat
      case dog
    
      var short: String {
        switch self {
        case .bird: return "B"
        case .cat: return "C"
        case .dog: return "D"
        }
      }
    
      public static func <(lhs: Family, rhs: Family) -> Bool {
        return lhs.rawValue < rhs.rawValue
      }
    }
    
    struct Animal: CustomDebugStringConvertible {
      let name: String
      let family: Family
    
      public var debugDescription: String {
        return "\(name) (\(family.short))"
      }
    }
    
    let animals = [
      Animal(name: "Leopard", family: .cat),
      Animal(name: "Wolf", family: .dog),
      Animal(name: "Tiger", family: .cat),
      Animal(name: "Eagle", family: .bird),
      Animal(name: "Cheetah", family: .cat),
      Animal(name: "Hawk", family: .bird),
      Animal(name: "Puma", family: .cat),
      Animal(name: "Dalmatian", family: .dog),
      Animal(name: "Lion", family: .cat),
    ]
    

    The main differences from Jamie's solution is that the access to the properties are defined inline rather than as static/instance methods on the class. E.g. $0.family instead of Animal.familyCompare. And ascending/descending is controlled by a parameter instead of an overloaded operator. Jamie's solution adds an extension on Array whereas my solution uses the built in sort/sorted method but requires two additional ones to be defined: compare and comparisons.

    For completeness sake, here's how my solution compares to the Hamish's tuple solution. To demonstrate I'll use a wild example where we want to sort people by (name, address, profileViews) Hamish's solution will evaluate each of the 6 property values exactly once before the comparison begins. This may not or may not be desired. For example, assuming profileViews is an expensive network call we may want to avoid calling profileViews unless it's absolutely necessary. My solution will avoid evaluating profileViews until $0.name == $1.name and $0.address == $1.address. However, when it does evaluate profileViews it'll likely evaluate many more times than once.

提交回复
热议问题