NSSortDescriptor: Custom comparison on multiple keys simultaneously - objective-c

I have an custom object which contains time period based information, for instance the attributes endCalYear, endMonth and periodLength which indicate the end of each period and its length.
I would like to create an NSSortDescriptor based or other sorting method which combines these three attributes and allows sorting this object on all three keys at the same time.
Example:
EndCalYear endMonth periodLength (months) sortOrder
2012 6 6 1
2012 6 3 2
2012 3 3 3
2011 12 12 4
The sort algorithm would be completely discretionary based on my own algorithm.
How could I code such an algorithm?
The block based sortDescriptorWithKey:ascending:comparator: method won't work in my view because it would allow me to specify only one sorting key. However, I need to sort on all three keys at the same time.
Any ideas or thoughts on how to solve this?
Thank you!

You could sort with a block instead:
NSArray *sortedArray;
sortedArray = [myArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
MyObject *first = (MyObject*)a;
MyObject *second = (MyObject*)b;
if (first.endCalYear < second.endCalYear) {
return NSOrderedAscending;
}
else if (first.endCalYear > second.endCalYear) {
return NSOrderedDescending;
}
// endCalYear is the same
if (first.endMonth < second.endMonth) {
return NSOrderedAscending;
}
else if (first.endMonth > second.endMonth) {
return NSOrderedDescending;
}
// endMonth is the same
if (first.periodLength < second.periodLength) {
return NSOrderedAscending;
}
else if (first.periodLength > second.periodLength) {
return NSOrderedDescending;
}
// periodLength is the same
return NSOrderedSame;
}]
This sorts by endCalYear ascending, then endMonth ascending and finally periodLength ascending. You could modify it to change the order or switch the signs in the if statement to make it descending.
For NSFetchedResultsController you might want to try something else:
It looks like you can pass it a list of descriptors, one for each column that you want sorted:
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSSortDescriptor *descriptor1 = [[NSSortDescriptor alloc] initWithKey:#"endCalYear" ascending:YES];
NSSortDescriptor *descriptor2 = [[NSSortDescriptor alloc] initWithKey:#"endMonth" ascending:YES];
NSSortDescriptor *descriptor3 = [[NSSortDescriptor alloc] initWithKey:#"periodLength" ascending:YES];
NSArray *sortDescriptors = #[descriptor1, descriptor2, descriptor3];
[fetchRequest setSortDescriptors:sortDescriptors];

APIs for sorting are generally capable to use an array of NSSortDescriptors, not just one, so why not use them?
For example, NSArray has a method called sortedArrayUsingDescriptors: (note the plural form) which takes an array of NSSortDescriptor objects.
So you can simply write this:
NSSortDescriptor *endCalYearSD = [NSSortDescriptor sortDescriptorWithKey:#"endCalYear" ascending:YES];
NSSortDescriptor *endMonthSD = [NSSortDescriptor sortDescriptorWithKey:#"endMonth" ascending:YES];
NSSortDescriptor *periodLenSD = [NSSortDescriptor sortDescriptorWithKey:#"periodLength" ascending:YES];
NSArray *sortedArray = [originalArray sortedArrayUsingDescriptors:#[endCalYearSD, endMonthSD, periodLenSD]];
This way, your originalArray will be sorted first by endCalYear, each entry with the same endCalYear will be sorted by endMonth, and each entry with same endCalYear and endMonth will then be sorted by periodLendth.
You have APIs that use an array of sortDescriptors for most of the APIs that propose sorting (including CoreData and such) so the principle is the same all the time.
If you really need to stick with only one NSSortDescriptor (and your sorting algorithm isn't flexible enough to use a block-based comparator or an array of NSSortDescriptors), you can simply provide a property to your custom object that compute some value on which you can base your sorting algorithm.
For example add such method to your custom class:
-(NSUInteger)sortingIndex {
return endCalYear*10000 + endCalMonth*100 + periodLength;
}
Then sort on this key/property. This is not really very clean to read and a very pretty design pattern, but the better way would be to alter your sorting algorithm API to allow sorting on multiple keys at once, so…
[EDIT] (to answer your [EDIT] on block-based API)
I don't see why the block-based API sortDescriptorWithKey:ascending:comparator: won't work for you. You can specify whatever custom NSComparator block you need there, so this block can tell, given two objects, which one is before the other. The way you determine which one is before which one is up to you, you can compare only endCalYear, or endCalYear and endMonth, etc. so there is no limitation here about sorting using multiple keys.

Related

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.

NSArray sorted by function

I have an NSArray containing several NSDictionary instances. Each NSDictionary has, among other fields, an object named rating and one numberOfVotes(both are int). How can I sort the array so it gets sorted by rating/numberOfVotes? More generically, can I sort it by doing an operation like mentioned above? Or would it be better to just add another object to each NSDictionary with the value of each operation and then sort by that?
Thanks
EDIT - I have added the following. Still not sorting properly. One question: Should this work for more than 2 objects in my array. (The number of objects will vary)
[sortedArray sortedArrayUsingComparator:^NSComparisonResult(id dict1, id dict2)
{
MyObj *obj1 = (MyObj *)dict1;
MyObj *obj2 = (MyObj *)dict2;
int rating1 = obj1.rating.intValue;
int rating2 = obj2.rating.intValue;
int number1 = obj1.number_of_votes.intValue;
int number2 = obj2.number_of_votes.intValue;
double key1 = ((double)rating1)/number1;
double key2 = ((double)rating2)/number2;
if (key1 < key2)
{
return NSOrderedDescending;
}
if (key2 < key1)
{
return NSOrderedAscending;
}
return NSOrderedSame;
}];
You can define a custom comparator to use a composite sorting key of the kind that you are looking for. If there is no good reason to have that sorting key in the dictionary, other than performing the sort, do not add an item to the dictionary.
array = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
int rating1 = [[obj1 objectForKey:#"rating"] intValue];
int numberOfVotes1 = [[obj1 objectForKey:#"numberOfVotes"] intValue];
int rating2 = [[obj2 objectForKey:#"rating"] intValue];
int numberOfVotes2 = [[obj2 objectForKey:#"numberOfVotes"] intValue];
double key1 = ((double)rating1)/numberOfVotes1;
double key2 = ((double)rating2)/numberOfVotes2;
if (key1 > key2) {
return (NSComparisonResult)NSOrderedDescending;
}
if (key1 < key2) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Note: The sortedArrayUsingComparator: method does not sort the array in place; instead, it returns a sorted copy. If you would like an in-place sorting, use NSMutableArray.
The adequate way would be using blocks like this
NSArray *sortedArray;
sortedArray = [unsortedArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
float first = [[(NSDictionary*)a objectForKey:#"rating"] floatValue]/
[[(NSDictionary*)a objectForKey:#"numberOfVotes"] floatValue];
float second = [[(NSDictionary*)a objectForKey:#"rating"] floatValue]/
[[(NSDictionary*)a objectForKey:#"numberOfVotes"] floatValue];
return [[NSNumber numberWithFloat:first] compare: [NSNumber numberWithFloat:second]];
}];
you may find more ways to compare:
How to sort an NSMutableArray with custom objects in it?
The family of methods you seek are prefixed with sortedArray.
For a function (which is one of multiple options), see: -[NSArray sortedArrayUsingFunction:context:].
Edit: If you want to do rating divided by numberOfVotes, you will have to sort by a function or comparator block or replace the dictionary with a model object which has a method to calculate rating / numberOfVotes.
Use NSSortDescriptor.
NSArray *sorted = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"rating" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"numberOfVotes" ascending:YES],
nil]
];
Filtering data is one of the essential tasks in computing.
You can sort it several ways.
NSPredicate
You can see examples here
NSSortDescriptor
You can see examples here
Regular Expressions
Set Operations
Key-Value Coding
Ideally, you should substitute NSDictionary by a custom object, with properties like rating and numberOfVotes. Then, you could declare a method compare: on this custom object and use as the selector to sort the array.
You can use a block code to sort the objects but abstracting the implementation into a custom object is much cleaner.
you can use NSSortDescriptor here for sorting for example your array that containing several NSDictionnaries name is dataArray.
NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:rating
ascending:YES];
NSArray * keyArrray = [NSArray arrayWithObjects:descriptor, nil];
NSArray * sortedArray = [dataArray sortedArrayUsingDescriptors:keyArrray];
With the help of NSSortDescriptor we can sort any data type key value pair.

Sorting NSMutableArrays alphabetically

I have an NSMutableArray, called categories, which contains an object called CategoryItem. CategoryItem has an NSString property, *text. Now how would I sort this Array based on the text property of the elements? Sorry if this doesn't make sense, I'm new to all this.
I tried this:
[categories sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
But it failed.
That's because you're trying to sort the array based on each object, not on the string it contains.
You need to use an instance of NSSortDescriptor to sort your array. You can create one like this:
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"text" ascending:YES];
Then:
[categories sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
Try this:
[categories sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
CategoryItem object1 = (CategoryItem)obj1;
CategoryItem object2 = (CategoryItem)obj2;
return [object1.text compare:object2.text];
}];
Hope that helps!
It fails because you did not implement localizedCaseInsensitiveCompare for CategoryItem.
If you want to do that, implement that function for CategoryItem like
- (NSComparisonResult)localizedCaseInsensitiveCompare:(CategoryItem *)anItem
{
return [anItem.stringProperty localizedCaseInsensitiveCompare:self.stringProperty];
}
You kinda did this right.
The problem being that you're trying to sort strings, but you dont have strings, you have CategoryItems. To keep the same code, you would just implement localizedCaseInsensitiveCompare: for CategoryItem. In that function, compare the text value and return the correct NSComparisonResult. Probably something like this
-(NSComparisonResult)localizedCaseInsensitiveCompare:(CategoryItem *)item2 {
return [text localizedCaseInsensitiveCompare:[item2 text]];
}

How to fetch data from a Nested Array according a key in a array

Hello frnds actually i have a array of arrays as given below:-`
parrent array{
PrArr =
(
{
dt = "01-Apr-2012 11:15 PM\n";
dur = "15\n\t\t";
name = "Tez Special\n\t\t";
},
{
dt = "01-Apr-2012 11:30 PM\n\n";
dur = "30\n\t\t";
name = "Tez Tare\n\t\t";
}
);
sid = "530\n";
},
{
PrArr = (
{
dt = "01-Apr-2012 11:20 PM\n";
dur = "20\n\t\t";
name = "Shiv Yog - Acharya Ishan Shivanandji\n\t\t";
},
{
dt = "01-Apr-2012 11:40 PM\n\n\n\t";
dur = "20\n\t\t";
name = "Param Pujya Swami HariChaitanya Puriji Maharaj\n\t\t";
};
sid = "560\n";
},
}
i want to fetch data according to sid in a array.
Thanx in advance
If you are trying to do find the dictionary where the sid is equal a something, then you will need to use NSPredicate.
From apple documentation:
Predicates provide a general means of specifying queries in Cocoa. The
predicate system is capable of handling a large number of domains, not
just—for example—Core Data or Spotlight. This document describes
predicates in general, their use, their syntax, and their limitations.
With NSPredicate you can find all sid that correspond to your search.
I will give you an example. Based on the array you posted here, you want to return the entry that have the sid equals to #"530\n", what you will need to do is:
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"sid LIKE %#",#"530\n"];
NSDictionary* entry = [[parentArray filteredArrayUsingPredicate:predicate] objectAtIndex:0];
But if you are trying to sort your array based on the sid, you can use the NSSortDescriptor, that will return your array sorted based in the sid.
From apple documentation:
A sort descriptor describes a comparison used to sort a collection of
objects. You create an instance of NSSortDescriptor that specifies the
property key to be sorted, and whether the comparison should be in
ascending, or descending order. A sort descriptor can also specify a
method to use when comparing the property key values, rather than the
default of compare:.
It is important to remember that NSSortDescriptor does not sort
objects. It provides the description of how to sort objects. The
actual sorting is done by other classes, often NSArray or
NSMutableArray.
One example for you
NSSortDescriptor *sidDescriptor= [[NSSortDescriptor alloc] initWithKey:#"sid" ascending:YES];
NSArray* sortDescriptors = [NSArray arrayWithObject:sidDescriptor];
NSArray* sortedArray = [parentArray sortedArrayUsingDescriptors:sortDescriptors];
I am just tentatively scribbling it, Check if something like this helps you.
NSSortDescriptor *sidDescr = [[NSSortDescriptor alloc] initWithKey:#"sid" ascending:YES];
sortDescriptors = [NSArray arrayByAddingObject:sidDescr];
NSArray * sortedArr = [*yourparentarray* sortedArrayUsingDescriptors:sortDescriptors];

How can I sort strings in NSMutableArray into alphabetical order?

I have a list of strings in an NSMutableArray, and I want to sort them into alphabetical order before displaying them in my table view.
How can I do that?
There is the following Apple's working example, using sortedArrayUsingSelector: and localizedCaseInsensitiveCompare: in the Collections Documentation:
sortedArray = [anArray sortedArrayUsingSelector:
#selector(localizedCaseInsensitiveCompare:)];
Note that this will return a new sorted array. If you want to sort your NSMutableArray in place, then use sortUsingSelector: instead, like so:
[mutableArray sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
Here's an updated answer for Swift:
Sorting can be done in Swift with the help of closures. There are two methods - sort and sorted which facilitate this.
var unsortedArray = [ "H", "ello", "Wo", "rl", "d"]
var sortedArray = unsortedArray.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
Note: Here sorted will return a sorted array. The unsortedArray itself will not be sorted.
If you want to sort unsortedArray itself, use this
unsortedArray.sort { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
Reference :
Here is the documentation for Swift's sorting methods.
Docs for different String comparison methods here.
Optional:
Instead of using localizedCaseInsensitiveCompare, this could also be done:
stringArray.sort{ $0.lowercaseString < $1.lowercaseString }
or even
stringArray.sort{ $0 < $1 }
if you want a case sensitive comparison
It's easy to get the ascending order. Follow the bellow steps,,,
Model 1 :
NSSortDescriptor *sortDesc = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
sortedArray=[yourArray sortedArrayUsingDescriptors:#[sortDesc]];
If you want the sorting to be case insensitive, you would need to set the descriptor like this,,
Model 2 :
NSSortDescriptor * sortDesc = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
sortedArray=[yourArray sortedArrayUsingDescriptors:#[sortDesc]];
For me this worked....
areas = [areas sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
Here areas is NSArray. I hope it helps someone.