How to search and combine elements inside NSDictionaries as arrays? - ios

As a concrete example, suppose I have the following array of dictionaries, which share the same keys and value types:
[ {car: "Ferrari", make: "Italian", color: "red"},
{car: "Ferrari", make: "Italian", color: "yellow"},
{car: "Porsche", make: "German", color: "green"},
{car: "Porsche", make: "German", color: "silver"}
]
How would I be able to search only by a key (for example, by "car") through the dictionaries, and then combine the values that are different (in this case, the "color") as arrays, so that my resultant array is:
[ {car: "Ferrari", make: "Italian", color: ["red", "yellow"]},
{car: "Porsche", make: "German", color: ["green", "silver"]},
]
Thanks!

The following method will solve your problem:
- (NSArray*) combineValuesFromArray:(NSArray*) array byKey: (NSString*) key
{
NSMutableSet *differentValues = [NSMutableSet set];
for(NSDictionary *dict in array)
{
[differentValues addObject:dict[key]];
}
NSMutableArray *result = [NSMutableArray array];
for(NSString *value in differentValues)
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K == %#", key, value];
NSArray *filteredArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary *sets = [NSMutableDictionary new];
for(NSDictionary *dict in filteredArray)
{
for(NSString *dictKey in dict)
{
NSMutableSet *set = [sets[dictKey] mutableCopy];
if(set == nil) set = [NSMutableSet new];
[set addObject:dict[dictKey]];
sets[dictKey] = set;
}
}
NSMutableDictionary *newDict = [NSMutableDictionary new];
for(NSString *dictKey in sets)
{
newDict[dictKey] = ([sets[dictKey] count] == 1) ? [sets[dictKey] anyObject] : [sets[dictKey] copy];
}
[result addObject:[newDict copy]];
}
return [result copy];
}
I've used following code for testing:
NSString *car = #"car";
NSString *make = #"make";
NSString *color = #"color";
NSArray *array = #[ #{car: #"Ferrari", make: #"Italian", color: #"red"},
#{car: #"Ferrari", make: #"Italian", color: #"yellow"},
#{car: #"Porsche", make: #"German", color: #"green"},
#{car: #"Porsche", make: #"German", color: #"silver"}
];
NSLog(#"%#", [self combineValuesFromArray:array byKey:car]);

Here is my approach with a little better time complexity. The first function convertDataBasedOnKey will receive the value from which you may want to compare, as you said on the question you wanted to be searchable by a key. The second function getSharedDictionaryOfElements is just for creating the final dictionary with the values merged.
- (void)convertDataBasedOnKey:(NSString *)baseKey {
desiredFormatData = [[NSMutableArray alloc] init];
NSMutableString *filterString = [NSMutableString stringWithFormat:#"(%# ==",baseKey];
[filterString appendString:#" %#)"];
while (currentFormatData.count != 0) {
NSDictionary *aDictionary = [currentFormatData firstObject];
NSString *firstValue = [aDictionary objectForKey:baseKey];
NSArray *filtered = [currentFormatData filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:filterString, firstValue]];
NSDictionary *mergedDictionary = [self getSharedDictionaryOfElements:filtered sharingValueForKey:baseKey];
[desiredFormatData addObject:mergedDictionary];
[currentFormatData removeObjectsInArray:filtered];
}
NSLog(#"%#",desiredFormatData.description);
}
- (NSDictionary *)getSharedDictionaryOfElements:(NSArray *)anArray sharingValueForKey:(NSString *)aKey {
if (!anArray || anArray.count == 0) {
return nil;
}
// As all the elements in anArray share the same keys
NSMutableDictionary *resultDictionary = [[NSMutableDictionary alloc] init];
NSDictionary *modelDictionary = [anArray firstObject];
for (NSString *aKey in modelDictionary.allKeys) {
NSPredicate *filterByKey = [NSPredicate predicateWithValue:YES];
NSArray *resultArray = [[anArray valueForKey:aKey] filteredArrayUsingPredicate:filterByKey];
NSMutableArray *uniqueArray = [NSMutableArray arrayWithArray:[[NSSet setWithArray:resultArray] allObjects]];
[resultDictionary setObject:uniqueArray forKey:aKey];
}
return resultDictionary;
}
Example
Using this two functions to obtain what you asked will be something like this:
Having declared this two variables:
NSMutableArray *currentFormatData;
NSMutableArray *desiredFormatData;
Then filling the first one with the example data and making the operation based on a key "car":
currentFormatData = [NSMutableArray arrayWithArray:#[#{#"car":#"Ferrari", #"make":#"Italian", #"color":#"red"},
#{#"car":#"Ferrari", #"make":#"Italian", #"color":#"yellow"},
#{#"car":#"Porsche", #"make":#"German", #"color":#"green"},
#{#"car":#"Porsche", #"make":#"German", #"color":#"silver"}]];
[self convertDataBasedOnKey:#"car"];

Related

Given an array of NSDictionary object how to get all the keys?

If you have an array of dictionaries, how do I create a new array containing all the keys present for each dictionary in the array ?
NSArray *array = #[#{#"key1" : #"value 1"},
#{#"key2" : #"value 2"},
#{#"key3" : #"value 3"} ];
// how to achieve this?
NSArray *allKeys = #{#"key1", #"key2", #"key3"};
If you know that each element in the array is an NSDictionary, you can call the allKeys method on each item in the array. I've added a type check to this example in case your array contains other objects that are not NSDictionary:
NSArray *array = #[#{#"key1" : #"value 1"},
#{#"key2" : #"value 2"},
#{#"key3" : #"value 3"}];
NSMutableArray *allKeys = [#[] mutableCopy];
for (id obj in array) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *dict = obj;
[allKeys addObjectsFromArray:[dict allKeys]];
}
}
NSLog(#"%#", allKeys);
Logs:
2016-04-20 11:38:42.096 ObjC-Workspace[10684:728578] (
key1,
key2,
key3
)
And if you need an immutable NSArray instead of an NSMutableArray:
NSArray *allKeysImmutable = [allKeys copy];
plz use this code, I think it helps you
NSArray *array = #[#{#"key1" : #"value 1"},
#{#"key2" : #"value 2"},
#{#"key3" : #"value 3"} ];
NSMutableArray *key_array=[NSMutableArray array];
for (NSDictionary *dictionary in array) {
NSArray *key_dictionary=[dictionary allKeys];
for (NSString *string_key in key_dictionary) {
[key_array addObject:string_key];
}
}
NSLog(#"%#",key_array);
Although Objective-C lacks an array-flattening method, you can nevertheless simplify the outer step:
NSMutableArray *result = [[NSMutableArray alloc] init];
for(NSArray *keys in [array valueForKey:#"allKeys"])
[result addObjectsFromArray:keys];
return [result copy];
Or, if you need keys deduplicated:
NSMutableSet *result = [[NSMutableSet alloc] init];
for(NSArray *keys in [array valueForKey:#"allKeys"])
[result unionSet:[NSSet setWithArray:keys]];
return [result allObjects];
... the only type assumption being (only slightly looser than) that array is all dictionaries. If you can annotate the collections any further then I recommend that you do.

NSPredicate for array of dictionaries, and the dict has a value of array

I have an array with dictionaries. The dictionary has a value of array, like:
{ #"categories" : [#"equipment", #"weapon", #"sword"] }
I want to find out all the dictionaries in that array whose categories contains weapon, I tried:
NSString *category = #"weapon";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.categories contains %#", category];
NSArray *resultArray = [dictionaryArray filteredArrayUsingPredicate:predicate];
But resultArray is empty.
How can I do it right?
You can try this,
NSString *category = #"weapon";
NSArray *items = #[#{ #"categories" : #[#"equipment", #"weapon", #"sword"] },
#{ #"categories" : #[#"toys", #"weapon", #"sword"] },
#{ #"categories" : #[#"arrow", #"bow", #"sword"] }];
NSPredicate*predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
NSArray *categories = [evaluatedObject objectForKey:#"categories"];
return [categories containsObject:category];
}];
NSArray *filteredArray = [items filteredArrayUsingPredicate:predicate];
In my case it outputs like,
(
{
categories = (
equipment,
weapon,
sword
);
},
{
categories = (
toys,
weapon,
sword
);
}

Merge an array into a dictionary

I am trying to take an array and merge it into an array of dictionaries but unsure as to how to do it.
I have an array of dictionaries that looks like this:
(
{
caption = a;
urlRep = "12";
},
{
caption = b;
urlRep = "34";
},
{
caption = c;
urlRep = "56";
}
)
and given an array like this:
(12,34,56,78)
I want to merge it into my dictionaries to make it look like this:
(
{
caption = a;
urlRep = "12";
},
{
caption = b;
urlRep = "34";
},
{
caption = c;
urlRep = "56";
},
{
caption = "";
urlRep = "78";
}
)
edit:
I need to also consider removing from the array of dicts if the given array does not contain one of the urlReps.
Any help would be greatly appreciated as I've been stuck trying to figure this out for some time.
Here's a simple, efficient and elegant solution using NSSets to handle unique keys:
NSMutableArray *arrayOfDicts; // your input array of dictionaries
NSArray *urlRepArray; // the new array with string elements
// create a set of potentially new keys (urlReps)
NSMutableSet *urlReps = [NSMutableSet setWithArray:urlRepArray];
// remove existing keys from your original array
[urlReps minusSet:[NSSet setWithArray:[arrayOfDicts valueForKey:#"urlRep"]]];
// merge new dicts to the original array
for (id urlRep in urlReps)
[arrayOfDicts addObject:#{ #"urlRep" : urlRep, #"caption" : #"" }];
Easiest way AFAIK, Filter using valueForKeyPath
//Your array of dictionary I created here for debugging purpose.
NSArray *tmpArray = #[ #{#"caption":#"a",#"urlRep":#"12"},
#{#"caption":#"b",#"urlRep":#"34"},
#{#"caption":#"c",#"urlRep":#"56"}];
//This will give you 12,34,56 in your case
NSArray *existingURLRep = [tmpArray valueForKeyPath:#"urlRep"];
NSMutableArray *targetArray = [[NSMutableArray alloc] initWithObjects:#12, #34,#56, #78, nil]; //Assuming you have your array as you said
[targetArray removeObjectsInArray:existingURLRep];
//remove existing items you will have 78 here now loop through
//this targetArray and add it to your array of dictionary.
(void)filterArray{
NSLog(#"Array before filtering = %#",initialArray);
NSLog(#"given Array = %#",givenArray);
NSMutableSet *urlReps = [NSMutableSet setWithArray:givenArray];
// remove existing records
[urlReps minusSet:[NSSet setWithArray:[initialArray valueForKey:#"urlRep"]]];
// adding new objects
for (id obj in urlReps) {
[initialArray addObject:#{#"caption":#"", #"urlRep" : obj}];
}
// removing objects
NSMutableSet *set = [[NSMutableSet alloc] init];
for (id obj in initialArray) {
NSDictionary *dict = (NSDictionary *)obj;
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"self = %#", dict[#"urlRep"]];
NSArray *filteredArray = [givenArray filteredArrayUsingPredicate:predicate];
if(filteredArray.count == 0) {
[set addObject:dict];
}
}
[initialArray removeObjectsInArray:[set allObjects]];
NSLog(#"Array after filtering = %#",initialArray);
}
NSMutableArray *yourArray;//This will be your original array of dictionary.
NSArray *newArray;//This is your new array which you want to add.
for(id obj in newArray) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"urlRep = %#", id];
NSArray *filteredArray = [locationsArray filteredArrayUsingPredicate:predicate];
if(filteredArray.count == 0) {
[yourArray addObject:#{#"caption":#"", #"urlRep" : id}];
}
}
/*
NSArray *inputArray;//(12,34,56,78)- I assumes you are having array which contains strings. If you are having number then modify the code as you needed
NSMutableArray *colloectionArray;// your total collection
NSMutableArray *tobeMerged;
*/
// Extract the dictionary set only to be merged
for (NSString* aNumber in inputArray) {
for (NSDictionary *aItem in colloectionArray) {
NSString *urlRep= [aItem valueForKey:#"urlRep"];
if (![urlRep isEqualToString:aNumber]) {
[tobeMerged addObject:urlRep];
}
}
}
// Add missed items in collection
for (NSString *aNumber in tobeMerged) {
NSMutableDictionary *newset = [[NSMutableDictionary alloc]init];
[newset setObject:#"" forKey:#"caption"];
[newset setObject:aNumber forKey:#"urlRep"];
[colloectionArray addObject:newset];
}

Filter NSArray to make new NSArray for use on tableview methods

I have a NSArray that looks like this:
{"result":
[
{
"epoch":"1371333600"
},
{
"epoch":"1371420000"
},
{
"epoch":"1371333600"
}
]
}
I want to sort the NSArray and make a new one so i can use it easier with the tableview methods to count the sections and rows.
All the dates that are the same need to be in one section.
The array should look like the example below but i don’t know how to get there. I have tried NSPredicate and used a loop but it won’t work.
What i want:
{"result":
[
{"data":
[
{
"epoch":"1371333600"
},
{
"epoch":"1371333600"
}
]
},
{"data":
[
{
"epoch":"1371420000"
}
]
}
]
}
My NSPredicate looks like this, but does not give me the result.
_finalArray = [[NSMutableArray alloc] init];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"epoch IN %#", [_resultArray valueForKey:#"epoch"]];
_predicateDate = [NSMutableArray arrayWithArray:[dataSortArray filteredArrayUsingPredicate:predicate]];
if ([_predicateDate count] != 0)
{
NSDictionary *itemsArrayDict = [NSDictionary dictionaryWithObject:_predicateDate forKey:#"data"];
[_finalArray addObject:itemsArrayDict];
}
NSOrderedSet is awesome for this occasion as it allows you to get the unique strings.
NSDictionary *dict1 = [NSDictionary dictionaryWithObject:#"2222222" forKey:#"epoch"];
NSDictionary *dict2 = [NSDictionary dictionaryWithObject:#"2222222" forKey:#"epoch"];
NSDictionary *dict3 = [NSDictionary dictionaryWithObject:#"1111111" forKey:#"epoch"];
NSArray *dictArray = #[dict1, dict2, dict3];
NSMutableArray *finalArray = [[NSMutableArray alloc]init];
NSArray *epoches = [dictArray valueForKey:#"epoch"];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:epoches];
for (NSString *string in orderedSet) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"epoch == %#", string];
NSArray *resultsArray = [dictArray filteredArrayUsingPredicate:predicate];
[finalArray addObject:resultsArray];
}
Hi you can use the NSPredicate to filter an array like:
//NSPredicate to filter an array
NSArray *data = [NSArray arrayWithObject:[NSMutableDictionary dictionaryWithObject:#"hello" forKey:#"Test"]];
NSArray *filtered = [data filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(Test == %#)", #"hello"]];
Thanks
It appears that you are using dictionaries along with arrays. Here's what I've achieved:
(result
(data
{
epoch = 1371333600;
},
{
epoch = 1371333600;
}
),
(data
{
epoch = 1371420000;
}
)
)
I'm not using predicates, but it looks as it's working :). Here is the code:
// Initial input
NSArray *result = #[#{#"epoch":#"1371333600"},#{#"epoch":#"1371420000"},#{#"epoch":#"1371333600"}];
// Get unique values for the input
NSSet *resultSet = [NSSet setWithArray:result];
// Here we are going to store the final result
NSMutableArray *newResult = [[NSMutableArray alloc] init];
// Loop over the unique items
for (NSDictionary *uniqueItem in resultSet) {
// Here we are going to store the grouped data
NSMutableArray *dataResult = [[NSMutableArray alloc] init];
// Loop over the initial input
for (NSDictionary *resultItem in result) {
// Search for all the items that are equal to the uniqe
// I would rather include a count instead of repeating values :)
if([uniqueItem isEqual:resultItem]) {
[dataResult addObject:resultItem];
}
}
[newResult addObject:dataResult];
}
NSLog(#"%#", newResult);
Cheers!

iOS: CHCSVParser & NSPredicate?

I'm currently attempting to use CHCSVParser to parse through a CSV file containing over 1500 entries, and 8 rows. I've successfully managed to parse through the file, and what I get is an NSArray of NSArrays of NSStrings.
For example here's what I get:
Loading CSV from: (
(
Last,
First,
Middle,
Nickname,
Gender,
City,
Age,
Email
),
(
Doe,
John,
Awesome,
"JD",
M,
"San Francisco",
"20",
"john#john.doe"
),
How could I sort this into a Person object and filter through it using NSPredicate, like Mattt Thompson does here.
Here's how I initialize the parser:
//Prepare Roster
NSString *pathToFile = [[NSBundle mainBundle] pathForResource:#"myFile" ofType: #"csv"];
NSArray *myFile = [NSArray arrayWithContentsOfCSVFile:pathToFile options:CHCSVParserOptionsSanitizesFields];
NSLog(#"Loading CSV from: %#", myFile);
Here's what Mattt does in the article I linked, which I'd like to do with my code:
NSArray *firstNames = #[ #"Alice", #"Bob", #"Charlie", #"Quentin" ];
NSArray *lastNames = #[ #"Smith", #"Jones", #"Smith", #"Alberts" ];
NSArray *ages = #[ #24, #27, #33, #31 ];
NSMutableArray *people = [NSMutableArray array];
[firstNames enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
Person *person = [[Person alloc] init];
person.firstName = firstNames[idx];
person.lastName = lastNames[idx];
person.age = ages[idx];
[people addObject:person];
}];
First, define a suitable Person class:
#interface Person : NSObject
#property(copy, nonatomic) NSString *firstName;
#property(copy, nonatomic) NSString *lastName;
// ...
#property(nonatomic) int age;
// ...
#end
Then you can read your data into an array of Person objects by enumerating the
myFile array. Inside the block, row is the "sub-array" for a single row:
NSMutableArray *people = [NSMutableArray array];
[myFile enumerateObjectsUsingBlock:^(NSArray *row, NSUInteger idx, BOOL *stop) {
if (row > 0) { // Skip row # 0 (the header)
Person *person = [[Person alloc] init];
person.lastName = row[0];
person.firstName = row[1];
// ...
person.age = [row[6] intValue];
// ...
[people addObject:person];
}
}];
Now you can filter that array as shown in the tutorial:
NSPredicate *smithPredicate = [NSPredicate predicateWithFormat:#"lastName = %#", #"Smith"];
NSArray *filtered = [people filteredArrayUsingPredicate:smithPredicate];

Resources