views:

4377

answers:

5

I'm curious if this is a situation any other folks have found themselves in. I have an NSDictionary (stored in a plist) that I'm basically using as an associative array (strings as keys and values). I want to use the array of keys as part of my application, but I'd like them to be in a specific order (not really an order that I can write an algorithm to sort them into). I could always store a separate array of the keys, but that seems kind of kludgey because I'd always have to update the keys of the dictionary as well as the values of the array, and make sure they always correspond. Currently I just use [myDictionary allKeys], but obviously this returns them in an arbitrary, non-guaranteed order. Is there a data structure in Objective-C that I'm missing? Does anyone have any suggestions on how to more elegantly do this?

+9  A: 

The solution of having an associated NSMutableArray of keys isn't so bad. It avoids subclassing NSDictionary, and if you are careful with writing accessors, it shouldn't be too hard to keep synchronised.

Abizern
One pitfall is that NS(Mutable)Dictionary makes copies of keys, so the objects in the key array will be different. This can be problematic if a key is mutated.
Quinn Taylor
That's a good point, but mitigated if the mutability is restricted to adding and removing object from the array rather than changing the items within them.
Abizern
Not sure how you'd restrict someone modifying a key for which they provided a pointer in the first place. The array just stores a pointer to that key, and won't know if it changes or not. If the key is changed, you may not even be able to remove the added entry from the array at all, which can be a real synchronization problem if you array keeps keys that are no longer in the dictionary, or vice versa... :-)
Quinn Taylor
BTW, my apologies if I sound overly critical. I've been dealing with this same problem myself, but since it's for a framework, I'm trying to design it to be robust and correct in every conceivable situation. You're correct that a separate array isn't *so* bad, but it's not as simple as a class that tracks it for you. Plus, there are numerous benefits if such a class extends NS(Mutable)Dictionary, since it could be used anywhere a Cocoa dictionary is required.
Quinn Taylor
I don't mind the discussion. My suggestion was just a workaround. Writing a framework is a different proposition entirely and it is something that needs careful thought. Personally, I find mutability troublesome for this very reason, and I try to minimise the use of those classes in my own programs.
Abizern
@Quinn Taylor: Late to the party here, but couldn't you avoid this problem by having your mutable array for keys *also* copy the key object before inserting it, just as the dictionary does?
quixoto
Not really, because then you'd have two different copies (different pointers) in the dictionary and array. What you want is to store the same pointer in both collections, so you can accurately find and/or remove objects which have been added, and which might have been mutated subsequently. Otherwise, the object might be removed from the dictionary but not the array, or vice versa
Quinn Taylor
+2  A: 

If you're going to subclass NSDictionary you need to implement these methods as a minimum:

  • NSDictionary
    • -count
    • -objectForKey:
    • -keyEnumerator
  • NSMutableDictionary
    • -removeObjectForKey:
    • -setObject:forKey:
  • NSCopying/NSMutableCopying
    • -copyWithZone:
    • -mutableCopyWithZone:
  • NSCoding
    • -encodeWithCoder:
    • -initWithCoder:
  • NSFastEnumeration (for Leopard)
    • -countByEnumeratingWithState:objects:count:

The easiest way to do what you want is to make a subclass of NSMutableDictionary that contains its' own NSMutableDictionary that it manipulates and an NSMutableArray to store an ordered set of keys.

If you're never going to encode your objects you could conceivable skip implementing -encodeWithCoder: and -initWithCoder:

All of your method implementations in the 10 methods above would then either go directly through your hosted dictionary or your ordered key array.

Ashley Clark
There's one more that's non-obvious and tripped me up — if you implement NSCoding, also override -(Class)classForKeyedArchiver to return [self class]. Class clusters set this to always return the abstract parent class, so unless you change that, instances will always be decoded as an NS(Mutable)Dictionary.
Quinn Taylor
+13  A: 

Matt Gallagher just wrote a blog post titled “OrderedDictionary: Subclassing a Cocoa class cluster” covering exactly this issue, complete with sample code.

Jon Shea
This might not be an accident. I had to write about something :-)
Matt Gallagher
It was a great post. One thing, if you want to preserve the O(1) amortized insertion time characteristic of a hash table, then you probably have to manage the size of the NSMutableArray yourself.
Jon Shea
I recently implemented something quite similar. However, one thing you have to address (that Matt doesn't in his post) is the fact that NS(Mutable)Dictionary makes a copy of each key, so the key in the array is actually a different object. A solution is to use a CFMutableDictionaryRef underneath. For implementation details, see http://dysart.cs.byu.edu/chsvn/CHDataStructures/source/CHLinkedDictionary.m
Quinn Taylor
A: 

Quick 'n dirty:

When you need to order your dictionary (herein called “myDict”), do this:

     NSArray *ordering = [NSArray arrayWithObjects: @"Thing",@"OtherThing",@"Last Thing",nil];

Then, when you need to order your dictionary, create an index:

    NSEnumerator *sectEnum = [ordering objectEnumerator];
    NSMutableArray *index = [[NSMutableArray alloc] init];
     id sKey;
     while((sKey = [sectEnum nextObject])) {
      if ([myDict objectForKey:sKey] != nil ) {
       [index addObject:sKey];
      }
     }

Now, the *index object will contain the appropriate keys in the correct order. Note that this solution does not require that all the keys necessarily exist, which is the usual situation we're dealing with...

Thinkingman.com
+2  A: 

I'm late to the game with an actual answer, but you might be interested to investigate CHOrderedDictionary. It's a subclass of NSMutableDictionary which encapsulates another structure for maintaining key ordering. (It's part of CHDataStructures.framework.) I find it to be more convenient than managing a dictionary and array separately.

Disclosure: This is open-source code which I wrote. Just hoping it may be useful to others facing this problem.

Quinn Taylor