I am trying to filter out a NSArray of NSDictionaries. With my below example, I want dict1, dict2 & dict4 grouped in one array, dict3 & dict5 grouped in second array and dict6 in third array.
I am getting this data in NSArray, so essentially the "orig" array below is my input and I know that I need to do grouping based on "Name" key.
Instead of looping through the NSArray I though of using valueForKeyPath to return me array based on the key path but this does not work (crashes with logs -[NSMutableArray addObjectsFromArray:]: array argument is not an NSArray').
Any suggestion.
NSDictionary *dict1 = #{#"Name" : #"T1", #"Age" : #"25"};
NSDictionary *dict2 = #{#"Name" : #"T1", #"Age" : #"25"};
NSDictionary *dict3 = #{#"Name" : #"T2", #"Age" : #"27"};
NSDictionary *dict4 = #{#"Name" : #"T1", #"Age" : #"25"};
NSDictionary *dict5 = #{#"Name" : #"T2", #"Age" : #"27"};
NSDictionary *dict6 = #{#"Name" : #"T3", #"Age" : #"28"};
NSArray *orig = #[dict1, dict2, dict3, dict4, dict5, dict6];
NSMutableArray *final = [NSMutableArray array];
final = [orig valueForKeyPath:#"#unionOfArrays.Name"];
NSLog(#"Final = %#", final);
It's a little hard to tell if what you want is three different arrays where each one only contains entries with a specific Name value (as your first paragraph suggests) or if you want a single array where the entries are sorted by Name (as your second paragraph suggests). Regardless,
To sort orig by the value of the Name field:
NSArray *sortedByName = [orig sortedArrayUsingDescriptors:#[[NSSortDescriptor sortDescriptorWithKey:#"Name" ascending:YES]]];
To get a new array by selecting only entries with a specific value for Name:
NSArray *t1Only = [orig filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"Name = %#", #"T1"]];
If the desired output is an array of arrays, you can get there by building a dictionary keyed by the name attribute in the orig dictionaries:
- (NSArray *)collateByName:(NSArray *)original {
NSMutableDictionary *collate = [NSMutableDictionary dictionary];
for (NSDictionary *d in original) {
NSString *newKey = d[#"Name"];
NSMutableArray *newValue = collate[newKey];
if (!newValue) {
newValue = [NSMutableArray array];
collate[newKey] = newValue;
}
[newValue addObject:d];
}
return [collate allValues];
}
It's a little verbose, but clear, I think. If you want to decide the attribute to distinguish with programmatically, pass in another param called attribute and replace the literal #"Name" with it.
Related
This question already has answers here:
sort NSDictionary values by key alphabetical order
(4 answers)
Closed 6 years ago.
I have a dictionary in which, for a single key(for example key "0") there are a key value pair data.The keys are like name, id,p_id. I want to sort the NSMutableDictionary for the values related to the Key "name". The data in the dictionary is as follows,
0 = {
id = 12;
name = "Accounts ";
"p_id" = 13222071;
};
1 = {
id = 13;
name = "consultant";
"p_id" = 15121211;
};
2 = {
id = 11;
name = "Tania";
"p_id" = 10215921;
};
}
Any help is appreciated!
Please try out the below code:
[yourMutableArray sortUsingComparator: (NSComparator)^(NSDictionary *a, NSDictionary *b) {
NSString *key1 = [a objectForKey: #"name"];
NSString *key2 = [b objectForKey: #"name"];
return [key1 compare: key2];
}];
NSLog(#"Sorted Array By name key : %#", yourMutableArray);
Hope this helps!
NSArray *sortedKeys = [dict.allKeys sortedArrayUsingComparator:^NSComparisonResult(NSDictionary *d1, NSDictionary *d2) {
return [d1[#"name"] compare:d2[#"name"]];
}];
NSArray *objects = [dict objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
Dictionaries are not sorted, and doesn't resemble any order. What you should do is to getAll the keys first. Then apply a sort method on the keys, then request the objects according to the ordered keys.
E.g:
NSArray *keys = [dictionary allKeys];
NSArray *sortedKeys = <sort the keys according to your preferred method>
Now you can iterate the Dictionary from the order of the array sortedKeys.
While it has been made abundantly clear that Dictionaries can't be sorted and rightfully so, that does not mean the ends you are aiming for can't be achieved. This code will do that for you:
NSArray *arrayOfDicts = dic.allValues; //Now we got all the values. Each value itself is a dictionary so what we get here is an array of dictionaries
NSSortDescriptor *nameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES]; //Create sort descriptor for key name
NSArray *sortingDesc = [NSArray arrayWithObject:nameDescriptor];
NSArray *sortedArray = [arrayOfDicts sortedArrayUsingDescriptors:sortingDesc]; //Get sorted array based on name
NSMutableDictionary *kindaSortedDict = [[NSMutableDictionary alloc] init];
int keyForDict=0;
for(NSDictionary *valDict in sortedArray)
{
[kindaSortedDict setObject:valDict forKey:[NSString stringWithFormat:#"%i",keyForDict]]; //Set values to our new dic which will be kind of sorted as the keys will be assigned to right objects
keyForDict++;
}
//Now you can simply get sorted array of keys from kindaSortedDic and results for them will always be sorted alphabetically. Alternatively you can just skip all that bother and directly use sortedArray
I have added comments in code to help you understand that.
For accessing sorted values I'd do this:
NSArray *sortedKeys = [kindaSortedDict.allKeys sortedArrayUsingDescriptors:
#[[NSSortDescriptor sortDescriptorWithKey:#"intValue"
ascending:YES]]];
for(NSString *key in sortedKeys)
{
NSDictionary *valDict = [kindaSortedDict objectForKey: key];
NSLog(#"Dict is: %# for key: %#",valDict,key);
}
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.
i've an array with a variable number of nsdictionary.
NSDictionary * name1 = #{#"name" : #"anthony",
#"born" : #(1989),
#"lives" : #(5)};
NSDictionary *name2 = #{#"name" : #"pietro",
#"born" : #(1982),
#"lives" : #(2)};
NSArray *people = #[anthony, pietro];
basically i need to populate the cells of my table with the name inside "name" in nsdictionary.
How can i do that?
Assuming from your code snippet that all your name dictionaries are in the array self.people.
NSString *nameForCell=((NSDictionary *)self.people[indexPath.row])[#"name"];
I have array which has dictionaries. each dictionary is :
NSDictionary *imageAndIndex=[[NSDictionary alloc] initWithObjectsAndKeys:image,[NSNumber numberWithLong:index], nil];
Where the object is image, and the key is NSNumber key made from index.
I want to sort the array according to the NSNumbers indexes so it will become:
0,1,2,3,4 ..
How can i use NSSortDescriptor ?
The problem (and the debate herein) is complicated by two factors: 1) The OP design choice to sort based on a dictionary key, rather than on a value. #sooper in comments pointed out correctly that the better design would be to add a #"sortBy" key, whose value is the NSNumber to be sorted. 2) The second complication is the question's reference to NSSortDescriptor, which is going to depend upon values for a given key, not the key itself.
I think the right answer is to take the #sooper suggestion to add #"sortBy" key-value pairs, but if you must sort the data as is...
- (void)sortDictionaries {
NSDictionary *d0 = #{ #0: someUIImage0};
NSDictionary *d1 = #{ #1: someUIImage1};
NSDictionary *d2 = #{ #": someUIImage2};
NSArray *unsorted = #[d1, d2, d0];
NSArray *sorted = [unsorted sortedArrayUsingComparator:^(NSDictionary *obj1, NSDictionary *obj2) {
NSNumber *key1 = [self numericKeyIn:obj1];
NSNumber *key2 = [self numericKeyIn:obj2];
return [key1 compare:key2];
}];
NSLog(#"%#", sorted);
}
- (NSNumber *)numericKeyIn:(NSDictionary *)d {
// ps. yuck. what do we want to assume here?
// that it's a dictionary?
// that it has only one key value pair?
// that an NSNumber is always one of the keys?
return [d allKeys][0];
}
Not sure why we had to handle this with so much ill-temperment. It's programming, it's supposed to be fun!
Anyway, here's how you'd do it with a sort key and sort descriptor:
- (void)betterSortDictionaries {
NSDictionary *d0 = #{ #"image":image1, #"sortBy":#0 };
NSDictionary *d1 = #{ #"image":image2, #"sortBy":#1 };
NSDictionary *d2 = #{ #"image":image3, #"sortBy":#2 };
NSArray *unsorted = #[d1, d2, d0];
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"sortBy" ascending:YES];
NSArray *sorted = [unsorted sortedArrayUsingDescriptors:#[descriptor]];
NSLog(#"%#", sorted);
}
I have an array of objects that I convert to a NSSet:
NSArray *arr = #[#{ #"someProp": #21, #"unnecessaryProp": #"tada" }, ... ];
NSSet *collection = [NSSet setWithArray:arr];
I would like to project the properties I want (by key) out of each object in the set and end up with a new array like:
NSArray *projectedArray = [collection allObjects]; // #[#{ "someProp": #21 }, ... ], "unnecessaryProp" has been removed
Besides enumeration, is there any other way, perhaps NSPredicate?
NOTE: The objects in the array are subclasses of NSObject, in my example I mentioned a NSDictionary
Since NSPredicate does not do projections, you would end up enumerating the set. I would enumerate it with a block, and project the keys in the individual dictionaries like this:
NSArray *keep= #["someProp"];
NSMutableArray *res = [NSMutableArray array];
[collection enumerateObjectsUsingBlock:^(id dict, BOOL *stop) {
NSArray *values = [dict objectsForKeys:keep notFoundMarker:#""];
[res addObject:[NSDictionary dictionaryWithObjects:values forKeys:keep]];
}];
EDIT : (in response to comments)
I should have mentioned that the objects inside the array are subclasses of NSObject and objectsForKeys is not a method.
Then you could use MartinR's suggestion to build a dictionary using KVC:
NSArray *keep= #["someProp"];
NSMutableArray *res = [NSMutableArray array];
[collection enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
[res addObject:[obj dictionaryWithValuesForKeys:keep]];
}];
If you only need the values for one property of the objects in a collection of type NSSet or NSArray or their subclasses, you can use the KVC method valueForKey:
NSArray *dogs = #[#{#"name" : #"Fido",
#"toys" : #[#"Ball", #"Kong"]},
#{#"name" : #"Rover",
#"toys" : #[#"Ball", #"Rope"]},
#{#"name" : #"Spot",
#"toys" : #[#"Rope", #"Kong"]}];
NSArray *vals = [set valueForKey:#"name"];
NSLog(#"%#", vals);
The above code prints the following on the console:
2014-05-16 09:26:58.293 xctest[17223:303] (
Fido,
Rover,
Spot
)
If you need the values of several properties of the objects in the collection, use dictionaryWithValuesForKeys:. Given the same array as in the previous example, the following code...
NSDictionary *dict = [dogs dictionaryWithValuesForKeys:#[#"name", #"toys"]];
NSLog(#"%#", dict);
produces an array of dictionaries, and logs the following output:
2014-05-16 09:35:34.793 xctest[17275:303] {
name = (
Fido,
Rover,
Spot
);
toys = (
(
Ball,
Kong
),
(
Ball,
Rope
),
(
Rope,
Kong
)
);
}
This works regardless of whether the objects in the target collections are instances of NSDictionary or of custom classes.
you can use indexOfObjectPassingTest on your array or NSSet.
__block NSUInteger maxIdex = [_myArrray count]-1;
__block NSMutableIndexSet* objToRemove = [[NSMutableIndexSet alloc]init];
[_myArrray indexOfObjectPassingTest:^(id object, NSUInteger idx, BOOL * stop){
MyObject *obj = (MyObject*)object;
if(....){
[objToRemove addIndex:[_myArrray indexOfObject:obj]];
}
*stop = (idx == maxIdex);
return *stop;
}];
[_myArrray removeObjectsAtIndexes:objToRemove];