YourMJK
YourMJK

Reputation: 1629

Sort NSDictionary first by value then by key

So I have a NSNumber dictionary similar to this (keys already added in order):

1 : @2000,
2 : @2000,
4 : @1000,
5 : @1000,
6 : @3000

And I want both values and keys to be sorted in ascending order with the order of the values having the higher priority. (Resulting ordered keys and values being in separate arrays of course as dictionaries aren't ordered as mentioned by @rmaddy). Result should be:

4 : 1000,
5 : 1000,
1 : 2000,
2 : 2000,
6 : 3000

I can already get the keys sorted by value using

NSArray<NSNumber *> *sortedKeys = [dict keysSortedByValueUsingSelector:@selector(compare:)];

but this doesn't respect the order of the keys of course.

Disclaimer: The solution to this problem in Swift is already given here but I can't figure out how to implement that in Obj-C

Upvotes: 0

Views: 856

Answers (2)

danh
danh

Reputation: 62686

I'd start with a data structure that is sortable:

NSDictionary *d = @{@1 : @2000,
                    @2 : @2000,
                    @4 : @1000,
                    @5 : @1000,
                    @6 : @3000};

NSMutableArray *array = [[NSMutableArray alloc] init];
for (id key in [d allKeys]) {
    [array addObject:@[key, d[key]]];
}

That will give us:

@[ @[@6, @3000], @[@2, @2000], ...]

Now, we can write a comparator that compares however you like. What I understand from the OP is that we want the primary sort on value with a secondary (tie-breaking) sort on keys:

[array sortUsingComparator:^NSComparisonResult(id elementA, id elementB) {
    NSNumber *valueA = ((NSArray *)elementA)[1];
    NSNumber *valueB = ((NSArray *)elementB)[1];
    if ([valueA isEqualToNumber:valueB]) {
        NSNumber *keyA = ((NSArray *)elementA)[0];
        NSNumber *keyB = ((NSArray *)elementB)[0];
        return [keyA compare:keyB];
    } else {
        return [valueA compare:valueB];
    }
}];

NSLog(@"%@", array);

Upvotes: 1

rmaddy
rmaddy

Reputation: 318774

Ok, you want two separate arrays where one is the sorted values of the dictionary and the other is the keys sorted to correspond with the values. Additionally, two keys associated with the same values should be sorted in the natural order of the two keys.

Sorting the values is easy. But there is nothing built in to get the corresponding keys sorted in the manner you want. The issue is getting keys for equal values sorted in order of the key. The code below is one solution that gives the desired results.

NSDictionary *dict = @{@1 : @2000,
                      @2 : @2000,
                      @4 : @1000,
                      @5 : @1000,
                       @6 : @3000};

NSArray *sortedValues = [dict.allValues sortedArrayUsingSelector:@selector(compare:)];
NSOrderedSet *uniqueValues = [NSOrderedSet orderedSetWithArray:sortedValues];
NSMutableArray *sortedKeys = [NSMutableArray arrayWithCapacity:dict.count];
for (id val in uniqueValues) {
    NSArray *keys = [dict allKeysForObject:val];
    [sortedKeys addObjectsFromArray:[keys sortedArrayUsingSelector:@selector(compare:)]];
}

NSLog(@"dict: %@", dict);
NSLog(@"keys: %@, values: %@", sortedKeys, sortedValues);

This outputs:

dict: {
    6 = 3000;
    2 = 2000;
    5 = 1000;
    1 = 2000;
    4 = 1000;
}
keys: (
    4,
    5,
    1,
    2,
    6
), values: (
    1000,
    1000,
    2000,
    2000,
    3000
)

Upvotes: 1

Related Questions