I'm working on a language learning app. So I have an NSMutableDictionary with 'word' as keys. The objects for these keys are nested NSDictionaries with the keys 'frequency' and 'count'. NSNumbers are the objects for 'frequency' and 'count'.
Here is the initializing code:
NSString* path = [[NSBundle mainBundle] pathForResource:#"french_top_50000"
ofType:#"txt"];
NSString *fh = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
self.userWordlist = [[NSMutableDictionary alloc] init];
for (NSString *word in fh) {
NSArray *keyArray = [[NSArray alloc] initWithObjects:#"frequency", #"count", nil];
NSArray *objectArray = [[NSArray alloc] initWithObjects:frequency, count, nil];
NSDictionary *detailsDict = [[NSDictionary alloc] initWithObjects:objectArray forKeys:keyArray];
[self.userWordlist setObject:detailsDict forKey:word];
}
I'm displaying part of this list in a table, and I want to sort by 'frequency', one of the inner keys. I can't figure out how to do this.
In case the first thought is, "Why did you store this in a nested dictionary?", I wanted the words to be keys because in other parts of the app I frequently search to see if a word is in the NSMutableDictionary.
I thought about having a flat dictionary with the following keys:
'word','frequency','count'
... but I'd have to enumerate to check for inclusion of words.
If there are any suggestions for a better data structure strategy I'd love to hear them. I'm going to be checking very frequently for inclusion of 'words' and less frequently will be sorting based on 'frequency' or 'count'.
I've seen lots of question similar to this but they're all for flat dictionaries.
If I understand correctly, use keysSortedByValueUsingComparator: like this:
NSArray *keysByFrequency = [self.userWordlist keysSortedByValueUsingComparator:^NSComparisonResult(NSDictionary* obj1, NSDictionary* obj2) {
return [obj1[#"frequency"] compare:obj2[#"frequency"]];
}];
Then you can iterate keys sorted by their frequency
for (NSString *word in keysByFrequency){
NSDictionary *detailsDict = self.userWordList[word];
// Do whatever...
}
Related
I have a NSDictonary that looks like this. I need to get all the key values that are associated for a particular name. For example the name Samrin is associated with keys 11.titleKey, 110.titleKey and so on. The problem I have is that I am not sure how can I get to the object in an array and then pass they key value back?
I tried the following code with not much success.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"birthdays.plist"];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:stringsPlistPath];
NSArray *temp = [dictionary allKeysForObject:#"Samrin Ateequi"];
NSLog(#"temp: %# ...", temp);
OUTPUT:
temp: (
) ...
I think you can use keysOfEntriesPassingTest for that. Something like:
NSSet *keysSet = [dictionary keysOfEntriesPassingTest:^(id key, id obj, BOOL *stop) {
if ([[obj objectAtIndex:0] isEqualToString:#"Samrin Ateequi"]) {
return YES;
} else {
return NO;
}
}];
allKeysForObject: looks through the dictionary for values equal to that object using isEqual:. Your values for that dictionary are NSArrays, so it will never match the NSString you are looking for.
If you don't change the data structure you will have to loop through everything to get the results you need.
If you are willing to upgrade to Core Data with an SQL store, then your results will be fast and the code will be easier than looping through the dictionary. This is the kind of problem that Core Data was meant to solve. You can get started with the Core Data Programming Guide.
Hope this will help you: I have taken an example.
NSDictionary *dict = #{#"key1":#[#"mania",#"champ"],
#"key2":#[#"mann",#"champ"],
#"key3":#[#"mania",#"champ",#"temp"]};
NSPredicate *filterPredicate = [NSPredicate predicateWithFormat:#"ANY SELF=%#",#"mania"];
NSArray *allValues = [dict allValues];
NSArray *requiredRows = [allValues filteredArrayUsingPredicate:filterPredicate];
NSMutableArray *requiredKeyArray = [[NSMutableArray alloc]initWithCapacity:0];
for (id anObj in requiredRows) {
[requiredKeyArray addObject:[dict allKeysForObject:anObj]];
}
NSLog(#"Desc: %#",[requiredKeyArray description]);
According to NSArray class reference there are 4 type of methods to sort array:
1- sortedArrayUsingComparator:
2- sortedArrayUsingSelector:
3- sortedArrayUsingFunction:context:
4- sortedArrayUsingDescriptors:
For first three methods it mentioned :
The new array contains references to the receiving array’s elements, not copies of them.
But for the forth method (descriptor) it mentioned:
A copy of the receiving array sorted as specified by sortDescriptors.
But following example shows like the other 3 methods, descriptor also retain original array and do not return a new copy of it:
NSString *last = #"lastName";
NSString *first = #"firstName";
NSMutableArray *array = [NSMutableArray array];
NSDictionary *dict;
NSMutableString *FN1= [NSMutableString stringWithFormat:#"Joe"];
NSMutableString *LN1= [NSMutableString stringWithFormat:#"Smith"];
NSMutableString *FN2= [NSMutableString stringWithFormat:#"Robert"];
NSMutableString *LN2= [NSMutableString stringWithFormat:#"Jones"];
dict = [NSDictionary dictionaryWithObjectsAndKeys: FN1, first, LN1, last, nil];
[array addObject:dict];
dict = [NSDictionary dictionaryWithObjectsAndKeys: FN2, first, LN2, last, nil];
[array addObject:dict];
// array[0].first = "Joe" , array[0].last = "Smith"
// array[1].first = "Robert" , array[1].last = "Jones"
NSSortDescriptor *lastDescriptor =[[NSSortDescriptor alloc] initWithKey:last
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSSortDescriptor *firstDescriptor =[[NSSortDescriptor alloc] initWithKey:first
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *descriptors = [NSArray arrayWithObjects:lastDescriptor, firstDescriptor, nil];
NSArray *sortedArray = [array sortedArrayUsingDescriptors:descriptors];
// array[1] == sortedArray[0] == ("Robert" , "Jones")
// comparing array entries whether they are same or not:
NSLog(#" %p , %p " , [array objectAtIndex:1] , [sortedArray objectAtIndex:0] );
// 0x10010c520 , 0x10010c520
it shows objects in both arrays are same,
"A copy of the receiving array sorted as specified by sortDescriptors" means that the array object is copied not the elements in the array. The reason the documentation uses the word "copy" is to make it clear that the returned array is not the same array instance as the receiver.
Elements in an array are never copied in Cocoa with the exception of initWithArray:copyItems:YES which will copy the first level items in the original array to the new array. Even then, this copy is done by calling copyWithZone: on the elements, so caveats apply depending on what elements are in your array.
Note that Cocoa is reference counted, so the concept of "deep copies" is not inherently built in for a reason. This is also (in part) the reason why array objects in cocoa come in two flavors (NSArray and NSMutableArray) and are usually immutable (NSArray) instead of as in other languages where there is not usually a concept of immutable and mutable arrays.
see this SO answer for how to get a "deep copy" of an NSArray.
I have an NSDictionary which contains several arrays with several strings in them. I want to put all strings under each array in one single array. How can I receive all the strings at once? I've tried this:
NSMutableArray *mutarr = [[NSMutableArray alloc] initWithObjects:[self.firstTableView allValues], nil];
NSArray *mySorted = [[NSArray alloc]init];
mySorted = [mutarr sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
NSLog(#"First table view values: %#", mySorted);
NOTE: self.firsttableview is a NSDictionary like this:
NSString *path = [[NSBundle mainBundle] pathForResource:#"firstTableView" ofType:#"plist"];
self.firstTableView = [NSDictionary dictionaryWithContentsOfFile:path];
EFFECT: This gives me a list in NSLog, but it isn't in alphabetic order.
You are initializing your mutarr with one object which is the array returned by allValues on your dictionary. Instead try this:
NSMutableArray* mutarr = [NSMutableArray array];
for (NSArray* array in self.firstTableView.allValues) {
[mutarr addObjectsFromArray:array];
}
Your code is going to mean that mutarr is an array containing an array of arrays, not an array of strings. You should be looping over the array values from the dictionary and adding the items from each one to your mutarr to make it an array of strings that you can sort.
If your NSDictionary values are arrays then you are trying to sort the arrays instead of the strings. Try to create a for loop to iterate [self.firstTableView allValues] then add all values in that array to your main to sort array.
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
Use [NSArray valueForKeyPath:#"#distinctUnionofObjects.room-type"]
to obtain a list of unique room-type values.
Loop through the list of unique room-type values
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?
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];
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;
}
Improving #Jonathan answer
Converting array to dictionary
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.
I have a
NSDictionary* dict = [[NSDictionary alloc]initWithObjectsAndKeys::arrayOne,#"Plants",arrayTwo,#"Animals"),arrayThree,#"Birds",nil];`
self.displayArray =[[dict allKeys] sortedArrayUsingSelector:#selector(compare:)];
Everything works fine, I am able to see all the key value/pair in the table but they are in sorted order. i.e Animals,Birds,Plants.
But I want to display as Plants,Animals,Birds.
Can anyone tell me how to sort the array in my customized order?
I have googled and found that we can use NSSortDescriptor for sorting. But I am not very clear with that. Can anyone help me ?
As your ordering doesnt follow any natural order, a simple solution could be to keep track of the order with another array
NSArray *array1 = [NSArray arrayWithObjects:#"rose",#"orchid",#"sunflower",nil];
NSArray *array2 = [NSArray arrayWithObjects:#"dog", #"cat",#"ogre",#"wookie", nil];
NSArray *array3 = [NSArray arrayWithObjects:#"parrot",#"canary bird",#"tweety",#"bibo",nil];
NSArray *keys = [NSArray arrayWithObjects:#"Plants",#"Animals",#"Birds", nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
array1,[keys objectAtIndex:0],
array2,[keys objectAtIndex:1],
array3,[keys objectAtIndex:2],
nil];
for (NSString *key in keys) {
NSLog(#"%# %#", key, [dict objectForKey:key]);
}
Matt shows in his fantastic blog, how to create a ordered dictionary, that essentially uses another array to keep the order just as I showed here: OrderedDictionary: Subclassing a Cocoa class cluster
You're on the right track, Cyril.
Here is some Apple documentation on "Creating and using Sort Descriptors"
Basically you need to subclass NSSortDescriptor and in your subclass, implement your own "compare:" method (you can actually name it anything you want; it needs to return a "NSComparisonResult") that somehow logically returns "Plants" before "Animals".