Nerethar
Nerethar

Reputation: 347

How to sort a stringsarray that contain numbers and names in iOS

I got an array that contains strings and want to sort it. So far, so easy.

I simply use

NSSortDescriptor *sort [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
[list sortUsingDescriptors:[NSArray arrayWithObject:sort] ];

The problem here is, that when I use it for a list like "ccccc, aab, bba, 123" he sorts it to "123, aab, bba, ccccc". I would like to sort it like "aab, bba, ccccc, 123"

Is there an iOS-function to sort it that numbers are put at the end of the list? The numbers are ofc also strings.

Thanks for any help guys.

Upvotes: 1

Views: 814

Answers (3)

GeneralMike
GeneralMike

Reputation: 3001

It's conventional for numbers to be put before letters in a standard sort. If you want the numbers last, you will have to do some manipulation.

You can use an NSPredicate to get just the numbers in one array, then just the letters in another array, then finally combine them to get the numbers at the end.

// Get just the letters
NSPredicate *predLetters = [NSPredicate predicateWithFormat:@"name MATCHES %@",@"^\\p{letter}.*"];
NSArray *tempLetters = [[list copy] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
tempLetters = [tempLetters filteredArrayUsingPredicate:predLetters];

// Get just the numbers
NSPredicate *predNumbers = [NSPredicate predicateWithFormat:@"NOT (name MATCHES %@)",@"^\\p{letter}.*"];
NSArray *tempNumbers = [[list copy] sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
tempNumbers = [tempNumbers filteredArrayUsingPredicate:predNumbers];

// Combine arrays
NSMutableArray *tempCombined = [[NSMutableArray alloc] initWithArray:tempLetters];
[tempCombined addObjectsFromArray:tempNumbers];

// Rename to 'list'
list = [tempCombined copy];

Clarification: This code only works based on the first character of name. I'm assuming from your question that you won't have any names that have both letters and numbers. If you do, then the filter will just go off the first character, and the rest of the characters in name will be sorted according to standard convention. You will probably have to set up a recursive function or something to deal with sorting a name that contains both letters and numbers if you want the letters to always be handled first, even if they are the 5th character in the string, for example.

EDIT: Thanks to @Nerethar for syntax fixes.

Upvotes: 1

Nerethar
Nerethar

Reputation: 347

It works now the way I wanted it, with minor modifications of GeneralMike's code, so credits to him. I post the working code with an explanation of the changes here.

NSSortDescriptor *sort [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];

// Get just the letters
NSPredicate *predLetters = [NSPredicate predicateWithFormat:@"name MATCHES %@",@"^\\p{letter}.*"];
NSArray *tempLetters = [[list copy] sortedArrayUsingDescriptors:@[sort]];
tempLetters = [tempLetters filteredArrayUsingPredicate:predLetters];

// Get just the numbers
NSPredicate *predNumbers = [NSPredicate predicateWithFormat:@"NOT (name MATCHES %@)",@"^\\p{letter}.*"];
NSArray *tempNumbers = [[list copy] sortedArrayUsingDescriptors:@[sort]];
tempNumbers = [tempNumbers filteredArrayUsingPredicate:predNumbers];

// Combine arrays
NSMutableArray *tempCombined = [[NSMutableArray alloc] initWithArray:tempLetters];
[tempCombined addObjectsFromArray:tempNumbers];

// Rename to 'list'
list = [tempCombined copy];

Aparently you can't use < and > to compare strings from what I've found. But to check if it starts with an letter this regex works fine. For more information check out: How can i filter NSMutableArray by firstName with non-alphabetical characters

The other changes have simply syntax reasons, but as GeneralMike said earlier, he hadn't the time to actually check it.

filteredArrayWithPredicate needs to be filteredArrayUsingPredicate

and

sortedArrayUsingDescriptors:sort needs to be sortedArrayUsingDescriptors:@[sort] else you would get a cast-warning.

Can a moderator be as kind as to edit the working code plus explanation into his post so that he could get the credit for the answer and the upvotes?

Thank you

Upvotes: 1

Andy Obusek
Andy Obusek

Reputation: 12842

Use a custom comparator:

sortedArray = [list sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    id aValue = [a valueForKey:@"name"];
    id bValue = [b valueForKey:@"name"];
    //check if both aValue and bValue are numbers or if both aValue and bValue are both strings
    //if so, then use normal sort order
    //else, if aValue is a string, and bValue is a number, then return NSOrderedAscending
    //else (aValue is a number, and bValue is a string), return NSOrderedDescending
}];

Upvotes: 0

Related Questions