0

I am trying to sort an array of objects that has a string date field in the from of "MM-DD-YYYY" and a boolean field. I want to create a function that sorts the array by both the date field and by the boolean field. I am having a hard time figuring out how to do this. The done field is default set to false and is not needed in my init func.

var items = [BucketItem(title: "blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-14-2017"),BucketItem(title: "blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-11-2017"), BucketItem(title: "blah blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-9-2017")]

I looked at the .sort function but I don't how to compare the dates since they are in string format and I also don't know how to sort by two fields rather than just one. I want to sort so that if the boolean is true, then it is automatically less then an item with a boolean field of false. If both booleans are false, then it is sorted by date.

6
  • 3
    There is no Boolean field in your example? Commented Feb 15, 2017 at 17:03
  • Create a DateFormatter, with the right dateFormat, then let date0 = dateformatter.date(from: $0.dat!) and let date1 = dateformatter.date(from: $1.dat!), and what's your logic with the boolean? How is done the sort? Commented Feb 15, 2017 at 17:15
  • 1
    Possible duplicate of Swift - Sort array of objects with multiple criteria Commented Feb 15, 2017 at 17:17
  • 1
    @Grimxn Chaining sorts like that doesn't work Commented Feb 15, 2017 at 17:29
  • @Grimxn That's a coincidence, it doesn't work in the general case. sorted vs sort doesn't matter, either Commented Feb 15, 2017 at 17:43

2 Answers 2

2

I would create an internal extension of Date that defines an initializer that takes a string, and parses it into a Date. You can use this in your entire module to parse dates according to your project's choice of date string format.

import Foundation

internal extension Date {
    init(_ s: String) {
        let df = DateFormatter()
        df.dateFormat = "MM-d-yyyy"
        guard let date = df.date(from: s) else {
            fatalError("Invalid date string.")
        }
        self.init(timeIntervalSince1970: date.timeIntervalSince1970)
    }
}

Then I would modify your structure to store the Date, rather than the String. Of course, modify the initializer to match.

let items = [
    BucketItem(
        title: "blah",
        des: "description",
        lat: 134.6, lon: 27.0,
        date: Date("02-14-2017")
    ),
    BucketItem(
        title: "blah",
        des: "description",
        lat: 134.6, lon: 27.0,
        date: Date("02-11-2017")
    ),
    BucketItem(
        title: "blah blah",
        des: "description",
        lat: 134.6, lon: 27.0,
        date: Date("02-9-2017")
]

From here, you have Comparable Date instances that you can use in your sorting. To see how to sort based off multiple criteria, see my answer here. Here's the rough code:

extension BucketItem: Equatable {
    static func ==(lhs: BucketItem, rhs: BucketItem) -> Bool {
        return lhs.isDone == rhs.isDone
            && lhs.date == rhs.date
            // && lhs.foo == rhs.foo
            // ...and so on, for all criteria that define equality of two BucketItems
    }
}

func <(lhs: Bool, rhs: Bool) -> Bool {
    return !lhs && rhs // false is less than true
}

extension BucketItem: Comparable {
    static func <(lhs: BucketItem, rhs: BucketItem) -> Bool {
        // First sort by isDone
        if (lhs.isDone != rhs.isDone) { return lhs.isDone < rhs.isDone }
        // else if (lhs.foo != rhs.foo) { return lhs.foo < rhs.foo }
        // ...and so on, for all sort criteria
        else (lhs.date != rhs.date) { return lhs.date < rhs.date }
    } 
}

letsortedItems = items.sorted()
Sign up to request clarification or add additional context in comments.

7 Comments

does the internal extension go in the class that defines BucketItem?
@user4151797 nope, and it can't be
Oh so then where do you define the internal extension ?
@user41517979 anywhere at the top level scope. For something like this (that you'll likely use in many places across your project), it may be worth putting it in its own file, but it's not necessary
I did as you said but my sorting function is still giving me issues: func tableViewSorter(item: BucketItem){ items.sort{ if $0.done && !$1.done{ //item $0 is less return $0 < $1 } else if !$0.done && $1.done{ //item $0 is greater return $0 > $1 } else if $0.done && $1.done { //want to compare by date return $0.date < $1.date } else{ //compare by date as well return $0.date < $1.date } } }
|
0

You can define your BucketItem as

struct BucketItem {
let title: String
let des: String
let lat: Double
let lon: Double
let dat: String
let someBoolValue : Bool
var date: NSDate {
    get {
        return BucketItem.dateFormatter.dateFromString(dat)!
    }
}
static let dateFormatter: NSDateFormatter = {
    let formatter = NSDateFormatter()
    formatter.dateFormat = "MM-dd-yyyy"
    return formatter
    }()

}

Then call sort method on the array

 var items = [BucketItem(title: "blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-14-2017", someBoolValue: false),BucketItem(title: "blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-11-2017", someBoolValue :true), BucketItem(title: "blah blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-9-2017",someBoolValue: false),  BucketItem(title: "blah blah", des: "description", lat: 134.6, lon: 27.0, dat: "02-9-2017",someBoolValue: true)]
    items.sortInPlace{
        if $0.date != $1.date {
            return $0.date.compare($1.date) == NSComparisonResult.OrderedAscending
        }
        else {
            return !$0.someBoolValue && $1.someBoolValue
        }
    }

4 Comments

($0.someBoolValue != $1.someBoolValue) && $0.someBoolValue == false can just be !$0.someBoolValue && $1.someBoolValue
Also, I think it's better to take a date in the initializer. Adding the date conversion within BucketItem makes it less flexible; it forces you to use that one and only conversion. It's much better to just take a Date parameter, and let the consumer do the formatting as they wish
@Alexander Thank you for the feedback :)
why not simply $0.date < $1.date? You can do that in Swift 3.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.