0

I was trying to sort an array of custom objects but for some reason, the code that worked a few weeks back won't work anymore. It is supposed to check if $0 and $1 have the same date, and if they do, it is supposed to sort the array by id, but currently it won't sort correctly and when I print the array, I get the following output:

11-07-2017 : 1
11-07-2017 : 10
11-07-2017 : 11
11-07-2017 : 12
11-07-2017 : 13
11-07-2017 : 14
11-07-2017 : 15
11-07-2017 : 16
11-07-2017 : 17
11-07-2017 : 18
11-07-2017 : 19
11-07-2017 : 2
11-07-2017 : 20
11-07-2017 : 3
11-07-2017 : 4
11-07-2017 : 5
11-07-2017 : 7
11-07-2017 : 8
11-07-2017 : 9
11-08-2017 : 1
11-08-2017 : 2
11-09-2017 : 1
11-09-2017 : 2

As can be seen above, the dates that only have a few entries sort correctly, but the dates with more entries (11-07-17) don't.

Below is my code:

Model for the Array:

struct BalanceUser {

    var id = ""

    var name = ""

    var date = ""

}

Current Sorting Code:

self.sortedTableArray.sort(by: {
       if $0.date != $1.date {
             return $0.date < $1.date
       } else {
             return $0.id < $1.id
       }
})

Firebase Code (As Requested):

ref.child("Admin").child("Balances").observeSingleEvent(of: .value, with: { (snapshot) in

        let value = snapshot.value as? NSDictionary

        if value!.count > 1 {

            let specificValues = value?.allKeys

            for balanceUser in specificValues! {

                var user = BalanceUser()

                user.date = balanceUser as! String

                if balanceUser as? String != "balance" {

                    var i = 0

                    var counter = 0

                    while i < 101 {

                        self.ref.child("Admin")
                            .child("Balances")
                            .child(balanceUser as! String)
                            .child(String(i))
                            .observeSingleEvent(of: .value, with: { (snapshot) in

                                let nameValue = snapshot.value as? NSDictionary

                                if nameValue != nil {

                                    user.id = counter

                                    var j = 0

                                    while j < (nameValue?.count)! {

                                        let item = nameValue?.allKeys[j] as? String

                                        var aItem = ""

                                        if let item = nameValue?.allValues[j] as? String {

                                            aItem = item

                                        } else if let item = nameValue?.allValues[j] as? NSNumber {

                                            aItem = String(describing: item)

                                        } else if let item = nameValue?.allValues[j] as? Int {

                                            aItem = String(describing: item)

                                        }


                                        if item == "name" {

                                            user.name = aItem

                                        } else if item == "money" {

                                            user.money = aItem

                                        } else if item == "balance" {

                                            user.balance = aItem

                                        } else if item == "status" {

                                            user.status = aItem

                                        }

                                        j += 1

                                    }


                                    let dateFormatter = DateFormatter()

                                    dateFormatter.dateFormat = "MM-dd-yyyy"

                                    if user.date.components(separatedBy: "-")[0] == dateFormatter.string(from: Date()).components(separatedBy: "-")[0] {

                                        self.sortedTableArray.append(user)

                                        self.sortedTableArray.sort(by: { (object1, object2) -> Bool in

                                            if object1.date == object2.date {

                                                return object1.id < object2.id

                                            } else {

                                                return object1.date < object2.date

                                            }

                                        })

                                    }

                                    self.tableArray.append(user)

                                    self.tableArray.sort(by: { (object1, object2) -> Bool in

                                        if object1.date == object2.date && object1.year == object2.year {

                                            return object2.id > object1.id

                                        } else {

                                            return object1.date < object2.date || object1.year < object2.year

                                        }

                                    })

                                    self.tableView.reloadData()

                                }

                                counter += 1

                            }) { (error) in

                                print(error.localizedDescription)

                        }

                        i += 1

                    }

                }

            }

        } else {

            self.view.makeToast(message: "No Users Found in Database")

        }

    }) { (error) in

        print(error.localizedDescription)

    }
kingkps
  • 156
  • 1
  • 13
  • 4
    It is sorting properly. Your ids are strings so they are sorted alphabetically, not numerically. – rmaddy Jan 11 '18 at 04:32
  • Oh, I did not realize that. So if I store the id as an integer and sort it that way, it will work correctly, right? – kingkps Jan 11 '18 at 04:33
  • 4
    BTW - your dates won't sort property as strings in that format. They need to be formatted as yyyy-MM-dd to sort properly as strings. – rmaddy Jan 11 '18 at 04:34
  • If you want your ids treated as numbers then don't use a string. Use a numeric data type. Or use code that sorts the strings as numbers. – rmaddy Jan 11 '18 at 04:35
  • The dates are sorting perfectly, thanks for pointing out my mistake and saving me a lot of time scratching my head as to what the issue is. – kingkps Jan 11 '18 at 04:36
  • 1
    No, your dates are not sorting correctly. Add a date of `02-15-2018` and see what happens. Or `12-25-2016`. – rmaddy Jan 11 '18 at 04:38
  • I didn't put all of the logs into the post as they are very long. I actually have dates all the way from 11-7-17 through 12-30-17 and they are all sorting correctly. To sort the dates, I am currently using another variable in my object called year to sort for that. Unfortunately, I cannot use your method as my database format is as follows : Balances/[Date]/[ID]/Info Here : and I cannot change the dates in the database anymore. – kingkps Jan 11 '18 at 04:41
  • There's a lot of red flags here. Does it make sense for `BalanceUser` to have a default `date` of `""`? Does it make sense for them to have a `name` and `id` that are mutable? I would strongly advise you take advantage of types (e.g. `Date` for dates, not `String`), remove the illogical default initializer values, and limit mutability to only where it makes sense – Alexander Jan 11 '18 at 05:06
  • The reason I use Strings for the Dates is because of my database format, as discussed in the comment before this. How do you suggest I remove the default initializer values? All of the variables inside of the BalanceUser have to be mutable because none of them has a default value, and I pull data from Firebase to write their values. – kingkps Jan 11 '18 at 05:10
  • @kps2501 Just because your database stores them as strings doesn't mean you're doomed to storing them as strings forever. Make a failable initializer that takes the date as a string, and uses a dateformatter to parse it into a Date. From there, all kinds of date manipulations (including sorting/comparing) become effortless. If need to write these instances back into the db, then just use the date formatter (with the same pattern) to convert them back into strings. – Alexander Jan 11 '18 at 05:59
  • @kps2501 how do you create your BalanceUser entries when you get the results from Firebase. Can you show that code? – Upholder Of Truth Jan 11 '18 at 09:56
  • I will try that and I will put the Firebase Code in the post. Thanks. – kingkps Jan 11 '18 at 22:58
  • I changed the type of my date from String to Date and the type of my id to Int and it is all working perfectly now. Thanks for everyone's help is getting this solved. – kingkps Jan 12 '18 at 03:35
  • @Leo Dabus I didn't down vote anyone's answer. – kingkps Jan 12 '18 at 03:37

4 Answers4

3

It's sorting absolutely correct as you write it.

Strings comparing work for every char from the beginning. If first char is equal then check second and so on. In your results "10" < "2", cause unicode of "1"-character is less then "2"-character code.

You need to compare do it like this:

self.sortedTableArray.sort(by: {
   if $0.date != $1.date {
         return $0.date < $1.date
   } else {
         return Int($0.id) ?? 0 < Int($1.id) ?? 0
   }
})

Also your should compare dates as Date not Strings.

Alexey Tyurnin
  • 316
  • 2
  • 4
1

You can extend your BalanceUser adding computed properties to return year, month, day and id value. Next just make your struct conform to Comparable protocol:

extension BalanceUser: Comparable {
    var year: Int {
        return Int(date.suffix(4))!
    }
    var month: Int {
        return Int(date.prefix(2))!
    }
    var day: Int {
        return Int(date.prefix(5).suffix(2))!
    }
    var idValue: Int {
        return Int(id)!
    }
    static func ==(lhs: BalanceUser, rhs: BalanceUser) -> Bool {
        return lhs.date == rhs.date && lhs.id == rhs.id
    }
    static func <(lhs: BalanceUser, rhs: BalanceUser) -> Bool {
        return (lhs.year, lhs.month, lhs.day, lhs.idValue) < (rhs.year, rhs.month, rhs.day, rhs.idValue)
    }
}

Now you can simply sort your custom type array:

sortedTableArray.sort()
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Not really any reason to separately compare the year, month, and day in the `==` operator. Just compare `date` and `id` as-is. – rmaddy Jan 11 '18 at 05:59
  • @rmaddy thats true – Leo Dabus Jan 11 '18 at 06:00
  • 1
    *Tuples* can be used to sort by multiple criteria, see https://stackoverflow.com/a/37612765/1187415. – In this case: `return (lhs.year, lhs.month, lhs.day) < (rhs.year, rhs.month, rhs.day)` – Martin R Jan 11 '18 at 06:20
  • 2
    Or `(lhs.year, lhs.month, lhs.day, lhs.idValue) < (rhs.year, rhs.month, rhs.day, rhs.idValue)` – Martin R Jan 11 '18 at 06:27
  • @MartinR thanks. Didn't know that tuples are comparable – Leo Dabus Jan 11 '18 at 15:00
  • They are since Swift 2.2: https://github.com/apple/swift-evolution/blob/master/proposals/0015-tuple-comparison-operators.md. – Martin R Jan 11 '18 at 15:11
  • @MartinR The link it is not working but I think remember. They also set a maximum number of elements in the tuple for the comparison. Six I think. – Leo Dabus Jan 11 '18 at 15:16
  • 1
    GitHub seems to be offline. And yes, it is implemented for tuples up to arity 6. – Martin R Jan 11 '18 at 15:21
1

Just use the string comparator which sorts numeric strings properly

self.sortedTableArray.sort(by: {
    if $0.date != $1.date {
        return $0.date < $1.date
    } else {
        return $0.id.localizedStandardCompare($1.id) == .orderedAscending
    }
})

or the standard compare selector with option .numeric

return $0.id.compare($1.id, options: .numeric) == .orderedAscending
vadian
  • 274,689
  • 30
  • 353
  • 361
0

Please check the following code:

let array = ["11-07-2017", "14-07-2017", "10-07-2017","08-07-2017"]
var convertedArray: [Date] = []

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"// yyyy-MM-dd"

for dat in array {
    let date = dateFormatter.date(from: dat)
       if let date = date {
            convertedArray.append(date)
        }
    }

var ready = convertedArray.sorted(by: { $0.compare($1) == .orderedDescending })

print(ready)
shim
  • 9,289
  • 12
  • 69
  • 108
Mahesh kumar
  • 278
  • 4
  • 15
  • This answer completely misses the issue of sorting the id part which is what is actually causing the issue. The date part is sorting correctly. – Upholder Of Truth Jan 11 '18 at 09:53