Being new to Cocoa, and probably not knowing all of the potential classes available that already have this functionality neatly wrapped in an OO class, here's an algorithm inquiry. What's the best bet to count how many times a particular key occurs in an array of multiple NSDictionary instances?
Essentially my data structure (in this case an NSArray) might contain several NSDictionary instances at any given time, each one having the same keys, potentially different values. Some values repeat. I'd like to be able to know how many times a particular key/value appears. Example:
{
foo => 1,
bar => 2
}
{
foo => 1,
bar => 3
}
{
foo => 2,
bar => 1
}
In this case I'm interested that foo=>1 occured 2 times and foo=>2 occured 1 time. Is building an instance of NSCountedSet the best way to go about this? Perhaps a C linked-list?
You may want to rethink how you are structuring your data. I'd track something like this while adding to the NSArray instead of trying to discover it at a later time. You might create a new class to handle adding and removing the data so that you can keep your own counts of the data.
NSDictionary * dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:1], #"foo",
[NSNumber numberWithInt:2], #"bar", nil];
NSDictionary * dict2 = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:1], #"foo",
[NSNumber numberWithInt:3], #"bar", nil];
NSDictionary * dict3 = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:2], #"foo",
[NSNumber numberWithInt:1], #"bar", nil];
NSArray * arrayOfDictionaries = [[NSArray alloc] initWithObjects:
dict1, dict2, dict3, nil];
// count all keys in an array of dictionaries (arrayOfDictionaries):
NSMutableDictionary * countKeys = [[NSMutableDictionary alloc] initWithCapacity:0];
NSCountedSet * counts = [[NSCountedSet alloc] initWithCapacity:0];
NSArray * keys;
NSString * pairString;
NSString * countKey;
for (NSDictionary * dictionary in arrayOfDictionaries)
{
keys = [dictionary allKeys];
for (NSString * key in keys)
{
pairString = [NSString stringWithFormat:#"%#->%#", key, [dictionary valueForKey:key]];
if ([countKeys valueForKey:pairString] == nil)
{
[countKeys setValue:[NSString stringWithString:pairString] forKey:pairString];
}
countKey = [countKeys valueForKey:pairString];
{ [counts addObject:countKey]; }
}
}
NSLog(#"%#", counts);
[counts release];
[countKeys release];
[arrayOfDictionaries release];
[dict1 release];
[dict2 release];
[dict3 release];
NSCountedSet *keyCounts = [NSCountedSet set];
for (NSDictionary *dict in myDictionaries)
[keyCounts unionSet:[NSSet setWithArray:[dict allKeys]]];
Related
I have two arrays, both full of NSString objects like this:
NSMutableArray *titles = [[NSMutableArray alloc] initWithObjects:#"Title1", #"Title2", #"Title3", #"Title4", #"Title5", nil];
NSMutableArray *distances = [[NSMutableArray alloc] initWithObjects:#"139.45", #"23.78", #"347.82", #"10.29", #"8.29", nil];
How can I sort both arrays by the nearest distance first?
So the results would be like this:
titles = #"Title5", #"Title4", #"Title2", #"Title1", #"Title3"
distances = #"8.29", #"10.29", #"23.78", #"139.45", #"347.82"
I understand that NSSortDescriptor can be used in these circumstances but after looking through the documentation, I am still unsure about how.
I would sort the distances this way...
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSArray *sortedDistances = [listItem sortedArrayUsingComparator: ^(id a, id b) {
NSNumber *aNum = [f numberFromString:a];
NSNumber *bNum = [f numberFromString:b];
return [aNum compare:bNum];
}];
I can't think of a particularly quick way to get the associated titles sorted, but this should work ...
NSMutableArray *sortedTitles = [NSMutableArray array];
NSDictionary *distanceTitle = [NSDictionary dictionaryWithObjects:titles forKeys:distances];
for (NSString *distance in sortedDistances) {
NSString *associatedTitle = [distanceTitle valueForKey:distance];
[sortedTitles addObject:associatedTitle];
}
You can use an NSComparator block and use NSArray's sortedArrayUsingComparator method. On that block, you will receive two objects to compare, and base on the comparison result, you can use NSMutableArray exchangeObjectAtIndex:withObjectAtIndex: method to change the values of titles.
Here is a sample how I sort an array of dictionaries by distance value:
-(void)reorderByDistance {
NSSortDescriptor *sortByName = [NSSortDescriptor sortDescriptorWithKey:#"distance" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortByName];
self.contentArray = [self.contentArray sortedArrayUsingDescriptors:sortDescriptors];
}
And my dictionary looks like this:
NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:#"1", #"id", #"Business #1", #"name", #"This business does some pretty remarkable things", #"description", #"Alley Bar", #"category", #"1.2", #"distance", nil];
One approach would be to create a dictionary mapping titles to distances, sort the distances, and then iterate through the distances to recreate the titles:
NSMutableArray *titles = [[NSMutableArray alloc] initWithObjects:#"Title1", #"Title2", #"Title3", #"Title4", #"Title5", nil];
NSMutableArray *distances = [[NSMutableArray alloc] initWithObjects:#"139.45", #"23.78", #"347.82", #"10.29", #"8.29", nil];
//Create a map of current titles to distances
NSDictionary *titleDistanceMap = [NSDictionary dictionaryWithObjects:titles forKeys:distances];
//Need to sort the strings as numerical values
[distances sortUsingComparator:^(NSString *obj1, NSString *obj2) {
return [obj1 compare:obj2 options:NSNumericSearch];
}];
//Now re-populate the titles array
[titles removeAllObjects];
for (NSString *distance in distances){
[titles addObject:[titleDistanceMap objectForKey:distance]];
}
I have already looked through a few answers using the various sorting methods of NSMutableArray, but for some reason they are not working for me.
I am just trying to sort the mutable array which contains dictionaries by the Delay key within each dictionary. However, the "sorted" array is the exact same as the original array.
By the way, it works fine if I create a dummy mutable array and populate it with dictionaries containing numbers, but for some reason it won't sort this mutable array that I am initializing.
What am I doing wrong?
Here's my code:
playlistCalls = [[NSMutableArray alloc] initWithArray:[currentPlaylist objectForKey:#"Tunes"]];
NSLog(#"original %#", playlistCalls);
NSSortDescriptor *delay = [NSSortDescriptor sortDescriptorWithKey:#"Delay" ascending:YES];
[playlistCalls sortUsingDescriptors:[NSArray arrayWithObject:delay]];
NSLog(#"sorted %#", playlistCalls);
Here's the array containing the dictionaries:
2012-06-04 15:48:09.129 MyApp[57043:f503] original (
{
Name = Test Tune;
Delay = 120;
Volume = 100;
},
{
Name = Testes;
Delay = 180;
Volume = 100;
},
{
Name = Testing;
Delay = 60;
Volume = 100;
}
)
2012-06-04 15:48:09.129 MyApp[57043:f503] sorted (
{
Name = Test Tune;
Delay = 120;
Volume = 100;
},
{
Name = Testes;
Delay = 180;
Volume = 100;
},
{
Name = Testing;
Delay = 60;
Volume = 100;
}
)
The code above is fine when I use NSNumbers in my dictionary. That leads me to believe that the Delay value is stored as strings in your dictionary. What you will need to do then is sort by the strings integerValue.
NSSortDescriptor *delay =
[NSSortDescriptor sortDescriptorWithKey:#"Delay.integerValue" ascending:YES];
Please try the following, it worked with me
NSDictionary *dic;
NSMutableArray *arr = [[NSMutableArray alloc] init];
dic = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:120], #"Delay",
[NSNumber numberWithInt:100], #"Volume",
nil];
[arr addObject:dic];
dic = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:160], #"Delay",
[NSNumber numberWithInt:100], #"Volume",
nil];
[arr addObject:dic];
dic = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:100], #"Delay",
[NSNumber numberWithInt:100], #"Volume",
nil];
[arr addObject:dic];
NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:#"Delay" ascending:YES];
[arr sortUsingDescriptors:[NSArray arrayWithObject:sortDesc]];
So I have three different arrays involved here...
I'm looping through golferThreeIcons (NSMutableArray), and I'm trying to take the highest 4 results (golferThreeIcons contains #'s as strings) and store them into the topFourIconsNum (NSMutableArray) and then I plan to store the id of that highest number in the array topFourIconsId (NSMutableArray).
One tricky thing is that golferThreeIcons start out at 99, but I want that to act like zero. So the number 99 should not get added to either of the arrays...
Example:
golferThreeIcons {2,1,5,3,9}
And after the loop went through I want it to show something like this...
topFourIconsId {0,2,3,4} --- 0 corresponds to 2 in golferThreeIcons --- 2 corresponds to 5 in golfer three icons
topFourIconsNum {2,5,3,9} ----(in any order as long as it corresponds to the top four icons id)
NSMutableArray *topFourIconsId = [NSMutableArray arrayWithObjects: #"0", #"0", #"0", #"0", #"0" ,nil];
NSMutableArray *topFourIconsNum = [NSMutableArray arrayWithObjects: #"0", #"0", #"0", #"0", #"0" ,nil];
int *ids = 0;
for (NSString *s in golferThreeIconCounter) {
if(s != #"0") {
int sint = [s intValue];
int *idn = 0;
for(NSString *n in topFourIconsNum) {
int nint= [n intValue];
if(sint == 99 && nint == 0) {
NSString *idstring = [NSString stringWithFormat:#"%d", ids];
NSString *sintstring = [NSString stringWithFormat:#"%d", sint];
[topFourIconsId replaceObjectAtIndex:idn withObject:idstring];
[topFourIconsNum replaceObjectAtIndex:idn withObject:sintstring];
NSLog(#"IN %#",sintstring);
break;
}
else {
if (sint > nint) {
NSString *idstring = [NSString stringWithFormat:#"%d", ids];
NSString *sintstring = [NSString stringWithFormat:#"%d", sint];
[topFourIconsId replaceObjectAtIndex:idn withObject:idstring];
[topFourIconsNum replaceObjectAtIndex:idn withObject:sintstring];
NSLog(#"IN %#",sintstring);
break;
}
}
idn++;
}
}
ids++;
}
Just a crazy idea
Put your golferThreeIcons in a NSDictionary with the array index as the key and the array value as the value and then sort the keys on their value:
NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:#"2", #"1", #"5", #"3", #"9", nil] forKeys:[NSArray arrayWithObjects:#"0", #"1", #"2", #"3", #"4", nil]];
NSArray *sortedKeys = [dict keysSortedByValueUsingSelector:#selector(caseInsensitiveCompare:)];
NSLog([sortedKeys description]);
I am writing a shopping cart application where I have Item Information sent back from server as a NSDictionary object with item number,description,price as values for their keys. I add all these Dictionary objects to mutable array and display in a table view. To manually increment quantity of each item I have added Quantity key to Item Dictionary object.
NSMutableDictionary* itemDictionary = [[NSMutableDictionary alloc] initWithDictionary:inventory.itemDict];
[itemDictionary setObject:[NSString stringWithFormat:#"1"] forKey:#"Quantity"];
[itemsArray addObject:itemDictionary];
[itemDictionary release];
[self.tableView reloadData];
How to increment value for key Quantity if there is a duplicate entry of the same item ? If I add same item to array I would end up with a duplicate, How to find duplicate item i.e., item that has same price, description and item number while ignoring value of Quantity key of the dictionary when searching for duplicates
I would create a new dict containing two items: the inventory dict and the quantity. I'd add an item to itemsArray like this (untested, so beware of typos):
BOOL found = NO;
for (NSDictionary *dict in itemsArray)
{
if ([[dict objectForKey: #"inventorydict"] isEqual: inventory.itemDict])
{
[dict setObject: [NSNumber numberWithInt:
[[dict objectForKey: #"quantity"] intValue] + 1]
forKey: #"quantity"];
found = YES;
break;
}
}
if (!found)
{
[itemsArray addObject:
[NSMutableDictionary dictionaryWithObjectsAndKeys:
inventory.itemDict, #"inventorydict",
[NSNumber numberWithInt: 1], #"quantity",
nil]];
}
So itemsArray contains NSDictionaries with two keys: "inventorydict" and "quantity". "inventorydict" is the dict passed to you containing the item the user bought, and "quantity" is an NSNumber. When you receive a new product item in the basket, you first check if the item is already in the array. If so, you add one to the "quantity" number, otherwise you create a new dictionary with the inventory item and a quantity of 1.
Store your dictionaries in an NSCountedSet. You can then get the quantity via -countForObject:. Use an array only for presentation purposes, so you can sort the values in a sane way. Rebuild this array whenever the set changes, something like so:
- (void)itemsDidChange
{
NSCountedSet *itemSet = [self itemSet];
NSMutableArray *sortedItems = [[NSMutableArray alloc] init];
for (NSDictionary *item in itemSet) {
NSUInteger count = [itemSet countForObject:item];
NSNumber *countNum = [[NSNumber alloc] initWithUnsignedInteger:count];
NSMutableDictionary *arrayItem = [item mutableCopy];
[arrayItem setObject:countNum forKey:KEY_QUANTITY];
[countNum release];
[sortedItems addObject:arrayItem];
[arrayItem release];
}
[sortedItems sortUsingComparator:/* your comparator here */];
[self setRowItems:sortedItems];
[[self tableView] reloadData];
}
Even simpler, use the object directly in your array without changing it at all. When you present the quantity to the user in the UI, just query the itemSet for the count, and use that. The array is then used solely to impose an order on the set's items.
FYI for setObject: you do not need to use stringWithFormat: if you do not have an object to add to it, you can simply use setObject:#"1".
If you want to increment the Quantity, you should be using setObject:[NSNumber numberWithInt:1] instead.
based on Rudy's post made it working this way:
-(void)addToCart:(id)sender
{
appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
BOOL found = NO;
NSString *itemName = // get your item name here
[theDict setObject:itemName forKey:#"name"];
if ([appDelegate.theCartArray isEqual:nil])
{
[appDelegate.theCartArray addObject:theDict];
}
else //implementing Rudy's idea
{
for (theDict in appDelegate.theCartArray)
{
if ([[theDict objectForKey:#"name"] isEqual:itemName])
{
[theDict setObject: [NSNumber numberWithInt:
[[theDict objectForKey: #"quantity"] intValue] + 1]
forKey: #"quantity"];
found = YES;
break;
}
}
if (!found)
{
[appDelegate.theCartArray addObject:
[NSMutableDictionary dictionaryWithObjectsAndKeys:
itemName, #"name",
[NSNumber numberWithInt: 1], #"quantity",
nil]];
}
}
}
I've got a simple object "post" that has two NSMutableArrays as properties. One is for "image" objects and the other is for "video" objects. At some point in the lifecycle of "post", I ask it for a dictionary representation of itself.
NSMutableDictionary *postDict = [post getDictionary];
-(NSMutableDictionary *)getDictionary{
NSMutableArray *imgDictArry = [NSMutableArray arrayWithObjects:nil];
NSMutableArray *movDictArry = [NSMutableArray arrayWithObjects:nil];
for (int i = 0; i<self.images.count; i++) {
NSMutableDictionary *imgDict = [[self.images objectAtIndex:i] getDictionary];
[imgDictArry addObject:imgDict];
}
for (int i = 0; i<self.videos.count; i++) {
NSMutableDictionary *movDict = [[self.videos objectAtIndex:i] getDictionary];
[movDictArry addObject:movDict];
}
NSMutableDictionary *postDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:self.friendsOnly], #"IsFriendsOnly",
self.message, #"Message",
self.shortText, #"ShortText",
self.authorId, #"AuthorId",
self.recipientId, #"RecipientId",
self.language, #"Language",
self.lat, #"Lat",
self.lng, #"Lng",
imgDictArry, #"Images",
movDictArry, #"Videos",
nil];
return postDict;
}
As you can see, the "image" and "video" objects have their own methods for describing themselves as NSMutableDictionary objects.
-(NSMutableDictionary *)getDictionary{
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
self.nativeURL, #"NativeURL",
self.previewURL, #"PreviewURL",
self.smallURL, #"SmallURL",
self.thumbURL, #"ThumbURL",
self.imageId, #"ImageId",
self.width, #"Width",
self.height, #"Height",
nil];
}
I'm not getting any errors but my imgDictArry and movDictArry objects are turning out to be NULL after I've set them on the postDict object. If I log them to the console just before this moment, I can see the dictionary data. But the other classes requesting this object is getting null for those properties.
Perhaps one of your functions such as self.shortText (or self.lat...) is returning nil, in which case dictionaryWithObjectsAndKeys isn't what you expect it to be: it's truncated to the first function that returns nil...
I changed the postDict creation to this...
NSMutableDictionary *postDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:self.friendsOnly], #"IsFriendsOnly",
self.message, #"Message",
self.shortText, #"ShortText",
self.authorId, #"AuthorId",
self.recipientId, #"RecipientId",
self.language, #"Language",
self.lat, #"Lat",
self.lng, #"Lng",
nil];
[postDict setObject:imgDictArry forKey:#"Images"];
[postDict setObject:movDictArry forKey:#"Videos"];
and it's fixed. But I don't know why.