I’ve already discussed previously how to sort NSArray with descriptors and selectors. Even how to unsort it i.e. shuffle it. You can use descriptors to sort by KVC-compliant properties. Selectors you would use if the objects contained in the array have a comparison method themselves.
For those special cases where you neither have properties nor a suitable sort method built into the objects to be sorted you have to resort to the third and last method of sorting an NSArray: with a specialized sort function.
For two projects I need to sort an array of NSDictionaries. Here I’ll show you how I did it.
The first example I invented for the maker of the Super Trumps game series. The game has all the cards informations in NSDictionary where the cards themselves are dictionaries. Those needed to be sorted for a variety of fields contained as keys inside the inner dictionaries.
My approach has two sorting functions depending on whether or not you want ascending or descending order. The third parameter I used to pass a string to specify the field to be sorted by.
NSInteger cardSortAsc(id card1, id card2, void *keyForSorting) { int v1 = [[card1 objectForKey:(NSString *)keyForSorting] intValue]; int v2 = [[card2 objectForKey:(NSString *)keyForSorting] intValue]; if (v1 v2) return NSOrderedDescending; else return NSOrderedSame; } NSInteger cardSortDesc(id card1, id card2, void *keyForSorting) { int v1 = [[card1 objectForKey:(NSString *)keyForSorting] intValue]; int v2 = [[card2 objectForKey:(NSString *)keyForSorting] intValue]; if (v1 > v2) return NSOrderedAscending; else if (v1 < v2) return NSOrderedDescending; else return NSOrderedSame; } - (NSArray *) cardsIn:(NSDictionary *)cards sortedBy:(NSString *)keyForSorting ascending:(BOOL)ascending { // first transfer the dictionary into an array, moving the key into a sub of each card dict NSMutableArray *unsortedCards = [NSMutableArray array]; for (NSString *oneCardKey in cards) { NSMutableDictionary *tmpCard = [[cards objectForKey:oneCardKey] mutableCopy]; [tmpCard setObject:oneCardKey forKey:@"index"]; // add the card key as field "index" [unsortedCards addObject:tmpCard]; [tmpCard release]; } // sort into a new array NSArray *sortedCards; if (ascending) { sortedCards = [unsortedCards sortedArrayUsingFunction:cardSortAsc context:keyForSorting]; } else { sortedCards = [unsortedCards sortedArrayUsingFunction:cardSortDesc context:keyForSorting]; } // now extract the indexes into their own array NSMutableArray *tmpArray = [NSMutableArray array]; /* -- variant returning all cards for (NSDictionary *oneCard in sortedCards) { [tmpArray addObject:[oneCard objectForKey:@"index"]]; } */ /* -- variant returning only first 10 cards */ for (int i=0;i<10;i++) { NSDictionary *oneCard = [sortedCards objectAtIndex:i]; [tmpArray addObject:[oneCard objectForKey:@"index"]]; } NSArray *retArray = [NSArray arrayWithArray:tmpArray]; // make immutable, autoreleased return retArray; } - (void)applicationDidFinishLaunching:(UIApplication *)application { // Override point for customization after app launch [window addSubview:viewController.view]; [window makeKeyAndVisible]; // TEST FOLLOWING // load dictionary of cards NSBundle *thisBundle = [NSBundle bundleForClass:[self class]]; // alternate method to get bundle NSString *cardsPath = [thisBundle pathForResource:@"cards" ofType:@"plist"]; NSDictionary *cards = [NSDictionary dictionaryWithContentsOfFile:cardsPath]; NSArray *keysSortedByCapacity = [self cardsIn:cards sortedBy:@"capacity" ascending:NO]; // test output for (NSString *cardKey in keysSortedByCapacity) { NSLog([[cards objectForKey:cardKey] description]); } } |
The second example I wrote up today I am needing for an upcoming version of LuckyWheel which will have artificial intelligence players. For this purpose I needed to go through all my questions and count how often one letter appears. Then I sorted the letters by the count descending.
NSInteger letterSortDesc(id letter1, id letter2, void *dummy) { int v1 = [[letter1 objectForKey:@"Count"] intValue]; int v2 = [[letter2 objectForKey:@"Count"] intValue]; if (v1 > v2) return NSOrderedAscending; else if (v1 < v2) return NSOrderedDescending; else return NSOrderedSame; } - (NSArray *) lettersSortedByUsage { NSMutableDictionary *letterCounts = [NSMutableDictionary dictionary]; // autoreleased NSMutableArray *letterCountsArray = [NSMutableArray array]; // all sentences for (NSDictionary *oneSentenceDict in sentences) { NSString *oneSentence = [oneSentenceDict objectForKey:@"String"]; // go through all characters for (int i=0; i<[oneSentence length];i++) { NSString *letter = [[oneSentence substringWithRange:NSMakeRange(i, 1)] uppercaseString]; NSRange r = ([letter rangeOfCharacterFromSet:[NSCharacterSet uppercaseLetterCharacterSet]]); if (r.location==0) // letter is alpha { NSMutableDictionary *letterDict = [letterCounts objectForKey:letter]; if (!letterDict) { // did not encounter letter before letterDict = [NSMutableDictionary dictionary]; [letterDict setObject:[NSNumber numberWithInt:1] forKey:@"Count"]; [letterDict setObject:letter forKey:@"Letter"]; [letterCounts setObject:letterDict forKey:letter]; [letterCountsArray addObject:letterDict]; } else { // add 1 to existing letter int newInt = [[letterDict objectForKey:@"Count"] intValue]+1; NSNumber *newNum = [NSNumber numberWithInt:newInt]; [letterDict setObject:newNum forKey:@"Count"]; } } } } // now we need to sort the array by descending count NSArray *sortedLetters = [letterCountsArray sortedArrayUsingFunction:letterSortDesc context:nil]; return [NSArray arrayWithArray:sortedLetters]; // immutable, autoreleased } |
The method is identical regarding the method of sorting. By defining a function like this you can even sort more complex objects where neither selectors or descriptors can be used.
Categories: Recipes
Hi Oliver, this is by far the best example I have found that describes sorting arrays of dictionary objects. I have followed your article but it didn’t work for me. I have based my app on TheElements example available online under developer.apple.com
When I build the project I get one warning that says NSMutableArray may not respond to – sortedArrayUsingFunction
And when I run the application there is an exception in this line (cardsIn function):
NSMutableDictionary *tmpCard = [[cards objectForKey:oneCardKey] mutableCopy];
2009-06-23 22:13:15.221 Project1[417:20b] *** -[card mutableCopyWithZone:]: unrecognized selector sent to instance 0x553a30
It seems that mutableCopy cannot be used due to the way that the array was declared.
In my .h file the dictionary has this property declaration
@property (nonatomic,retain) NSMutableDictionary *cards;
(I have renamed my variables to match the ones on your sample code.)
What should I change on my code to make it work? Thanks in advance for your help.
Javier
Hello Javier,
you cannot sort dictionaries, only arrays. Dictionaries are organized non-linear so that their elements can be quickly retrieved via key. You can get an array of keys for the dictionary with method allKeys. Then you can sort the key array and retrieve the dictionary elements in order.
I have checked your blog and i have found some duplicate
content, that’s why you don’t rank high in google’s search results,
but there is a tool that can help you to create 100% unique articles,
search for: Boorfe’s tips unlimited content
I see you don’t monetize your blog, don’t waste your traffic, you can earn extra bucks every month because you’ve got high quality content.
If you want to know how to make extra money, search for: Boorfe’s tips best adsense alternative