BuySellAds.com

Read the chapters in my new book Barcodes with iOS 7 as I hand them in. Great new app opportunities await!
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

%d bloggers like this: