Question

I'm working on a project that requires a tableView list of categorized grocery items. Each category can have n depth. The JSON response from the API looks like this.

"items":[
{
  "id":"5366f8d3e4b0e44dc2d4a6fb",
  "name":"String Cheese"
  "description":"Sargento String Cheese",
  "categorization":[
    [
      "Dairy",
      "Cheese"
    ]
  ]
},
{
  "id":"5366f8d3e4b0e44dc2d1a6fb",
  "name":"Budlight 6-pk"
  "description":"Budlight 12-pk",
  "categorization":[
    [
      "Beverages",
      "Alcohol",
      "Beer"
    ]
  ]
}
]

Right now I'm creating Item objects from the item dictionaries and storing them in a mutable array like below.

NSArray *itemsArray = [response objectForKey:items];

NSMutableArray *itemsMutableArray = [[NSMutableArray alloc] init];
for(NSDictionary *itemDict in itemsArray){
    Item *itemObj = [[Item alloc] initWithDictionary:itemDict]
    [itemsMutableArray addObject:itemObj];
}

I would like to loop through itemsMutableArray and create a tree data structure that has a path from the root to each of the items. Then, I would like to be able to use the tree as a datasource for tableViews in each level of category.

Here's what my Item class header looks like.

@interface Item : NSObject

@property (nonatomic, strong) NSString *id;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *description;
@property (nonatomic, strong) NSArray  *categorization;

@end

...and the implementation

#import "Item.h"

@implementation Item

- (id)initWithDictionary:(NSDictionary *)objDictionary{
    if (self = [super init]) {
        self.id = [objDictionary valueForKey:@"id"];
        self.name = [objDictionary valueForKey:@"name"];
        self.description = [objDictionary valueForKey:@"description"];
        self.categorization = [objDictionary valueForKey:@"categorization"];
    }
    return self;
}

@end

I am not very familiar with tree data structures and recursion. I would greatly appreciate any help on how to approach this. Thanks!

Was it helpful?

Solution

If you need simple node tree data structure. How about this way? Hope this little help.

Header

@interface ItemCategory : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic) ItemCategory *parent;
@property (nonatomic, strong) NSMutableArray *children;

-(id)initWithName:(NSString *)n parent:(ItemCategory *)p;
@end

@interface CategoryTree : NSObject

@property (nonatomic, strong) ItemCategory *root;

-(ItemCategory *)_getChildCategory:(ItemCategory *)category name:(NSString *)name;
-(ItemCategory *)_addChildCategory:(ItemCategory *)category name:(NSString *)name;
-(void)_dumpCategory:(ItemCategory *)category depth:(int)depth;

-(void)dump;
-(ItemCategory *)getCategory:(NSArray *)arr;
-(void)addCategory:(NSArray *)arr;

@end

Source

@implementation CategoryTree
@synthesize root;

-(id)init {
    if (self = [super init]) {
        root = [[ItemCategory alloc] initWithName:@"root" parent:nil];
    }
    return self;
}

-(ItemCategory *)_getChildCategory:(ItemCategory *)category name:(NSString *)name {
    for (ItemCategory *child in category.children)
        if ([child.name isEqualToString:name])
            return child;

    return nil;
}

-(ItemCategory *)_addChildCategory:(ItemCategory *)category name:(NSString *)name {
    ItemCategory *child = [self _getChildCategory:category name:name];
    if (child)
        return child;
    child = [[ItemCategory alloc] initWithName:name parent:category];
    [category.children addObject:child];

    return child;
}

-(void)_dumpCategory:(ItemCategory *)category depth:(int)depth{
    NSString *parentStr = @"";
    ItemCategory *parent = category.parent;
    while (parent) {
        parentStr = [NSString stringWithFormat:@"%@%@%@", parent.name, parentStr.length > 0 ? @">" : @"", parentStr];
        parent = parent.parent;
    }
    NSLog(@"%@%@%@", parentStr, parentStr.length > 0 ? @">" : @"", category.name);

    for (ItemCategory *child in category.children) {

        [self _dumpCategory:child depth:depth + 1];
    }
}

-(void)dump {
    [self _dumpCategory:root depth:0];
}

-(ItemCategory *)getCategory:(NSArray *)arr {
    ItemCategory *category = root;
    for (NSString *categoryName in arr) {
        category = [self _getChildCategory:category name:categoryName];
        if (!category)
            return nil;
    }
    return category;
}

-(void)addCategory:(NSArray *)arr {
    if ([self getCategory:arr])
        return;

    ItemCategory *category = root;
    for (NSString *categoryName in arr) {
        ItemCategory *childCategory = [self _getChildCategory:category name:categoryName];
        if (!childCategory) {
            childCategory = [self _addChildCategory:category name:categoryName];
        }
        category = childCategory;
    }
}
@end

Usage

CategoryTree *tree = [[CategoryTree alloc] init];
[tree addCategory:@[@"Dairy", @"Cheese"]];
[tree addCategory:@[@"Dairy", @"Milk"]];
[tree addCategory:@[@"Beverages", @"Alcohol", @"Beer"]];
[tree addCategory:@[@"Beverages", @"Alcohol", @"Wine"]];
[tree addCategory:@[@"Beverages", @"Non-Alcohol", @"Cola"]];
[tree dump];

Result

root
root>Dairy
root>Dairy>Cheese
root>Dairy>Milk
root>Beverages
root>Beverages>Alcohol
root>Beverages>Alcohol>Beer
root>Beverages>Alcohol>Wine
root>Beverages>Non-Alcohol
root>Beverages>Non-Alcohol>Cola

OTHER TIPS

well I have found a way to implement what you need. I do not know how optimised it is since i do not how many items you'll be receiving . The implementation is given below.

You need to start with adding this dictionary in Item.h @property (nonatomic, strong) NSMutableDictionary *catTree;

Next do this to get the tree

[itemsMutableArray enumerateObjectsUsingBlock:^(Item *itm, NSUInteger i,BOOL *stop){
        itm.catTree = [NSMutableDictionary dictionary];
        NSString *dairy = @"",*beverage = @"";
        for (NSArray *catArray in itm.categorization) {
            /*
                Everything below is written assuming the format of the JSON will be "as-is"
             */

            if ([catArray containsObject:@"Dairy"]) {
                //Take everything except Dairy
                NSArray *stripedArray = [catArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != \"Dairy\""]];
                int i = 0;
                //Loop through the array to get any sub categories.
                while (i < stripedArray.count) {
                    dairy = [dairy stringByAppendingString:[NSString stringWithFormat:(i == stripedArray.count-1)?@"%@ ":@"%@->",stripedArray[i]]]; //Space at the end to account for similar entry in the same category for e.g two dairy products.
                    i++;
                }
            } else  if ([catArray containsObject:@"Beverages"]) {
                NSArray *stripedArray = [catArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF != \"Beverages\""]];
                int i = 0;
                while (i < stripedArray.count) {
                    beverage = [beverage stringByAppendingString:[NSString stringWithFormat:(i == stripedArray.count-1)?@"%@ ":@"%@->",stripedArray[i]]];
                    i++;
                }
            }
        }


        //Set the category tree for every item using a dictionary
        [itm.catTree setValue:dairy forKey:@"Dairy"];
        [itm.catTree setValue:beverage forKey:@"Beverage"];

        NSLog(@"%@",itm.catTree);
    }];

the above code gives the following output for your json

{
    Beverage = "";
    Dairy = "Cheese ";
}
{
    Beverage = "Alcohol->Beer ";
    Dairy = "";
}

For multiple beverages

{
   Beverage = "Alcohol->Beer Alcohol->Wine->Red Soda->Coke ";
   Dairy = "";
}

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top