Sort NSArray of NSDictionaries using comparator

2019-05-18 12:03发布

I've been trying to sort an NSArray of NSDictionaries using a comparator, but I cannot seem to get the output I desire.

The output I'm trying to achieve is that A-Z usernames should come first in the sorted array, then usernames that start with a digit should come second in the sorted array, and lastly usernames that start with an underscore should be last in the sorted array. Any help is truly appreciated!

EDIT: It should be sorted so it looks consistent through the whole NSArray so that: _Anna comes before _Bob and _11Bob comes before _12Cary but after _09Bob

Example of desired output I'm looking for:

(
        {
        username = abcd;
    },
        {
        username = Anna;
    },
        {
        username = 01Bob;
    },
        {
        username = 02Tob;
    },
        {
        username = 03ZED;
    },
        {
        username = 04_Hob;
    },
        {
        username = 04_sob;
    },
        {
        username = "_anna";
    },
        {
        username = "_bob";
    },
        {
        username = "_boc";
    },
        {
        username = "_bocd12";
    },
        {
        username = "_bocd13";
    }
        {
        username = _01Bob;
    },
        {
        username = _02Tob;
    },
)

I hope that makes sense now.

Sample NSDictionary with an NSArray of NSDictionaries:

NSDictionary *dictionary = @{@"users":@[@{@"username":@"191anna"},@{@"username":@"_091bob"},@{@"username":@"Bob"},@{@"username":@"charlie"}]};

I'm trying by using this comparator:

NSArray *array = [[dictionary objectForKey:@"users"] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
{
    NSString *f1 = [obj1 objectForKey:@"username"];
    NSString *f2 = [obj2 objectForKey:@"username"];

    NSString *s1 = [[obj1 objectForKey:@"username"]substringFromIndex:1];
    NSString *s2 = [[obj2 objectForKey:@"username"]substringFromIndex:1];

    if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location)
    {
        return  [f1 localizedCaseInsensitiveCompare:f2];
    }
    else if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location != [s2 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location)
    {
        return  [f1 localizedCaseInsensitiveCompare:f2];

        if ([s1 rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].location == NSNotFound)
        {
            return NSOrderedDescending;
        }
    }
     return  NSOrderedAscending;
}];

But it gives me the following (not the way I want) sorted NSArray:

(
        {
        username = "_091bob";
    },
        {
        username = 191anna;
    },
        {
        username = Bob;
    },
        {
        username = charlie;
    }
)

2条回答
Ridiculous、
2楼-- · 2019-05-18 12:54

You can optimize this further, but your sort logic would be like below.

     NSArray *sorted = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSString *name1 = [(NSDictionary *) obj1 objectForKey:NAME];
        NSString *name2 = [(NSDictionary *) obj2 objectForKey:NAME];

        if ([name1 characterAtIndex:0] == '_' && [name2 characterAtIndex:0] == '_')
        {
            return [name1 compare:name2 options:NSCaseInsensitiveSearch];
        }
        else if ([name1 characterAtIndex:0] == '_')
        {
            return NSOrderedDescending;
        }
        else if ([name2 characterAtIndex:0] == '_')
        {
            return NSOrderedAscending;
        }
        else if (([name1 intValue] && [name2 intValue]) || ([name1 characterAtIndex:0] == '0' && [name2 characterAtIndex:0] == '0'))
        {
            return [name1 compare:name2 options:NSCaseInsensitiveSearch];
        }
        else if ([name1 intValue] >0 || [name1 characterAtIndex:0] == '0')
        {
            return NSOrderedDescending;
        }
        else if ([name2 intValue]>0 || [name2 characterAtIndex:0] == '0')
        {
            return NSOrderedAscending;
        }
        else
        {
            return  [name1 compare:name2 options:NSCaseInsensitiveSearch];
        }
        //return res;

    }];
查看更多
一纸荒年 Trace。
3楼-- · 2019-05-18 13:00

Here's what I came up with. It's a touch long because it requires quite a bit of logic. It can likely be optimized further:

My Set Up:

NSArray * usernames = @[@"191anna", @"abcd", @"Anna", @"01Bob", @"02Tob", @"03ZED", @"04_rob", @"_anna", @"_bob", @"_boc", @"_bocd12", @"_bocd13", @"_01Bob", @"_02Tob"];
NSMutableArray * users = [NSMutableArray array];
for (NSString * username in usernames) {
    [users addObject:@{@"username":username}];
}
NSDictionary * dictionary = @{@"users":users};

And The Sort:

NSArray *sortedArray = [dictionary[@"users"] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2)
{
    NSString *nameOne = obj1[@"username"];
    NSString *nameTwo = obj2[@"username"];

    NSString *startOne;
    NSString *startTwo;

    NSInteger currentIndex = 0;
    NSInteger maxIndex = (nameOne.length < nameTwo.length) ? nameOne.length : nameTwo.length;

    // Get our first differentiating letter
    do {
        if (currentIndex < maxIndex) {

            startOne = [nameOne substringWithRange:NSMakeRange(currentIndex, 1)];
            startTwo  = [nameTwo substringWithRange:NSMakeRange(currentIndex, 1)];
            currentIndex++;
        }
        else {

            // Names are equal up to max length. Same length is same, else shorter word ascending.  (bob above bobb)
            if (nameOne.length == nameTwo.length) {
                return NSOrderedSame;
            }
            else {
                return (nameOne.length < nameTwo.length) ? NSOrderedAscending : NSOrderedDescending;
            }
        }

    } while ([startOne isEqualToString:startTwo]);
    // Prioritize underscores to bottom
    NSCharacterSet * underscoreCharSet = [NSCharacterSet characterSetWithCharactersInString:@"_"];

    NSRange underscoreRangeOne = [startOne rangeOfCharacterFromSet:underscoreCharSet];
    NSRange underscoreRangeTwo = [startTwo rangeOfCharacterFromSet:underscoreCharSet];

    if (underscoreRangeOne.length > 0 || underscoreRangeTwo.length > 0) {
        // Something is underscored, put it on the bottom
        return (underscoreRangeOne.length > 0) ? NSOrderedDescending : NSOrderedAscending;
    }
    // Prioritize numbers to bottom
    NSRange decimalRangeOne = [startOne rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
    NSRange decimalRangeTwo = [startTwo rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]];
    if (decimalRangeOne.length > 0 || decimalRangeTwo.length > 0) {
        // Something is numbered, put it on the bottom
        if (decimalRangeOne.length == decimalRangeTwo.length) {
            return (startOne.intValue > startTwo.intValue) ? NSOrderedDescending : NSOrderedAscending;
        }
        else if (decimalRangeOne.length > decimalRangeTwo.length) {
            return NSOrderedDescending;
        }
        else if (decimalRangeTwo.length > decimalRangeOne.length) {
            return NSOrderedAscending;
        }

    }

    // Now, sort alphabetically
    return  [nameOne localizedCaseInsensitiveCompare:nameTwo];

}];

NSLog(@"SortedArray: %@", sortedArray);

Will log as:

abcd,
Anna,
01Bob,
02Tob,
03ZED,
"04_rob",
191anna,
"_anna",
"_bob",
"_boc",
"_bocd12",
"_bocd13",
"_01Bob",
"_02Tob"
查看更多
登录 后发表回答