Sorting NSArray -- advice on how to accomplish a "simple" alpha ordering?

  • The NSSortDescriptor documentation seems especially opaque to me tonight.
    Surely there is a useful short description somewhere … ?  *whimper*
  • On 01/08/2012, at 3:01 PM, Erik Stainsby wrote:

    > The NSSortDescriptor documentation seems especially opaque to me tonight.
    > Surely there is a useful short description somewhere … ?  *whimper*

    Sort descriptors are quite easy to use - what's the specific problem?

    warning: typed into Mail:

    NSSortDescriptor* desc = [NSSortDescriptor sortDescriptorWithKey:@"compare:" ascending:YES];

    [myArray sortUsingDescriptors:[NSArray arrayWithObject:desc]];    //<-- myArray assumed to be an NSMutableArray

    note that for this simple case, using a sort descriptor might not the most efficient solution.

    --Graham
  • On 01/08/2012, at 3:07 PM, Graham Cox wrote:

    > NSSortDescriptor* desc = [NSSortDescriptor sortDescriptorWithKey:@"compare:" ascending:YES];

    Oops, this wasn't what I meant.

    The "key" should be the property you're comparing to sort the objects. If your array is a bunch of strings to be sorted, then what is the key? Well, it's actually "self":

    NSSortDescriptor* desc = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES];

    Because internally the descriptor is looping over the array and doing a -valueForKey:@"self" on each object, and that returns the object itself. The sort comparison itself is done by invoking -compare: on the objects.

    If it's another sort of object that has a string property, then you would pass the name of that property, e.g. @"lastName".

    --Graham
  • This has me thinking that to get the alpha sorted list of keys from a dictionary I should be passing the keypath as the param for sortDescriptorWithKey: and not trying to externalize the keys into an array first … ?

    On 2012-07-31, at 10:13 PM, Graham Cox <graham.cox...> wrote:

    >
    > On 01/08/2012, at 3:07 PM, Graham Cox wrote:
    >
    >> NSSortDescriptor* desc = [NSSortDescriptor sortDescriptorWithKey:@"compare:" ascending:YES];
    >
    >
    > Oops, this wasn't what I meant.
    >
    > The "key" should be the property you're comparing to sort the objects. If your array is a bunch of strings to be sorted, then what is the key? Well, it's actually "self":
    >
    > NSSortDescriptor* desc = [NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES];
    >
    > Because internally the descriptor is looping over the array and doing a -valueForKey:@"self" on each object, and that returns the object itself. The sort comparison itself is done by invoking -compare: on the objects.
    >
    >
    > If it's another sort of object that has a string property, then you would pass the name of that property, e.g. @"lastName".
    >
    > --Graham
    >
    >
  • On 01/08/2012, at 3:20 PM, Erik Stainsby wrote:

    > This has me thinking that to get the alpha sorted list of keys from a dictionary I should be passing the keypath as the param for sortDescriptorWithKey: and not trying to externalize the keys into an array first … ?

    If you want to iterate over the contents of a dictionary in alphabetical order of keys, I don't think there is a magic keypath that will allow you to operate on the dictionary itself.

    Your first thought was right:

    NSMutableArray    * sortedKeys =[ [dictionary allKeys] mutableCopy];
    [sortedKeys sortUsingDescriptors:<descriptors>];        //<--- create descriptor using key @"self" or else using an alternative means of sorting

    // now you can iterate over the dictionary in alphabetical order.

    I would only use sort descriptors if a) I had to sort based on more than one criterion, e.g. lastName, firstName or b) I was using a table view that manages sorting using descriptors. For simple sorts there are easier ways to do it.

    --Graham
  • Hi Mark,

    So I have a dictionary like so:

    NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];

    I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:

    NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];

    What I have come up with is this:

    [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
      return [(NSString*)obj1 compare:(NSString*)obj2 ];
    } ];

    Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?

    ~ Erik

    On 2012-08-01, at 3:20 AM, Mark Woollard <mark...> wrote:

    > Can you give a bit more info on what is stored in your NSArray? NSString? NSManagedObject? NSDictionary? Something else?
    > Regards
    > Mark
    >
    > On 1 Aug 2012, at 06:01, Erik Stainsby <erik.stainsby...> wrote:
    >
    >> The NSSortDescriptor documentation seems especially opaque to me tonight.
    >> Surely there is a useful short description somewhere … ?  *whimper*
    >
  • On Aug 1, 2012, at 8:00 AM, Erik Stainsby <erik.stainsby...> wrote:

    > So I have a dictionary like so:
    >
    > NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >
    > I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >
    > NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >
    > What I have come up with is this:
    >
    > [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    > return [(NSString*)obj1 compare:(NSString*)obj2 ];
    > } ];
    >
    > Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?

    If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:

    [sortkeys sortUsingSelector:@selector(compare:)];
  • On 1 Aug 2012, at 21:38, Michael Babin <mbabin...> wrote:

    >
    > On Aug 1, 2012, at 8:00 AM, Erik Stainsby <erik.stainsby...> wrote:
    >
    >> So I have a dictionary like so:
    >>
    >> NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >>
    >> I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >>
    >> NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >>
    >> What I have come up with is this:
    >>
    >> [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    >> return [(NSString*)obj1 compare:(NSString*)obj2 ];
    >> } ];
    >>
    >> Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?
    >
    > If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:
    >
    > [sortkeys sortUsingSelector:@selector(compare:)];

    The documentation says: "If you are comparing strings to present to the end-user, you should typically use localizedCompare: or localizedCaseInsensitiveCompare: instead."

    Kind regards,

    Gerriet.
  • On Aug 1, 2012, at 7:00 AM, Erik Stainsby wrote:

    > Hi Mark,
    >
    > So I have a dictionary like so:
    >
    > NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >
    > I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >
    > NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >
    > What I have come up with is this:
    >
    > [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    > return [(NSString*)obj1 compare:(NSString*)obj2 ];
    > } ];
    >
    > Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?

    Why not just use an NSDictionaryController and bind sortDescriptors?

    Keary Suska
    Esoteritech, Inc.
    "Demystifying technology for your home or business"
  • I'm also working on this for the simple reason that it just appears that not having sorted dicts (in a straightforward way) is an offense to all that it holy and good.  Matt Galloway and others have already published methods for doing this, but sometimes, the learning is in the doing.

    What I'm currently doing to populate a TVC from a dict is to reference the dict in the datastore first.  Ideally, I'd have accessors in the datastore to return the sections[sectionNum] and rows[rowNum].

    Once I have that dict ref, this is what I'm doing:

        NSDictionary *customerFilters = [DataStore findFiltersForCustomers: customers];

        sectionArray = [[NSMutableArray alloc] init];
        rowArray = [[NSMutableArray alloc] init];
        for (id key in customerFilters) {
            id myRowData = [customerFilters objectForKey:key];
            [sectionArray addObject: key];
            [rowArray addObject: myRowData];
            NSLog(@"%@", key);
            NSLog(@"%@", myRowData);
        }

        NSLog(@"%@",sectionArray );
        NSLog(@"%@",rowArray );

    IMHO, it's still a PITA and it would seem to make a boatload of sense to be able to grab keys and objects/values from dicts simply by using their index, without having to code up too much of a solution.

    On Aug 1, 2012, at 10:38 AM, Michael Babin wrote:

    > On Aug 1, 2012, at 8:00 AM, Erik Stainsby <erik.stainsby...> wrote:
    >
    >> So I have a dictionary like so:
    >>
    >> NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >>
    >> I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >>
    >> NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >>
    >> What I have come up with is this:
    >>
    >> [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    >> return [(NSString*)obj1 compare:(NSString*)obj2 ];
    >> } ];
    >>
    >> Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?
    >
    > If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:
    >
    > [sortkeys sortUsingSelector:@selector(compare:)];
  • I'm also working on this for the simple reason that it just appears that not having sorted dicts (in a straightforward way) is an offense to all that it holy and good.  Matt Galloway and others have already published methods for doing this, but sometimes, the learning is in the doing.

    What I'm currently doing to populate a TVC from a dict is to reference the dict in the datastore first.  Ideally, I'd have accessors in the datastore to return the sections[sectionNum] and rows[rowNum].

    Once I have that dict ref, this is what I'm doing:

      NSDictionary *customerFilters = [DataStore findFiltersForCustomers: customers];

      sectionArray = [[NSMutableArray alloc] init];
      rowArray = [[NSMutableArray alloc] init];
      for (id key in customerFilters) {
          id myRowData = [customerFilters objectForKey:key];
          [sectionArray addObject: key];
          [rowArray addObject: myRowData];
          NSLog(@"%@", key);
          NSLog(@"%@", myRowData);
      }

      NSLog(@"%@",sectionArray );
      NSLog(@"%@",rowArray );

    IMHO, it's still a PITA and it would seem to make a boatload of sense to be able to grab keys and objects/values from dicts simply by using their index, without having to code up too much of a solution.

    On Aug 1, 2012, at 10:38 AM, Michael Babin wrote:

    > On Aug 1, 2012, at 8:00 AM, Erik Stainsby <erik.stainsby...> wrote:
    >
    >> So I have a dictionary like so:
    >>
    >> NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >>
    >> I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >>
    >> NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >>
    >> What I have come up with is this:
    >>
    >> [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    >> return [(NSString*)obj1 compare:(NSString*)obj2 ];
    >> } ];
    >>
    >> Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?
    >
    > If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:
    >
    > [sortkeys sortUsingSelector:@selector(compare:)];
  • On Aug 1, 2012, at 10:38 AM, Michael Babin wrote:

    > On Aug 1, 2012, at 8:00 AM, Erik Stainsby <erik.stainsby...> wrote:
    >
    >> So I have a dictionary like so:
    >>
    >> NSDictionary * countries = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"Australia",@"Canada",@"United Kingdom",@"United States",nil] forKeys:[NSArray arrayWithObjects:@"au",@"ca",@"uk",@"us",nil]];
    >>
    >> I want to present them alphabetically as menuItems in an NSPopUpButton.  I grab the list of keys:
    >>
    >> NSMutableArray * sortkeys  = [NSMutableArray arrayWithArray:[countries allKeys]];
    >>
    >> What I have come up with is this:
    >>
    >> [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    >> return [(NSString*)obj1 compare:(NSString*)obj2 ];
    >> } ];
    >>
    >> Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?
    >
    > If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:
    >
    > [sortkeys sortUsingSelector:@selector(compare:)];

    What's the reason for not using caseInsensitivecompare?
  • On Aug 1, 2012, at 12:08 PM, Alex Zavatone <zav...> wrote:
    >> If your comparison is only going to be a single method call with a single parameter like this, then probably a bit simpler to use:
    >>
    >> [sortkeys sortUsingSelector:@selector(compare:)];
    >
    > What's the reason for not using caseInsensitivecompare?

    I'm guessing just an oversight.

    Also, you can save a line of code with

        NSArray *sortedkeys = [[countries allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

    If you need to do anything more than simple alphabetical sort it gets more verbose, but for sorting country names this should suffice.

    I suspect this is one of those things for which lots of people have their own category methods on NSArray and/or NSString.

    --Andy
  • On Aug 1, 2012, at 6:00 AM, Erik Stainsby <erik.stainsby...> wrote:

    > What I have come up with is this:
    >
    > [sortkeys sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
    > return [(NSString*)obj1 compare:(NSString*)obj2 ];
    > } ];
    >
    > Is this a sane approach ? Seems a bit fussy to have to spec the cast like this. Or is this just the what-is of this kind of functionality?

    The "(NSString*)" casts are unnecessary, as "id" is type-compatible with any object-pointer type. You can also remove the "NSComparisonResult", as the compiler can infer the return type from the 'return' statement.

    Alternatively you can change the parameter types to NSString*, which makes the block more type-safe:

    [sortkeys sortUsingComparator:^(NSString* obj1, NSString* obj2) {
      return [obj1 compare:obj2 ];
    } ];

    Or since your block just calls one method, you could just use:

    [sortKeys sortUsingSelector: @selector(compare:)];

    —Jens
  • On Aug 1, 2012, at 8:57 AM, Alex Zavatone <zav...> wrote:

    > sectionArray = [[NSMutableArray alloc] init];
    > rowArray = [[NSMutableArray alloc] init];
    > for (id key in customerFilters) {
    > id myRowData = [customerFilters objectForKey:key];
    > [sectionArray addObject: key];
    > [rowArray addObject: myRowData];
    > }

    You can just use
    sectionArray = [customerFilters allKeys];
    rowArray = [customerFilters objectsForKeys: sectionArray] notFoundMarker: @""];

    (There is an -allValues method, but the orderings is explicitly undefined, so it might not match the ordering of -allKeys.)

    —Jens
  • Hmm.  The row accessor is doing something quite unexpected.

    Automatic Reference Counting Issue: Receiver type 'NSDictionary' for instance message does not declare a method with selector 'objectsForKeys:'

    Since when does an NSDictionary on iOS 5.0 not declare a selector for objectForKeys:?

    On Aug 1, 2012, at 12:45 PM, Jens Alfke wrote:

    >
    > rowArray = [customerFilters objectsForKeys: sectionArray] notFoundMarker: @""];
    >
    > (There is an -allValues method, but the orderings is explicitly undefined, so it might not match the ordering of -allKeys.)
    >
    > —Jens
  • Never used it myself, but the method I see in the docs is called objectsForKeys:notFoundMarker:.

    --Andy

    On Aug 1, 2012, at 5:24 PM, Alex Zavatone <zav...> wrote:

    > Hmm.  The row accessor is doing something quite unexpected.
    >
    > Automatic Reference Counting Issue: Receiver type 'NSDictionary' for instance message does not declare a method with selector 'objectsForKeys:'
    >
    > Since when does an NSDictionary on iOS 5.0 not declare a selector for objectForKeys:?
    >
    > On Aug 1, 2012, at 12:45 PM, Jens Alfke wrote:
    >
    >>
    >> rowArray = [customerFilters objectsForKeys: sectionArray] notFoundMarker: @""];
    >>
    >> (There is an -allValues method, but the orderings is explicitly undefined, so it might not match the ordering of -allKeys.)
    >>
    >> —Jens

  • On Aug 1, 2012, at 2:24 PM, Alex Zavatone wrote:

    > Hmm.  The row accessor is doing something quite unexpected.
    >
    > Automatic Reference Counting Issue: Receiver type 'NSDictionary' for instance message does not declare a method with selector 'objectsForKeys:'
    >
    > Since when does an NSDictionary on iOS 5.0 not declare a selector for objectForKeys:?
    >

    The selector is -objectsForKeys:notFoundMarker:, not to be confused with -objectForKey:.

    (There was an errant square bracket in Jens' earlier message.)

    -Conrad
  • Stupid extra brackets.  This works fine:

      NSArray *aRowArray = [customerFilters objectsForKeys: sectionArray notFoundMarker: @""] ;

    On Aug 1, 2012, at 12:45 PM, Jens Alfke wrote:

    >
    > On Aug 1, 2012, at 8:57 AM, Alex Zavatone <zav...> wrote:
    >
    >> sectionArray = [[NSMutableArray alloc] init];
    >> rowArray = [[NSMutableArray alloc] init];
    >> for (id key in customerFilters) {
    >> id myRowData = [customerFilters objectForKey:key];
    >> [sectionArray addObject: key];
    >> [rowArray addObject: myRowData];
    >> }
    >
    > You can just use
    > sectionArray = [customerFilters allKeys];
    > rowArray = [customerFilters objectsForKeys: sectionArray] notFoundMarker: @""];
    >
    > (There is an -allValues method, but the orderings is explicitly undefined, so it might not match the ordering of -allKeys.)
    >
    > —Jens
  • Stupid extra brackets.  This works fine:

      NSArray *aRowArray = [customerFilters objectsForKeys: sectionArray notFoundMarker: @""] ;

    On Aug 1, 2012, at 12:45 PM, Jens Alfke wrote:

    >
    > On Aug 1, 2012, at 8:57 AM, Alex Zavatone <zav...> wrote:
    >
    >> sectionArray = [[NSMutableArray alloc] init];
    >> rowArray = [[NSMutableArray alloc] init];
    >> for (id key in customerFilters) {
    >> id myRowData = [customerFilters objectForKey:key];
    >> [sectionArray addObject: key];
    >> [rowArray addObject: myRowData];
    >> }
    >
    > You can just use
    > sectionArray = [customerFilters allKeys];
    > rowArray = [customerFilters objectsForKeys: sectionArray] notFoundMarker: @""];
    >
    > (There is an -allValues method, but the orderings is explicitly undefined, so it might not match the ordering of -allKeys.)
    >
    > —Jens
  • On 02/08/2012, at 1:53 AM, Alex Zavatone wrote:

    > not having sorted dicts (in a straightforward way) is an offense to all that it holy and good.

    I disagree. Dictionaries by their nature are not "sorted" because they are random-access containers. Separating the responsibility for sorting into another class (NSMutableArray) is a good example of the correct separation of concerns. Besides, what does "sorted" mean? There's no single definition of sorted, that's why there are many ways to accomplish it, with case sensitive or not, different locales, and so on.

    If you really want a sorted dictionary, for your own definition of "sorted", adding the feature via a category is no problem.

    > it would seem to make a boatload of sense to be able to grab keys and objects/values from dicts simply by using their index

    Maybe you're just using the wrong container for the job. Dictionaries have no index, the very concept does not make sense for a dictionary. THAT IS THEIR POINT! If you want indexing, use an array. If you want some sort of hybrid, composite one in a class of your own device.

    --Graham
previous month august 2012 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
Go to today