Sorting a dictionary

Discussion in 'Mac Programming' started by mdeh, Feb 28, 2010.

  1. mdeh macrumors 6502

    Joined:
    Jan 3, 2009
    #1
    Hi all,
    I am stuck on something which ought to be really simple. ( I think!!!)
    I have an NSDictionary which stores the distribution of characters in a string.
    In order to sort the dictionary so that it can be displayed in a tableView, I have used the method:

    Code:
    return [[ self characterList] keysSortedByValueUsingSelector:@selector(compare:)];
    where "characterList" is the dictionary. This sorts the array by value, ( where value is an NSNumber, which is what I want, but it is being sorted in the inverse order to the way I want it displayed. This has seemed to confuse quite a few as many questions have been asked to which all the answers have been: "Read the documentation". Well, the docs are quite clear as to what is happening,
    So, conceptually, I get it ...I think. I have tried to write a custom method "inverseCompare" but get stuck as to understanding exactly how cocoa wishes the comparison presented. My idea would be simply to reverse the comparison, so something like this.
    Code:
    -(NSComparisonResult)inverseCompare: (NSNumber *) n
    {
    	return [n compare:[[self characterList]objectForKey:key]];
    }
    
    but, as one expects, the compiler rejects this as key is undeclared.
    Anyway, some insight would be greatly appreciated. Thanks as always in advance.
     
  2. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #2
    The other relevant part of the documentation:
    The comparator method should return NSOrderedAscending if the receiver is smaller than the argument, NSOrderedDescending if the receiver is larger than the argument, and NSOrderedSame if they are equal.​

    Then if you look up the types of NSOrdered* values, you see:
    Code:
    enum {
       NSOrderedAscending = -1,
       NSOrderedSame,
       NSOrderedDescending
    };
    typedef NSInteger NSComparisonResult;
    So the numeric values are -1, 0, 1. This suggests that a way to invert a comparison result is to simply negate the return value:
    -NSOrderedAscending is equal to NSOrderedDescending.


    Exactly what are you trying to do with this code?

    The receiver of the message is one operand to compare. The argument n is the other operand. Why do you need a key?

    If that's not the situation, then you need to change something: either the class being compared needs a suitable method, or you need to use one of the dictionary methods that use NSComparator.
     
  3. mdeh thread starter macrumors 6502

    Joined:
    Jan 3, 2009
    #3
    Hi Chown,
    Firstly, thank you for answering.

    Agree completely. But...the point that I guess that I am not seeing is *how* one would implement this in code? As far as I can tell, the "compare:" method does it's comparison of the values in the directory in a way that gives the inverse of the result I want. Initially, I looked for a comparator that could be implemented by simply replacing the method "compare:" with something like "inverseCompare;" but of course there is no "built in" method by that name in the directory class. Hence, the question you alluded to below, ie my effort to implement this

    Well, there you have honed in on my fuzziness of understanding of the documentation. It seems to imply that the method will compare two values in the dictionary. So, I am trying to present to the method the 2 objects that need comparison...incorrectly, obviously. Comparing "self" to n, does not work either, but in any case, that's just guessing, which does not lead to a better understanding of the issue.

    I think this may well be the way to go, but I first need to understand the issue better...perhaps with your help.
    Thanks
     
  4. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #4
    The method doesn't "compare two values in the dictionary". It just compares one value (self) with another value (its argument).

    From NSDictionary reference doc:
    Discussion
    Pairs of dictionary values are compared using the comparison method specified by comparator; the comparator message is sent to one of the values and has as its single argument the other value from the dictionary. [emphasis added]

    The comparison message isn't sent to the dictionary. It's sent to one of the values (objects) retrieved from the dictionary. You said your values are NSNumbers, so the message is sent to an NSNumber.

    I also have to ask: What class did you put your inverseCompare: method in?
     
  5. mdeh thread starter macrumors 6502

    Joined:
    Jan 3, 2009
    #5
    I have an NSObject Class ( called Cypher) that contains an NSMutableDictionary called "characterList".

    Perhaps this will help explain what I am missing. This method, which I just found ( on your indirect prompting ) , **DOES** the trick, but it would be nice to know, exactly why. (As an aside: The keyArray method returns an array to one of the TableView methods which I then interpret and use to display the results...which works)

    Code:
    -(NSArray *)keyArray
    {
    
    	return [[ self characterList] keysSortedByValueUsingComparator:^(id obj1, id obj2) {
    	
        if ([obj1 integerValue]  > [obj2 integerValue] ) {
            return (NSComparisonResult)NSOrderedAscending;
        }
    	
        if ([obj1 integerValue]  < [obj2 integerValue] ) {
            return (NSComparisonResult)NSOrderedDescending;
        }
        return (NSComparisonResult)NSOrderedSame;
    	}];
    
    }
    

    So, if my understanding is close ( :) ) I am telling the receiver ( in this case, the dictionary) that the comparison to use on the "object values" is the integer values of those objects. If this is correct, then I guess what I missed is that the method is sort of setting the "ground rules" for the comparison, without getting into the nitty-gritty of exactly which 2 values in the directory are being compared. Does that make any sense?
     
  6. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #6
    If you need a new method on NSNumber, which is what will be getting the compare message, you can add one via a category. This will allow you to add methods to a class for which you don't have the original code. This is often a good alternative to subclassing, because the class will act exactly as before, except when you call your one new method. Otherwise, odd behavior can occur if you override a basic method in a class which could be used by other methods.

    Also, while it may be unlikely, the actual numerical values of an enum might change. As such, I wouldn't game it by multiplying the compare: result by -1. A quick if should let you swap the return.

    -Lee
     
  7. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #7
    I don't see any practical way for the numerical values to change. The current values end up as compile-time constants in the code. If Apple were to change the values of the existing names, then it would be incompatible with kajillions of lines of code in bazillions of existing programs. To me, that seems like a vanishingly small possibility.
     
  8. mdeh thread starter macrumors 6502

    Joined:
    Jan 3, 2009
    #8

    Hi Lee,
    Thank you. So, added this:

    Code:
    @implementation NSNumber(myCompare)
    
    
    -(NSComparisonResult)inverseCompare:(id) obj2 
    {
    	
    	
    	if (  [self integerValue]  > [obj2 integerValue])
    	{
    		return (NSComparisonResult) NSOrderedAscending;
    	}
    	
    	else if  ( [self  integerValue]  < [obj2 integerValue])
    		
    	{
    		return (NSComparisonResult) NSOrderedDescending;
    	}
    	
    	else 
    	
    	{
    		return (NSComparisonResult) NSOrderedSame;
    	}
    	
    	
    }
    
    @end
    and in the custom class this calls it:

    Code:
    -(NSArray *)keyArray
    {
    	
    	return [[ self characterList] keysSortedByValueUsingSelector:@selector(inverseCompare:)];
    	
    }
    
    which works. Now!!! I would like to make sure I understand this...sorry for the pedantic approach.

    When I tell the receiver, ( which in this case is the dictionary) to sort the keys using the value **objects**, does cocoa examine these objects and decide that I ( the programmer) have NSNumber Objects stored as values to the keys, and thus , it ( cocoa) needs to use a comparison method which it will look for in the NSNumber class. So, my obligation, as the programmer, is to provide code, with the understanding that one of the objects ( the argument) will be an an object, from which I still have to extract, in my case, an integer value and the other object will be the "self" ie the object against which cocoa will do the comparison. If this is coming off a little confused, it's because I have not quite seen the clarity, but would appreciate your insight.
     
  9. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #9
    Since message passes are done at run-time, rather than fixed method addresses being generated for calls at compile-time, it allows for "UsingSelector"-type methods. As long as everything in the dictionary responds to the selector passed, even if they are different types (this would require more work to deal with, checking the type of the argument, etc.) this will work.

    See NSObject's performSelector for some better understanding on selectors and what happens with selectors and message passes in general at run-time:
    http://developer.apple.com/mac/libr...apple_ref/occ/intfm/NSObject/performSelector:

    -Lee
     
  10. mdeh thread starter macrumors 6502

    Joined:
    Jan 3, 2009
    #10
    Thanks Lee..it's almost clear :)
     

Share This Page