views:

153

answers:

3

I have an unusual problem where I have a list of fruits (apple, orange, banana, grapes, etc etc). They can be organized into small groups for example:

Group 1: apple, green_apple, pineapple
Group 2: grapes, banana, strawberries, apple
Group 3: orange, grapes, green_apple

Each group is then associated with a price. So Group 1->9.99, Group 2-> 15.99 etc

And then later on when given a list, they need to be matched against existing groups, and if none was found, create one. If one exists, return the price.

What is the best way to do this? Basically the association needs to be an NSDictionary, but the key seems like it should be an array. The problem then is, how do I construct the key when given some input? I have to resort to keeping things in alphabetical order for keys to be consistent? But then this approach would not be too good when names includes symbols ($apple etc).

What would your approach be?

+3  A: 

Use an NSSet instead of an NSArray (Sets are unordered lists, and it sounds like that's what you've got here). However, I'm not sure that NSDictionary will use the [NSSet isEqualToSet:] (or whatever it's called) method rather than pointer equality when determining keys, so you might have to implement your own key-checking method.

Ian Henry
+1  A: 

Seems like you can use NSSet as key to NSDictionary:

NSMutableDictionary * dict = [NSMutableDictionary dictionary];

NSSet * set;
set = [NSSet setWithObjects:@"a", @"b", @"c", @"d", nil];
[dict setObject:@"1" forKey:set];

set = [NSSet setWithObjects:@"b", @"c", @"d", @"e", nil];
[dict setObject:@"2" forKey:set];

id key;
NSEnumerator * enumerator = [dict keyEnumerator];
while ((key = [enumerator nextObject]))
    NSLog(@"%@ : %@", key, [dict objectForKey:key]);

set = [NSSet setWithObjects:@"c", @"b", @"e", @"d", nil];
NSString * value = [dict objectForKey:set];
NSLog(@"set: %@ : key: %@", set, value);

Outputs:

2009-12-08 15:42:17.885 x[4989] (d, e, b, c) : 2
2009-12-08 15:42:17.887 x[4989] (d, a, b, c) : 1
2009-12-08 15:42:17.887 x[4989] set: (d, e, b, c) : key: 2


Another way is to use NSMutableDictionary that holds multiple NSSet's of items and price as a key, however is noted this will not work if the price is not unique.

You check manually if the set is in dictionary by iterating over items and for each set using isEqualToSet: - unless anyone can come up with better way.

If it is you return the price (key) if it is not you can insert it with a price, the main parts:

@interface ShoppingList : NSObject
{
    NSMutableDictionary * shoppingList;
}


- (void)setList:(NSSet*)aList
       forPrice:(double)aPrice
{
    [shoppingList setObject:aList forKey:[NSNumber numberWithDouble:aPrice]];
}


- (double)priceForList:(NSSet*)aList
{
    id key;
    NSEnumerator * enumerator = [shoppingList keyEnumerator];
    while ((key = [enumerator nextObject]))
    {
        NSSet * list = [shoppingList objectForKey:key];

        if ([aList isEqualToSet:list])
        {
            return [(NSNumber*)key doubleValue];
        }
    }

    return 0.0;
}


{
    ShoppingList * shoppingList = [[ShoppingList alloc] init];

    NSSet * list;
    double price = 0.0;

    list =
      [NSSet setWithObjects:@"apple",@"green_apple",@"pineapple",nil];
    [shoppingList setList:list forPrice:9.99];

    list =
      [NSSet setWithObjects:@"grapes",@"banana",@"strawberries",@"apple",nil];
    [shoppingList setList:list forPrice:15.99];

    list =
     [NSSet setWithObjects:@"orange",@"grapes",@"green_apple",nil];
    [shoppingList setList:list forPrice:7.50];

    // searching for this
    list =
      [NSSet setWithObjects:@"grapes",@"banana",@"strawberries",@"apple",nil];

    price = [shoppingList priceForList:list];
    if (price != 0.0)
    { 
        NSLog(@"price: %.2f, for pricelist: %@", price, list);
    }
    else
    {
        NSLog(@"shopping list not found: %@", list);
        [shoppingList setList:list forPrice:15.99];
    }
}
stefanB
But what if the price wasn't unique?
erotsppa
Then store the objects in NSMutableSets, and instead of adding a new set with the same price check to see if a set exists with that price already, and add objects into it if so.
Kendall Helmstetter Gelner
All I can think of in case when price is not unique is to create record, either custom class or NSDictionary of NSSet, the list of items, and price, and for key you would use something like unique id (int).
stefanB
If isEqualToSet works for a Set of strings why can't you use the list as a key and just let NSDictionary retrieves the price based on the set?
erotsppa
A: 

You could use a simple CoreData solution for this, and in the long run, it will probably be easiest. For your scenario, you could create two entities, let's say Item and PriceGroup. The Item entity could have a name attribute and a to-one priceGroup relationship. The PriceGroup entity could have a description attribute (although, in your scenario, it doesn't even need that), a price attribute and a to-many items relationship. Assuming you set up these two, simple entities, and get a context, you could easily create your associations like so in code:

- (void)createGroup:(NSNumber*)price withProducts:(NSSet*)productNames
{
  // assume context exists and was retrieved from somewhere
  NSManagedObject* priceGroup = [NSEntityDescription insertNewObjectForEntityForName:@"PriceGroup" inManagedObjectContext:context];
  [priceGroup setValue:price forKey:@"price"];

  for( NSString* productName in productNames ) {
    NSManagedObject* product = [NSEntityDescription insertNewObjectForEntityForName:@"Item" inManagedObjectContext:context];
    [product setValue:productName forKey:@"name"];
    [product setValue:priceGroup forKey:@"priceGroup"];
  }
}

You would now have all your products associated with your price group in your managed object context. That means you can do all kinds of queries on them, sort them easily, etc.

As another example, here is how you might get all items whose group price is over $9.99 but not in some specific group (let's say the $15.99 sale price group):

- (NSSet*)itemsAsDescribed
{
  // Get this from somewhere
  NSManagedObjectContext* context = /* get this or set this */
  NSManagedObject* exclusionGroup = /* get this or set this */
  NSFetchRequest* request = [[[NSFetchRequest alloc] init] autorelease];
  NSPredicate* predicate = [NSPredicate predicateWithFormat:@"priceGroup.price >= 9.99 AND NOT SELF in %@", [exclusionGroup valueForKey:@"items"]];
  [request setEntity:[NSEntityDescription entityForName:@"Item" inManagedObjectContext:context]];
  [request setPredicate:predicate];
  NSError* error = nil;
  NSSet* results = nil;
  NSArray* array = [context executeFetchRequest:request error:&error];
  if( !array ) {
    // handle the error
  } else {
    results = [NSSet setWithArray:array];
  }
  return results;
}

In this case, since there are not many-to-many relationships, having the set is unnecessary, however, if you changed it in the future so that an item could be in multiple groups (like a regular price group and a sale price group), this would be necessary to ensure you didn't get duplicate items in your result.

Jason Coco