1

I have an array of dictionaries that contain information about high scores. I am trying to figure out how to sort them by the different values in the dictionaries but cannot get it to work.

An example shown below attempts to sort by "Score":

NSDictionary *highScoreDictionary1 = @{@"Score" : @52, @"Duration" : @230 , @"Date" : [NSDate date]};
NSDictionary *highScoreDictionary2 =  @{@"Score" : @23, @"Duration" : @230 , @"Date" : [NSDate date]};
NSDictionary *highScoreDictionary3 = @{@"Score" : @35, @"Duration" : @230 , @"Date" : [NSDate date]};

NSArray *highScoresArray = @[highScoreDictionary1, highScoreDictionary2, highScoreDictionary3];

NSSortDescriptor *highScoreSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"Score" ascending:YES];  // Sort by Score
NSArray *sortDescriptorArray = [NSArray arrayWithObject:highScoreSortDescriptor];

[highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];

The output I get from NSLog(@"sorted array of dictionaries: %@", highScoresArray); is:

sorted array of dictionaries: (
        {
        Date = "2014-09-01 19:38:00 +0000";
        Duration = 230;
        Score = 52;
    },
        {
        Date = "2014-09-01 19:38:00 +0000";
        Duration = 230;
        Score = 23;
    },
        {
        Date = "2014-09-01 19:38:00 +0000";
        Duration = 230;
        Score = 35;
    }
)

How do I remedy this? Am I missing something here because it seems that the dictionaries are not being sorted by score.

geeves31
  • 35
  • 6

4 Answers4

3
highScoresArray = [highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
  • Ok wow...like I said I missed the most obvious explanation. I did not realize I was trying to change an immutable array / did not see the return variable was an NSArray *. Thanks. – geeves31 Sep 01 '14 at 22:19
1

You're trying to sort an NSArray, which is immutable. You need to use the sort function to create a mutable array, i.e.

replace your:

[highScoresArray sortedArrayUsingDescriptors:sortDescriptorArray];

with:

NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:[highScoresArray sortedArrayUsingDescriptors:@[highScoreSortDescriptor]]];

I have tested this and it seems to work.

Pang
  • 9,564
  • 146
  • 81
  • 122
Gordon Worswick
  • 191
  • 1
  • 9
  • Mutability is redundant here. – keeshux Sep 01 '14 at 20:47
  • ok.. i'm pretty new to programming, but it still works and he gets an array that is sorted how he wants it :) – Gordon Worswick Sep 01 '14 at 20:49
  • I never said it doesn't work, but there's room for improvement. :) Replacing `NSMutableArray` with `NSArray` makes this a legit answer, but your headline about the need of mutability is not reflected in the code. – keeshux Sep 01 '14 at 20:54
  • or if you want to show the usage of NSMutableArray use `sortUsingComparator:` for in-place sorting. – vikingosegundo Sep 01 '14 at 20:56
  • @vikingosegundo and this is where Apple deceives devs into thinking that an in-place method always exist in the mutable counterpart class. – keeshux Sep 01 '14 at 20:58
  • @keeshux, well, what does make you think it is not in-place? – vikingosegundo Sep 01 '14 at 21:01
  • Err no. I meant Apple didn't define a `[NSMutableArray sortUsingDescriptors:]` .. only to find it actually exists. Why `sortUsingComparator:` then? – keeshux Sep 01 '14 at 21:04
  • oh, mistyping. I actually prefer comparators. the hand-based memory. – vikingosegundo Sep 01 '14 at 21:05
  • @GordonWorswick here's your mutable solution "fixed" for completeness: `NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:highScoresArray]; [newArray sortUsingDescriptors:@[highScoreSortDescriptor]]; highScoresArray = newArray;` – keeshux Sep 01 '14 at 21:06
  • or `NSMutableArray *newArray = [highScoresArray mutableCopy];` instead of `NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:highScoresArray];` – vikingosegundo Sep 01 '14 at 21:08
  • Excellent thanks, it clicked as soon as you pointed it out the first time :) – Gordon Worswick Sep 01 '14 at 21:10
-1

Try this approach

NSArray *sortedArray;
sortedArray = [highScoresArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
    NSDictionary *first = (NSDictionary*)a;
    NSDictionary *second = (NSDictionary*)b;

    int firstScore = [first objectForKey:@"score"];
    int secondScore = [second objectForKey:@"score"];

    if(firstScore > secondScore)
    {
        return NSOrderedDescending;
    }
    else if (firstScore < secondScore)
    {
        return NSOrderedAscending;
    }

    return NSOrderedSame;
}];

Got the code from here

Community
  • 1
  • 1
Leonardo
  • 1,740
  • 18
  • 27
-3

If you would like, here's my own sorting method which I implemented manually, in your case just use it like this

// you could also pass "DESC" for descending order
NSMutableArray* copiedArray = [[NSMutableArray alloc] initWithArray:highScoresArray];
[self sortArray:copiedArray inOrder:@"ASC" basedOnField:@"Score" args:-1];
// Now copiedArray contains a sorted array :)

Here's the full code (2 methods, one main and one helper), copy these to some class so the above code would work.

/*
 * This method sorts a given array based on the given field name and the given sorting order
 * fieldName could be nil if the comparison shall happen directly on the array items
 * args contain the array index of the value to compare if "field name" pointed to an array or -1
 */
- (void)sortArray:(NSMutableArray*)array
          inOrder:(NSString*)sortingOrder
     basedOnField:(NSString*)fieldName
             args:(int)args {
    for (int i = 1; i < array.count; i++) {
        // Start the insertion sort algorithm
        int j = i;

        // Get the current value and one before it
        id currentValue = [self itemInArray:array
                                    atIndex:j
                                  fieldName:fieldName
                                       args:args];

        id previousValue = [self itemInArray:array
                                     atIndex:j-1
                                   fieldName:fieldName
                                        args:args];

        // Set the comparison result based on the user request
        NSComparisonResult requiredResult = NSOrderedDescending;
        if ([sortingOrder compare:@"ASC"] == NSOrderedSame) {
            requiredResult = NSOrderedAscending;
        }

        while ((j > 0) && ([previousValue compare:currentValue] == requiredResult)) {
            // Swap the current and previous objects
            id temp = array[j];
            array[j] = array[j-1];
            array[j-1] = temp;

            // Get back one step and get the new current and previous values
            j--;
            if (j == 0) {
                break;
            }

            currentValue = [self itemInArray:array
                                     atIndex:j
                                   fieldName:fieldName
                                        args:args];

            previousValue = [self itemInArray:array
                                      atIndex:j-1
                                    fieldName:fieldName
                                         args:args];
        }
    }
}

// This method gets an item from the array based on the given index and the field name if the item is an object, as well as a specific member of that item if it's an array (index is passed in args)
- (id)itemInArray:(NSArray*)array
          atIndex:(int)index
        fieldName:(NSString*)fieldName
             args:(int)args {

    // Get the item at the given index
    id value = array[index];

    // Get the sepcific field from it if it's an object
    if (fieldName != nil) {
        value = [value valueForKey:fieldName];
    }

    // Get the specific value if the field is an array
    if ([value isKindOfClass:[NSArray class]]) {
        value = value[args];
    }

    return value;
}
Boda
  • 1,484
  • 1
  • 14
  • 28
  • Why are you reinventing the wheel? [«Don't second guess Apple, because Apple has already second guessed YOU.»](http://ridiculousfish.com/blog/posts/array.html) – vikingosegundo Sep 02 '14 at 11:00
  • I understand but this is an old method I wrote once and kept, it's fully working and if built-in methods didn't work, he could have used this one – Boda Sep 02 '14 at 11:32
  • he wrote that he wants to use descriptors. and actually just one assignment was missing. and you come with and old, hackish, non-threadsafe, non-performant (as you don't have internal access to the datastore) and complicated method and want applause for that? – vikingosegundo Sep 02 '14 at 23:59