Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift - Sort array of persons with boolean and optional date

I'd like to sort an array of persons considering 3 criteria : this array is made of customers with 3 informations :

  1. name (String)
  2. hasPurchased (Bool)
  3. purchaseDate (that can be nil if hasPurchased = false)

I'm able to sort my customers by name, no problem:

    customersList.sort(by: {$0.name!.compare($1.name!) == .orderedAscending})

I'm able to sort by boolean and name, no problem:

    customersList.sort {$1.hasPurchased == $0.hasPurchased ? ($0.name!.compare($1.name!) == .orderedAscending) : $1.hasPurchased && !$0.hasPurchased}

But what I would like to do is to sort in a single array :

  1. first, by boolean and name (hasPurchased = false, name orderedAscending)
  2. then, if hasPurchased = true, sort by purchaseDate, comparing dates orderedAscending

This way, I would keep in a single array my customers by "ABC" that haven't purchased yet and then those who purchased sorted by date of purchase (non-null criteria in this case)...

tell me what you think about it.

like image 326
Creanomy Avatar asked Jan 21 '26 17:01

Creanomy


2 Answers

Notice that the argument to sort is called "areInIncreasingOrder". Given two Customer objects, you are supposed to return whether they are in increasing order. So to determine whether two customers are in increasing order with your 2 rules:

  • if they have different hasPurchased values, they are in increasing order if the second one has purchased and the first one has not
  • otherwise, if they have the same hasPurchased value of false, then they are in increasing order if the first one's name is in increasing order with the second one's name
  • otherwise, if they have the same hasPurchased value of true, then they are in increasing order if the first one's purchase date is in increasing order with the second one's purchase date

Translating that to code:

customerList.sort { c1, c2 in
    if c1.hasPurchased != c2.hasPurchased {
        // non-purchased customers are ordered before purchased customers
        return c2.hasPurchased && !c1.hasPurchased
    } else if !c1.hasPurchased { // both are non-purchased customers
        // sort by name
        return c1.name < c2.name
    } else { // both are purchased customers
        // sort by date
        return c1.purchaseDate! < c2.purchaseDate!
    }
}
like image 60
Sweeper Avatar answered Jan 23 '26 08:01

Sweeper


If this will be the default and most prevalent way you want want to sort customers by I'd approach this by defining Comparable for the customer to take into account these priorities.

extension Customer: Comparable {
   static func < (lhs: Customer, rhs: Customer) -> Bool {
      switch (lhs.hasPurchased, rhs.hasPurchased) {
         case (true, true): return lhs.purchaseDate! < rhs.purchaseDate!
         case (true, false): return false
         case (false, true): return true
         case (false, false): return lhs.name < rhs.name
      }
   }
}

This will then allow you to just use the .sorted() method from Sequence:

customers.sorted()

As an examples of this:

let customers:[Customer] = [
   Customer(name: "Alan", hasPurchased: false, purchaseDate: nil),
   Customer(name: "Ben", hasPurchased: false, purchaseDate: nil),
   Customer(name: "Carol", hasPurchased: true, purchaseDate: Date()),
   Customer(name: "Dan", hasPurchased: true, purchaseDate: Date().addingTimeInterval(100)),
   Customer(name: "Elsie", hasPurchased: false, purchaseDate: nil),
   Customer(name: "Frank", hasPurchased: true, purchaseDate: Date().addingTimeInterval(200))
]

let s = customers.sorted()
s.map{print($0)}

Which gives you:

Customer(name: "Alan", hasPurchased: false, purchaseDate: nil)
Customer(name: "Ben", hasPurchased: false, purchaseDate: nil)
Customer(name: "Elsie", hasPurchased: false, purchaseDate: nil)
Customer(name: "Carol", hasPurchased: true, purchaseDate: Optional(2021-08-09 11:02:55 +0000))
Customer(name: "Dan", hasPurchased: true, purchaseDate: Optional(2021-08-09 11:04:35 +0000))
Customer(name: "Frank", hasPurchased: true, purchaseDate: Optional(2021-08-09 11:06:15 +0000))

This approach will mean that if you want at some other point, for example, to just sort by name, then you will have to define that sort explicitly via a closure, but as that sort closure will be far simpler to write (and later on to understand) I see this as an acceptable compromise. It depends on the detail of your wider use case, which isn't clear from the question. If this need is the exception rather than the norm, you can adopt the above approach to an explicit sort closure.

like image 24
flanker Avatar answered Jan 23 '26 07:01

flanker