NSSortComparator having no affect on NSMutableArray - objective-c

I am running the following code to sort my array in the order of ending soonest (nearest expiry_date to [NSDate date] (now).
However, whenever I run the comparator, it has no affect, at all on the array, and all objects retain their current positions. Please can you tell me where I am going wrong?
[self.questions sortUsingComparator:^NSComparisonResult(NSDictionary * question1, NSDictionary *question2) {
NSDate* dateq1 = [NSDate dateWithTimeIntervalSinceNow:[[question1 objectForKey:#"expiry_date"] floatValue]];
NSDate * dateq2 = [NSDate dateWithTimeIntervalSinceNow:[[question2 objectForKey:#"expiry_date"] floatValue]];
NSComparisonResult result = [dateq1 compare:dateq2];
NSLog(#"%#", result == NSOrderedAscending ? #"ASC" : result == NSOrderedDescending ? #"DESC" : #"SAME");
return result;
}];
The NSLog() returns the following:
DESC
DESC
ASC
I have tried reversing the comparison to have dateq2 in the place of dateq1 and vice versa, but the array never changes, even though the returned values do.

It looks like it is working to me (judging by your output), but maybe this is more succinct and therefore less error prone?
NSArray* unsorted;
NSSortDescriptor* descriptor = [NSSortDescriptor sortDescriptorWithKey: #"expiry_date" ascending: NO];
self.questions = [unsorted sortedArrayUsingDescriptors: #[ descriptor ] ];

Sort returns a new array; I don't think it changes the receiver. I can't tell if you're doing an assignment or not.

Related

Sort NSStrings containing date

I've got an array of dictionaries. Each dictionary has a string object for a key #"date". I need to sort the array by this date, descending. How can I do it the best way?
You can create a NSSortDescriptor, for example:
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:#"date" ascending:YES];
[array sortUsingDescriptors:#[sorter]];
This will use string comparison to compare your date strings. It should work if the date string is formatted correctly and consistently.
If i understood the structure of this array, I hope this answer helped! :)
I used this method to sort an array that holds an array that held anther array that finally held a string I wanted to sort the top array by. So obviously something simple doesn't work. But i ran into this:
- (void)sortUsingComparator:(NSComparator)cmptr;
In your case, i hope, this is what you'd use:
NSMutableArray *arrayItems = [NSMutableArray arrayWithObjects:
#{#"date":[NSDate date]},
#{#"date":[NSDate dateWithTimeIntervalSinceNow: 60*60*45]},
#{#"date":[NSDate dateWithTimeIntervalSinceNow: 60*60*25]},
#{#"date":[NSDate date]},
#{#"date":[NSDate dateWithTimeIntervalSinceNow: 60*60*5]},
#{#"date":[NSDate dateWithTimeIntervalSinceNow: 60*60*32]},
#{#"date":[NSDate date]},
#{#"date":[NSDate dateWithTimeIntervalSinceNow: 60*60]},
nil];
[arrayItems sortUsingComparator:^NSComparisonResult(id obj1, id obj2) { //obj1 and obj2 are objects in the list of the array. so in your case a list of dictionaries, so obj1 and obj2 are dictionaries, mutable
//And now, which is why you would use this block to sort your objects, there you'll access each date, or object, you want to have the 'power' to sort the objects inside the caller, in this case arrayItems
NSDate *date1 = [obj1 objectForKey: #"date"];
NSDate *date2 = [obj2 objectForKey: #"date"];
//And now you compare the two of whom is larger
return [date1 compare: date2];
}];
NSLog( #"%#", arrayItems);
I hope this helped, or hinted you to the answer :)
The best way will be to format that strings into NSDate objects and use compare: method.

Sort NSMutableArray by date and then by alphabetical order

I have an NSMutable array of objects. The objects represent football (soccer) matches and have an NSString parameter called title (ed "Arsenal v Chelsea"), and an NSDate parameter called ActualDate.
I am sorting the array by date at the moment using the following code:
NSMutableArray* a = [self getMatchListFromURL:#"http://www.url.com"];
[a sortUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"ActualDate" ascending:YES]]];
Obviously there are multiple games that happen on the same date. I would like to sort games that happen on the same date in alphabetical order. Is there a simple way to do this?
The method sortUsingDescriptors takes array of NSSortDescriptor as an argument. So you can pass multiple sort descriptors to method as follow:
NSSortDescriptor *sortAlphabetical = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
NSSortDescriptor *sortByDate = [NSSortDescriptor sortDescriptorWithKey:#"ActualDate" ascending:YES];
NSArray *sortDescriptors = #[sortAlphabetical, sortByDate];
//perform sorting
[a sortUsingDescriptors:sortDescriptors];
There are a few ways to implement this, but I think the most readable is to implement a comparison block, like so:
[a sortUsingComparator:^NSComparisonResult(SomeClass *obj1, SomeClass *obj2) {
NSComparisonResult dateCompare = [obj1.actualDate compare:obj2.actualDate];
if (dateCompare != NSOrderedSame) {
return dateCompare;
} else {
return [obj1.title compare:obj2.title];
}
}];
This will sort a first by its actualDate property, and if they're the same, then by the titleproperty. You can add additional logic if you need to.
You could, alternatively, add additional NSSortDescriptor objects to the array you pass to sortUsingDescriptors:, but I think that's less readable.

Error when combining a subquery in a NSPredicate with valueForKeyPath

I store time periods in Core Data. Each time period has an DateTime attribute called EndDate. I am trying to get the maximum end date, which is before (<) the date specified.
This is how I have coded this using a subquery and ValueForKeyPath:
NSString *keyPath = [NSString stringWithFormat:#"SUBQUERY(SELF, $x, $x.EndDate < %#).#max.EndDate", date];
IBFinPeriod *periodBeforeCurrentDate = [self.finperiod valueForKeyPath:keyPath];
However, when running this code, I get the runtime error: the entity IBFinPeriod is not key value coding-compliant for the key "SUBQUERY(SELF, $x, $x".'
What is wrong with my code?
Do I need to specify the subquery differently?
Thank you for your help!!
You could use a fetch request with fetchLimit set to 1 and a descending sort descriptor.
If you insist on the valueForKeyPath: I would first filter the results with filteredArrayUsingPredicate: (with a straight forward predicate selecting the records with dates prior to your date) and then simply using #"#max.EndDate" as the key path.
If you need the entire object rather than just the date, just sort your set:
NSSet *periodsBeforeCurrentDate = [self.finperiod
filteredSetUsingPredicate:[NSPredicate predicateWithFormat:
#"EndDate < %#", date]];
if (periodsBeforeCurrentDate.count) {
*sort = [NSSortDescriptor sortDescriptorWithKey:#"EndDate" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sort];
IBFinPeriod *lastPeriodBeforeCurrentDate =[[periodsBeforeCurrentDate
sortedArrayUsingDescriptors:sortDescriptors] objectAtIndex:0];
}
In my opinion it would be easier to just fetch.

writing a plist in objective c

I know there are many topics with similar issues, but I have not been able to find a topic addressing my question.
I want to store a plist of highscores.
Every entry of highscores must have two elements
an NSString* and an int.
I want to store the top 20 high scores (pairs of strings and ints) and do that in a plist.
I start with:
NSMutableArray *arr = [[NSMutableArray alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:#"Mylist" ofType:#"plist"]];
I want the item 0 of the array to be a dictionary, where I can insert key value pairs of
(string, int)
How do I do that?
You can always call [arr addObject:score];, sort it, and remove the final item until there are 10.
To sort:
[arr sortUsingComparator:^(id firstObject, id secondObject) {
NSDictionary *firstDict = (NSDictionary *)firstObject;
NSDictionary *secondDict = (NSDictionary *)secondObject;
int firstScore = [[firstDict objectForKey:#"score"] intValue];
int secondScore = [[secondDict objectForKey:#"score"] intValue];
return firstScore < secondScore ? NSOrderedAscending : firstScore > secondScore : NSOrderedDescending : NSOrderedSame;
}];
If you want the scores to be the other way around, change the '>' to '<' and vice-versa. To keep the list down to 10:
while ([arr count] > 10) {
[arr removeLastObject];
}
You may have to sort when you load from your plist. For 10 scores the performance hit will be minimal, so I suggest you do it just in case.
Property List Serialization
You will want to make notice of: the mutability option, as your method probably returns immutable arrays...
storing in a plist is done with the writeToFile:... or writeToURL:... methods
[arr insertObject:[NSMutableDictionary dictionary] atIndex:0];

Sorting NSStrings of Numbers

So I have an NSDictionary where the keys are years as NSString's and the value for each key is also an NSString which is sort of a description for the year. So for example, one key is "943 B.C.", another "1886". The problem I am encountering is that I want to sort them, naturally, in ascending order.
The thing is that the data source of these years is already in order, it's just that when I go ahead and call setValue:forKey the order is lost, naturally. I imagine figuring out a way to sort these NSString's might be a pain and instead I should look for a method of preserving the order at the insertion phase. What should I do? Should I instead make this an NSMutableArray in which every object is actually an NSDictionary consisting of the key being the year and the value being the description?
I guess I just answered my own question, but to avoid having wasted this time I'll leave this up in case anyone can recommend a better way of doing this.
Thanks!
EDIT: I went ahead with my own idea of NSMutableArray with NSDictionary entries to hold the key/value pairs. This is how I am accessing the information later on, hopefully I'm doing this correctly:
// parsedData is the NSMutableArray which holdes the NSDictionary entries
for (id entry in parsedData) {
NSString *year = [[entry allKeys] objectAtIndex:0];
NSString *text = [entry objectForKey:year];
NSLog(#"Year: %#, Text: %#", year, text);
}
Maintain a NSMutableArray to store the keys in order, in addition to the NSDictionary which holds all key-value pairs.
Here is a similar question.
You could either do it as an array of dictionaries, as you suggest, or as an array of strings where the strings are the keys to your original dictionary. The latter is probably a simpler way of going about it. NSDictionary does not, as I understand it, maintain any particular ordering of its keys, so attempting to sort the values there may be unwise.
I needed to solve a similar problem to sort strings of operating system names, such as "Ubuntu 10.04 (lucid)".
In my case, the string could have any value, so I sort by tokenizing and testing to see if a token is a number. I'm also accounting for a string like "8.04.2" being considered a number, so I have a nested level of tokenizing. Luckily, the nested loop is typically only one iteration.
This is from the upcoming OpenStack iPhone app.
- (NSComparisonResult)compare:(ComputeModel *)aComputeModel {
NSComparisonResult result = NSOrderedSame;
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSArray *tokensA = [self.name componentsSeparatedByString:#" "];
NSArray *tokensB = [aComputeModel.name componentsSeparatedByString:#" "];
for (int i = 0; (i < [tokensA count] || i < [tokensB count]) && result == NSOrderedSame; i++) {
NSString *tokenA = [tokensA objectAtIndex:i];
NSString *tokenB = [tokensB objectAtIndex:i];
// problem: 8.04.2 is not a number, so we need to tokenize again on .
NSArray *versionTokensA = [tokenA componentsSeparatedByString:#"."];
NSArray *versionTokensB = [tokenB componentsSeparatedByString:#"."];
for (int j = 0; (j < [versionTokensA count] || j < [versionTokensB count]) && result == NSOrderedSame; j++) {
NSString *versionTokenA = [versionTokensA objectAtIndex:j];
NSString *versionTokenB = [versionTokensB objectAtIndex:j];
NSNumber *numberA = [formatter numberFromString:versionTokenA];
NSNumber *numberB = [formatter numberFromString:versionTokenB];
if (numberA && numberB) {
result = [numberA compare:numberB];
} else {
result = [versionTokenA compare:versionTokenB];
}
}
}
[formatter release];
return result;
}