Sort 2D Array in New Swift, previous sort not working

被刻印的时光 ゝ 提交于 2020-02-06 09:24:13

问题


Hi and thank you very much for your help. I'm new to swift and have thoroughly researched the other answers but they all are not working for me. I have sample data in the format:

["10-24-2015", "124", "Yes", "No", "No", "Yes", "Yes", "NA", "NA"]

There are only 3 entries with varying dates and values and they're all in a master array. It's been written to a file already and so I start my code below from a loaded file. All I would like to do is to sort the arrays by date which is the first value in each array.

The problem with most of the answers already posted were that they used older codes and I did my best to try to convert them unsuccessfully. I had been using XCode 7.1 beta until XCode 7.1 came out a couple of days ago. The closest I've got is below which gives me no compiler error but a big quit app exception error. I also included other ones that I tried that were close as comments and the errors I got.

var resultsArray = [[String]]()


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cellIdentifier = "resultsViewCell"
    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! resultsTableViewCell

    let resultsdocumentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let resultsfileURL = resultsdocumentsURL.URLByAppendingPathComponent("ResultsPlist.plist")

    resultsArray = NSKeyedUnarchiver.unarchiveObjectWithFile(resultsfileURL.path!) as! Array


    let Sort: NSSortDescriptor = NSSortDescriptor(key: "date", ascending: false)
    let sortedArray: NSArray = (resultsArray as NSArray).sortedArrayUsingDescriptors([Sort])


    // var sortedArray = resultsArray.sort {$0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }  
    // Error: Value of type '[String]' has no member 'localizedCaseInsensitiveCompare'

    // var sortedArray = resultsArray.sort({ $0.0 > $1.0 })
    // Error: Value of type '[String]' has no member '0'


    return cell

}

Forgot to mention that all the codes work no problem when I use a sample single array. But make it multidimensional then I get my exception errors.


回答1:


There are several issues to address here:

Sorting a Multidimensional Array

For testing, I've filled out your resultsArray format with a few bogus rows:

let resultsArray = [
    ["10-24-2015", "124", "Yes", "No", "No", "Yes", "Yes", "NA", "NA"],
    ["09-23-2015", "125", "Yes", "No", "No", "No", "Yes", "NA", "NA"],
    ["10-21-2015", "121", "Yes", "No", "Yes", "Yes", "Yes", "NA", "NA"],
    ["08-24-2015", "141", "Yes", "Yes", "No", "Yes", "Yes", "NA", "NA"],
    ["10-24-2014", "199", "Yes", "No", "No", "Yes", "No", "NA", "NA"],
]

Now, sorting this in Swift is pretty simple: call the sort function, and pass a closure that takes two (outer) array elements and returns true if the first should be sorted before the second. Your elements are of type [String] — that is, arrays of strings — so to sort by one of the sub-array elements you just need to look them up and compare them.

let sorted = resultsArray.sort { left, right in
    left[0] < right[0]
}
// or a shorter syntax:
let sorted2 = resultsArray.sort { $0[0] < $1[0] }

But this probably isn't what you want.

Sorting by Date

The above sorts strings lexicographically. This means that you get a result like this:

["08-24-2015", "141", "Yes", "Yes", "No", "Yes", "Yes", "NA", "NA"]
["09-23-2015", "125", "Yes", "No", "No", "No", "Yes", "NA", "NA"]
["10-21-2015", "121", "Yes", "No", "Yes", "Yes", "Yes", "NA", "NA"]
["10-24-2014", "199", "Yes", "No", "No", "Yes", "No", "NA", "NA"]
["10-24-2015", "124", "Yes", "No", "No", "Yes", "Yes", "NA", "NA"]

Your date format is month-day-year, so when you compare these as strings you don't get a temporal ordering ("08-24-2015" comes before "10-24-2014" because the first one starts with "08").

The better way to do this is to parse the dates so that you can compare them as dates. Here's a shot at that:

// parse actual dates
let formatter = NSDateFormatter()
formatter.dateFormat = "MM-dd-yyyy"
let sorted3 = resultsArray.sort { left, right in
    let leftDate = formatter.dateFromString(left[0])!
    let rightDate = formatter.dateFromString(right[0])!
    // NSDate doesn't have a < operator, so use the Foundation compare method
    return leftDate.compare(rightDate) == .OrderedAscending
}

This sorts the way you expect. You can probably get better, though.

Using a Model Object

You're probably going to want to do more with these, so it might help to treat the rows as objects where each field has some meaning. For example:

struct Result {
    let date: NSDate
    let number: Int
    let responses: [Bool?]
    init(array: [String]) {
        date = formatter.dateFromString(array[0])!
        number = Int(array[1])!
        responses = array[2..<array.count].map {
            switch $0 {
                case "Yes": return true
                case "No": return false
                default: return nil
            }
        }
    }
}

Now you can have real objects for each result, so you can better keep track of what's what in your later code. Let's make an array of Result objects, and sort it:

let results = resultsArray.map(Result.init).sort {
    $0.date.compare($1.date) == .OrderedAscending
}

Wait, how many times are we doing this?

You're doing all your data loading and sorting (and date parsing, and transforming to Result objects, if you followed my advice above) all in the tableView(_:cellForRowAtIndexPath:) method. This method gets called once for every row in your table... which, presumably due to you also loading the data in the tableView(_:numberOfRowsInSection:) method, is the number of results in your data file. That means you're doing the same work again and again and again, and slowing down the initial load of your table (as well as any subsequent reloads, and scrolling when the row cache overflows).

Move your data loading, parsing, and sorting code to someplace that only executes once (or once every time you change the underlying data file and want to reload its updated contents). Maybe viewDidLoad, maybe a utility method you can call again later. Have it save its results in an instance property of your table view controller. Then you can refer to those results much more quickly, whenever you need.

For example (shortened with some pseudocode):

class MyTableViewController: UITableViewController {

    let cellIdentifier = "resultsViewCell"
    var results: [Result]!

    override func viewDidLoad() {
        let resultsdocumentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
        let resultsfileURL = resultsdocumentsURL.URLByAppendingPathComponent("ResultsPlist.plist")
        guard let resultsArray = NSKeyedUnarchiver.unarchiveObjectWithFile(resultsfileURL.path!) as? [[String]]
            else { fatalError("unexpected results from plist") }
        results = resultsArray.map(Result.init).sort {
            $0.date.compare($1.date) == .OrderedAscending
        }
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return results.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ResultsTableViewCell
        let result = results[indexPath.row]
        cell.textLabel?.text = result.description // or however you display a result
    }
}


来源:https://stackoverflow.com/questions/33354144/sort-2d-array-in-new-swift-previous-sort-not-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!