问题
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