BuySellAds.com

If you like my tutorials, you will love my book . It is chockful of advanced programming techniques and the only comprehensive barcode reference for serious iOS developers.
Our DNA is written in Objective-C
Jump

Advanced Sorting of NSArray with Functions

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

3 Comments »

  1. 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

  2. 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.

Trackbacks

  1. Nsarray | Pen Pal

Leave a Comment

%d bloggers like this: