Sort strings alphabetically AND by length?

1.5k views Asked by At

I want to sort an array of NSStrings by length, but then have all strings that are the same length be sub-sorted alphabetically. For example, "cat, hat, zen, nine, rate, tale, access, vanish."

How can I do this? I've been trying to figure out NSSortDescriptors for like an hour and gotten nowhere. I do not know what keys to use in order to sort NSStrings alphabetically or by length. I have sorted them alphabetically using sortedArrayUsingSelector, but over KevinTTrimm's comment's link, I need to use NSSortDescriptor in order to sort by two metrics, so I need to know the keys.

3

There are 3 answers

0
David Rönnqvist On BEST ANSWER

tl;dr: the key paths you are looking for are "length" and "self" (or "description" or "uppercaseString" depending on what how you want to compare)


As you can see in the documentation for NSSSortDescriptor, a sort descriptor is created "by specifying the key path of the property to be compared". When sorting string by their length, the property on NSString that you are looking for is length:

@property(readonly) NSUInteger length;

Since you want the words to be longer and longer, that sort descriptor should be ascending so that later values have a greater length than the previous value.

NSSortDescriptor *byLength =
    [NSSortDescriptor sortDescriptorWithKey:@"length" ascending:YES];

Next, to sort the string alphabetically, what you really want is to compare the string directly with the other string. There are a couple of different ways you can do this. For example, the description method on NSString (which can be invoked using KVC) is documented to return "This NSString object", meaning the object itself. That would be one option.

NSSortDescriptor *alphabeticallyAlternative1 =
    [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES];

Other methods you could invoke over KVC are for example uppercaseString or lowercaseString. Using either of these in the sort descriptor will make it an case-insensitive comparison (which either is useful to you, or it isn't).

// case-insensitive comparison
NSSortDescriptor *alphabeticallyAlternative2 =
    [NSSortDescriptor sortDescriptorWithKey:@"uppercaseString" ascending:YES];

A third way of comparing the string itself directly with the other string is to use the key path "self". You will sometimes see this being used in predicates.

NSSortDescriptor *alphabeticallyAlternative3 =
    [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES];

In all 3 cases the sort descriptor was made ascending so that letters that appear later in the alphabet come later in the array.


Whichever version of alphabetical sorting that you pick, since you want to sort by length first you would pass the byLength descriptor first and the alphabetical descriptor second.

NSArray *sorted =
    [someStrings sortedArrayUsingDescriptors:@[byLength, alphabetically]];
0
temporary_user_name On

Finally got it with:

NSArray* words2 = [words sortedArrayUsingComparator: ^(NSString* s1, NSString* s2) {
    if([s1 length] != [s2 length]){
        NSNumber* s1length = [NSNumber numberWithInt:[s1 length]];
        NSNumber* s2length = [NSNumber numberWithInt:[s2 length]];
        return [s1length compare:s2length];
    }
    else return [s1 compare:s2];
}];

Not sure why they felt boolean was insufficient and added NSComparisonResult, but whatever...

3
matt On

I've been trying to figure out NSSortDescriptors for like an hour

Why use NSSortDescriptors at all? I mean, they are nice, but this is so much easier using something like sortedArrayUsingComparator: because you can just write your own test saying what counts as greater than and what counts as less than.