Split NSArray into sub-arrays based on NSDictionar

2019-04-09 17:27发布

问题:

We have an app that calls a SOAP web service and retrieves a long list of XML, which the app then parses into an NSArray of NSDictionary objects. The NSArray contains a list of Rental Apartment information, each of which is stored into an NSDictionary.

The entire list may contain 10 different types of Apartments (i.e. 2-room, 3-room), and we need to split the NSArray into smaller NSArrays based on Room-Type, which has the key "roomType" in the NSDictionary objects.

Currently our algorithm is

  1. Use [NSArray valueForKeyPath:@"@distinctUnionofObjects.room-type"] to obtain a list of unique room-type values.
  2. Loop through the list of unique room-type values
  3. For each unique room-type value, use NSPredicate to retrieve matching items from the Original list

Our code is below (renamed for clarity):

NSArray *arrOriginal = ... ...; // Contains the Parsed XML list

NSMutableArray *marrApartmentsByRoomType = [NSMutableArray arrayWithCapacity:10];

NSMutableArray *arrRoomTypes = [arrOriginal valueForKeyPath:@"distinctUnionOfObjects.roomType"];

for(NSString *strRoomType in arrRoomTypes) {
  NSPredicate *predicateRoomType = [NSPredicate predicateWithFormat:@"roomType=%@", strRoomType];

  NSArray *arrApartmentsThatMatchRoomType = [arrOriginal filteredArrayUsingPredicate:predicateRoomType];  // TAKES A LONG TIME EACH LOOP-ROUND

  [marrApartmentsByRoomType addObject:arrApartmentsThatMatchRoomType];
}

However, step 3 is taking a long time as the original list may contain large amount (>100,000) of items. It seems that NSPredicate goes through the entire list for each key value. Is there a more efficient way of splitting a large NSArray into smaller NSArrays, based on NSDictionary keys?

回答1:

If the order of your splited Arrays is not important, i have a solution for you:

NSArray *arrOriginal;
NSMutableDictionary *grouped = [[NSMutableDictionary alloc] initWithCapacity:arrOriginal.count];
for (NSDictionary *dict in arrOriginal) {
    id key = [dict valueForKey:@"roomType"];

    NSMutableArray *tmp = [grouped objectForKey:key];
    if (tmp == nil) {
        tmp = [[NSMutableArray alloc] init];
        [grouped setObject:tmp forKey:key];
    }
    [tmp addObject:dict];
}
NSMutableArray *marrApartmentsByRoomType = [grouped allValues];


回答2:

This is quite performant

- (NSDictionary *)groupObjectsInArray:(NSArray *)array byKey:(id <NSCopying> (^)(id item))keyForItemBlock
{
    NSMutableDictionary *groupedItems = [NSMutableDictionary new];
    for (id item in array) {
        id <NSCopying> key = keyForItemBlock(item);
        NSParameterAssert(key);

        NSMutableArray *arrayForKey = groupedItems[key];
        if (arrayForKey == nil) {
            arrayForKey = [NSMutableArray new];
            groupedItems[key] = arrayForKey;
        }
        [arrayForKey addObject:item];
    }
    return groupedItems;
}


回答3:

Improving @Jonathan answer

  1. Converting array to dictionary
  2. Maintaining the same order as it was in original array

    //only to a take unique keys. (key order should be maintained)
    NSMutableArray *aMutableArray = [[NSMutableArray alloc]init];
    
    NSMutableDictionary *dictFromArray = [NSMutableDictionary dictionary];
    
    for (NSDictionary *eachDict in arrOriginal) {
    //Collecting all unique key in order of initial array
    NSString *eachKey = [eachDict objectForKey:@"roomType"];
    if (![aMutableArray containsObject:eachKey]) {
        [aMutableArray addObject:eachKey];
    }
    
    NSMutableArray *tmp = [grouped objectForKey:key];
    tmp  = [dictFromArray objectForKey:eachKey];
    
    if (!tmp) {
        tmp = [NSMutableArray array];
        [dictFromArray setObject:tmp forKey:eachKey];
    }
    [tmp addObject:eachDict];
    
    }
    
    //NSLog(@"dictFromArray %@",dictFromArray);
    //NSLog(@"Unique Keys :: %@",aMutableArray);
    

    //Converting from dictionary to array again...

    self.finalArray = [[NSMutableArray alloc]init];
    for (NSString *uniqueKey in aMutableArray) {
       NSDictionary *aUniqueKeyDict = @{@"groupKey":uniqueKey,@"featureValues":[dictFromArray objectForKey:uniqueKey]};
    [self.finalArray addObject:aUniqueKeyDict];
    }
    

Hope, It will help when client wants final array in same order as input array.