pasine
pasine

Reputation: 11543

Create a structured NSDictionary from a NSArray

I need to create a structured NSDictionary with grouped keys, starting from a NSArray.
This is an example:

[
    {
        section = section1,
        category = category1,
        date = 2011-12-01,
        key1 = foo,
        key2 = bar
    },
    {
        section = section1,
        category = category2,
        date = 2011-12-01,
        key1 = foo,
        key2 = bar
    },
    {
        section = section1,
        category = category2,
        date = 2011-12-03
        key1 = foo,
        key2 = bar
    },
    {
        section = section2,
        category = category1,
        date = 2011-12-03
        key1 = foo,
        key2 = bar
    }
]  

The result should be a NSDictionary like this (I didn't checked that the values are ok, I just want to give the idea):

[
    section1 = {
        category1 = {
            2011-12-01 = 
                [{
                    key1 = foo;
                    key2 = bar;
                },
                {
                    key1 = foo;
                    key2 = bar;
                }
                ]
            }
        },
        category2 = {
            2011-12-01 =
                [{
                    key1 = foo;
                    key2 = bar;
                }],
        }
    },
    section2 = {
        category1 = {
            2011-12-01 =
                [{
                    key1 = foo;
                    key2 = bar;
                }]
        }
    }
]

Can I achieve this using NSPredicate or Key-Value Coding, and avoid many loops?

My proposed solution

NSMutableDictionary *sectionEmpty = [NSMutableDictionary dictionary];
NSArray *values = [records allValues];

NSArray *sections = [[records allValues] valueForKeyPath:@"@distinctUnionOfObjects.section"];

for(NSString *section in sections) {
    // Find records for section
    NSArray *sectionRecords = [values filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"section == %@",section]];

    // Find unique categories
    NSArray *categorys = [sectionRecords valueForKeyPath:@"@distinctUnionOfObjects.category"];

    // Loop through categories
    for (NSString *category in categorys) {

        // Creating temporary record
        NSMutableDictionary *empty = [NSMutableDictionary dictionary];

        // Find records for category
        NSArray *categoryRecords = [sectionRecords filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"category == %@",category]];

        // Find unique dates
        NSArray *dates = [categoryRecords valueForKeyPath:@"@distinctUnionOfObjects.date"];

        // Loop through dates
        for (NSString *date in dates) {

            // Creating temporary record
            NSMutableDictionary *emptyDate = [NSMutableDictionary dictionary];

            // Find records for dates
            NSArray *dateRecords = [categoryRecords filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"date == %@",date]];

            // Split date
            NSString *dateString = [[date componentsSeparatedByString:@" "] objectAtIndex:0];

            // Check if date exist in temporary record
            if(![[emptyDate allKeys] containsObject:dateString]){
                [emptyDate setObject:[NSMutableArray array] forKey:dateString];
            }

            // Set date records for date key
            [[emptyDate objectForKey:dateString] addObject:dateRecords];

            // Set date for category
            [empty setObject:emptyDate forKey:category];
        }

        // Set category for section
        [sectionEmpty setObject:empty forKey:section];
    }

}

Upvotes: 0

Views: 1845

Answers (1)

ayoy
ayoy

Reputation: 3835

It seems the only way you can achieve what you want is a series of nested for loops (for each level: section, category, date). In the innermost loop you create a complex predicate like this:

NSPredicate *predicate = [NSPredicate predicateWithFormat:
                          @"section = %@ AND category = %@ AND date = %@",
                          section, category, date];

Then you filter out the input array, and construct new array of dictionaries with key1 and key2 only. You add this dates array to categories dictionary for a given date, and then you add a categories dictionary to the output dictionary for a given category. I don't see any simpler way of doing this.

NSArray *input = [records allValues];
NSMutableDictionary *output = [NSMutableDictionary dictionary];


NSArray *sections = [NSArray valueForKeyPath:@"@distinctUnionOfObjects.section"];
NSArray *categories = [NSArray valueForKeyPath:@"@distinctUnionOfObjects.category"];
NSArray *dates = [NSArray valueForKeyPath:@"@distinctUnionOfObjects.date"];

NSPredicate *predicate;
for (NSString *section in sections) {
    NSMutableDictionary *categoriesDictionary = [NSMutableDictionary dictionary];

    for (NSString *category in categories) {
        NSMutableArray *datesArray = [NSMutableArray array];

        for (NSString *date in dates) {
            predicate = [NSPredicate predicateWithFormat:
                         @"section = %@ AND category = %@ AND date = %@",
                         section, category, date];

            NSArray *filteredInput = [input filteredArrayUsingPredicate:predicate];
            if (filteredInput.count > 0) {
                NSMutableArray *filteredOutput = [NSMutableArray arrayWithCapacity:filteredInput.count];

                [filteredInput enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
                    [filteredOutput addObject:
                     [NSDictionary dictionaryWithObjectsAndKeys:
                      [obj objectForKey:@"key1"], @"key1",
                      [obj objectForKey:@"key2"], @"key2", nil]
                     ];
                }];

                if (filteredOutput.count > 0)
                    [datesArray addObject:filteredOutput];
            }
        }
        if (datesArray.count > 0)
            [categoriesDictionary setObject:datesArray forKey:category];
    }
    if (categoriesDictionary.count > 0)
        [output setObject:categoriesDictionary forKey:section];
}

Upvotes: 1

Related Questions