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]];
}
Related
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.
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.
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.
If I have a large NSDirectory typically from a parsed JSON-object I can access this object with code like so:
[[[[[obj objectForKey:#"root"] objectForKey:#"key1"] objectAtIndex:idx] objectForKey:#"key2"] objectAtIndex:idz];
The line might be a lot longer than this.
Can I optimize this code in any way? At least make it easier to read?
This line will also generate a runtime-error if the object does not correspond, what is the most efficient way to avoid that?
If you were using -objectForKey: for everything you could use -valueForKeyPath:, as in
[obj valueForKeyPath:#"key1.key2.key3.key4"]
However, this doesn't work when you need to use -objectAtIndex:. I don't think there's any good solution for you. -valueForKeyPath: also wouldn't solve the problem of the runtime errors.
If you truly want a simple way to do this you could write your own version of -valueForKeyPath: (call it something else) that provides a syntax for specifying an -objectAtIndex: instead of a key, and that does the appropriate dynamic checks to ensure the object actually responds to the method in question.
If you want easier to read code you can split the line into several lines like this
MyClass *rootObject = [obj objectForKey:#"root"];
MyClass *key1Object = [rootObject objectForKey:#"key1"];
MyClass *myObject = [key1Object objectAtIndex:idx];
...
and so forth.
I think, you can create some array, that will contain full "path" to your object. The only thing, you need to store your indexes somehow, maybe in NSNumber, in this case you cannot use NSNumber objects as keys in your dictionaries. Then create a method, that will return needed object for this given "path". smth like
NSMutableArray* basePath = [NSMutableArray arrayWithObjects: #"first", [NSNumber numberWithInt:index], nil];
id object = [self objectForPath:basePath inContainer:container];
- (id) objectForPath:(NSMutableArray*)basePath inContainer:(id)container
{
id result = nil;
id pathComponent = [basePath objectAtIndex: 0];
[basePath removeObjectAtIndex: 0];
// check if it is a number with int index
if( [pathComponent isKindOfClass:[NSNumber class]] )
{
result = [container objectAtIndex: [pathComponent intValue]];
}
else
{
result = [container objectForKey: pathComponent];
}
assert( result != nil );
// check if it is need to continue searching object
if( [basePath count] > 0 )
{
return [self objectForPath:basePath inContainer: result];
}
else
{
return result;
}
}
this is just an idea, but I hope you understand what I mean. And as Kevin mentioned above, if you don't have indexes, you can use key-value coding.
Don't know if it can suit you, but you could also give a try to blocks, I always find them very convenient. At least they made code much more readable.
NSArray *filter = [NSArray arrayWithObjects:#"pathToFind", #"pathToFind2",nil];
NSPredicate *filterBlock = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
NSArray *root = (NSArray*)obj;
// cycle the array and found what you need.
// eventually implementing some sort of exit strategy
}];
[rootObject filteredArrayUsingPredicate:filterBlock];
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.