Sorting NSString values as if NSInteger using NSSortDescriptor
Asked Answered
E

7

28

I have created a sort descriptor to sort a plist response coming from my server. This works well with sort key having values upto 9. With more than 10 items I see abrupt results with sort key arranged in the order = 1, 10, 11, 2, 3, 4, 5, 6, 7, 8, 9

NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sort" ascending:YES];
self.myList = [NSMutableArray arrayWithArray:[unsortedList sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]]];

How to make it arrange in the correct order of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11?

Evade answered 12/3, 2012 at 20:58 Comment(0)
D
44

You can do this by implementing a custom comparator block when creating your NSSortDescriptor:

NSSortDescriptor *aSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sort" ascending:YES comparator:^(id obj1, id obj2) {

    if ([obj1 integerValue] > [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedDescending;
    }
    if ([obj1 integerValue] < [obj2 integerValue]) {
        return (NSComparisonResult)NSOrderedAscending;
    }
    return (NSComparisonResult)NSOrderedSame;
}];
self.myList = [NSMutableArray arrayWithArray:[unsortedList sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]]];

See Apple documentation here

Dissimilar answered 12/3, 2012 at 21:11 Comment(1)
please note that this solution will not work with CoreData fetchRequests, because it doesn't accept descriptors based on blocks.Unalloyed
O
39

[list sortUsingSelector:@selector(localizedStandardCompare:)]; will sort the list in a "human" way (so "11" will come last, not between "1" and "2"). But if you really do want to treat these strings as numbers, you should make them number first!

Oesophagus answered 12/3, 2012 at 21:20 Comment(2)
perfect! other solutions didn't work for me, because CoreData doesn't accept sortDescriptors based on blocks. but this one worked.Unalloyed
The best solution for me;)Houseline
B
16
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sort.intValue" ascending:YES];
self.myList = [NSMutableArray arrayWithArray:[unsortedList sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]]];

Sort based on the value of the integer.

Baptista answered 26/3, 2013 at 13:38 Comment(3)
Perfect with it's simplicity!Abraham
didn't work. i get '0 1 10 11 12 ... 2 3 4' instead of '0 1 2 3 4 ... 10 11 12'Unalloyed
Simplest answer. Worked for integer values very wellPyrogallate
B
11

You need your strings to be compared with the NSNumericSearch option:

NSNumericSearch
Numbers within strings are compared using numeric value, that is, Name2.txt < Name7.txt < Name25.txt.

which requires the compare:options: method to be called for the comparison.

In order to do that, your sort descriptor can use an NSComparator Block:

[NSSortDescriptor sortDescriptorWithKey:@"self"
                              ascending:YES
                             comparator:^(NSString * string1, NSString * string2){
                                            return [string1 compare:string2
                                                            options:NSNumericSearch];
 }];

Or, indeed, you can skip the sort descriptor and simply sort the array directly, using the same Block:

[unsortedList sortedArrayUsingComparator:^NSComparisonResult (NSString * string1, NSString * string2){
    return [string1 compare:string2
                    options:NSNumericSearch];
 }];
Behring answered 12/3, 2012 at 21:14 Comment(2)
Small addition: Why casting to NSString? You could pass NSString directly instead of id, makes it easier to read. :-)Audile
please note that this solution will not work with CoreData fetchRequests, because it doesn't accept descriptors based on blocks.Unalloyed
L
7
NSMutableArray *list = [NSMutableArray arrayWithObjects:@"11",@"2",@"3",@"1", nil];
[list sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    NSInteger firstInteger = [obj1 integerValue];
    NSInteger secondInteger = [obj2 integerValue];
    if( firstInteger > secondInteger) return NSOrderedDescending;
    if( firstInteger == secondInteger) return NSOrderedSame;
    return NSOrderedAscending; // edited
}];

No guarantees about performance

Laic answered 12/3, 2012 at 21:13 Comment(1)
please note that this solution will not work with CoreData fetchRequests, because it doesn't accept descriptors based on blocks.Unalloyed
Q
0

All the above methods need good knowledge of basics to implement; but for the newbies I suggest the simplest way – to use the native block method

NSArray* sortedArr =[fetchResults sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {

    int aValue = [[a valueForKey:@"subgroupId"] intValue];
    int bValue = [[b valueForKey:@"subgroupId"] intValue];

    return aValue > bValue;
}];
Quadrille answered 26/7, 2016 at 9:11 Comment(0)
T
0

input

{
    "seats": [{
        "seatNumber": "1"
    }, {
        "seatNumber": "10"
    }, {
        "seatNumber": "2"
    }]
}

sort using [NSSortDescriptor sortDescriptorWithKey:@"seatNumber" ascending:YES selector:@selector(localizedStandardCompare:)]]. The key is to use localizedStandardCompare. This will solve you problem.

NSArray *seats = [self.seats sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"seatNumber" ascending:YES selector:@selector(localizedStandardCompare:)]]];

output

{
    "seats": [{
        "seatNumber": "1"
    }, {
        "seatNumber": "2"
    }, {
        "seatNumber": "10"
    }]
}

documentation: https://developer.apple.com/documentation/foundation/nsstring/1409742-localizedstandardcompare

Theophany answered 13/10, 2017 at 22:26 Comment(1)
You should not just answer with code, but try to give a description/explanation of what it does, how it solves the problem, etc...Bionomics

© 2022 - 2024 — McMap. All rights reserved.