1

I've seen lots of questions how to sort 1,10,2,3 etc.

I have some Json data imported in the form: @"arrived", @"boarding", @"1", @"2", @"3" etc..

I currently have only a case-insensitive sort:

sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"time" ascending:YES selector:@selector(caseInsensitiveCompare:)];

This sorts the numbers first, and the words last, where I need it in the form shown above.

What options do I have to do this? Thanks

Dennis
  • 2,119
  • 20
  • 29
ICL1901
  • 7,632
  • 14
  • 90
  • 138
  • 1
    What about `sortedArrayUsingComparator:` and a custom result of the block? – Larme Mar 23 '15 at 11:22
  • Thanks. I've seen this, but how do I frame the compare so that alpha precedes numeric? Could you please point me to an example? – ICL1901 Mar 23 '15 at 11:25
  • Are there mixed strings as well, e.g. `@"1way"` or `@"airforce1"`? – Dennis Mar 23 '15 at 11:31
  • no, simply either numeric, or alpha. – ICL1901 Mar 23 '15 at 11:32
  • @DavidDelMonte, May be your answer is there.Pls review them & let us know ur comments. http://stackoverflow.com/questions/8242735/how-to-sort-array-controller-alphabetically-with-numbers-last-in-objective-c – Renish Dadhaniya Mar 23 '15 at 11:35

2 Answers2

3

This lengthy comparator will do the trick:

NSArray *testArray = @[@"3", @"boarding", @"2", @"arrived", @"1"];

NSArray *sortedArray = [testArray sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
    NSInteger firstInteger = 0;
    BOOL isInteger1 = [[NSScanner scannerWithString:obj1] scanInteger:&firstInteger];

    NSInteger secondInteger = 0;
    BOOL isInteger2 = [[NSScanner scannerWithString:obj2] scanInteger:&secondInteger];

    if (isInteger1 && isInteger2) {
        // Normal integer comparison
        if (firstInteger > secondInteger) {
            return NSOrderedDescending;
        }
        else if (secondInteger > firstInteger) {
            return NSOrderedAscending;
        }
        else {
            return NSOrderedSame;
        }
    }
    else if (isInteger1) {
        return NSOrderedDescending;
    }
    else if (isInteger2) {
        return NSOrderedAscending;
    }
    else {
        return [obj1 compare:obj2 options:NSCaseInsensitiveSearch];
    }
}];

EDIT

Sample output:

Printing description of sortedArray:
<__NSArrayI 0x7ffacaa26da0>(
arrived,
boarding,
1,
2,
3
Dennis
  • 2,119
  • 20
  • 29
  • I was hoping to say "brilliant", as I spent the weekend trying this.. Unfortunately, I get this log out: sortedArray: (1, 2, 3, arrived, boarding) I need the alpha words first... +1 for big effort. – ICL1901 Mar 23 '15 at 11:50
  • Just made an edit - sorry it was wrong the first time. Now it is ok. Can you please re-check? – Dennis Mar 23 '15 at 11:51
  • Glad it helped. Make sure to test it on a couple of edge cases. Sorry 'bout having it written the wrong way at first. – Dennis Mar 23 '15 at 11:57
  • 2
    The final `return NSOrderedSame;` seems to be unreachable code. – And just in case somebody is interested: The integer comparison can be reduced to a one-liner, using the technique from http://stackoverflow.com/a/22399374/1187415 (which in turn comes from http://stackoverflow.com/questions/1903954/is-there-a-standard-sign-function-signum-sgn-in-c-c). – Martin R Mar 23 '15 at 12:04
  • @MartinR thanks for pointing this out. I removed the unreachable code. As for the integer comparison, the solution you linked to is really clever. However, personally I prefer this version, as it is easier to understand. In a real project, I would probably put your comparison into its own method, choosing a concise name, to make it a no-brainer. Thanks again! – Dennis Mar 23 '15 at 13:18
2

try this one

    NSComparator compare=^(id obj1, id obj2) {
    NSString *s1 = [obj1 substringToIndex:1];
    NSString *s2 = [obj2 substringToIndex:1];

    NSRange r1 = [s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
    NSRange r2 = [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
    if (r1.location == r2.location ) { // either both start with a number or both with a letter
        return [obj1 compare:obj2 options:NSDiacriticInsensitiveSearch|NSCaseInsensitiveSearch];
    } else {  // one starts wit a letter, the other with a number
        if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound) {
            return NSOrderedAscending;
        }
        return NSOrderedDescending;
    }
};

NSSortDescriptor *desc=[[NSSortDescriptor alloc]initWithKey:@"time" ascending:YES comparator:compare];

originalArray =  [originalArray sortedArrayUsingDescriptors:@[desc]];

may help you.

SGDev
  • 2,256
  • 2
  • 13
  • 29
  • Thanks Sumit, This sorts the numeric correctly, but leaves the alpha last. I need the alpha first. – ICL1901 Mar 23 '15 at 11:38
  • Thank you so much Sumit. That is really cool. @Dennis, I had some trouble trying to integrate your method. Sumit's dropped in nicely. Thanks to you both. – ICL1901 Mar 23 '15 at 12:13
  • Note that this will sort "numerical strings" in the order "1", "11", "2", which might be unwanted. – Martin R Mar 23 '15 at 13:29