How to check if the value in an NSDictionary exists in an array of dictionarys - iphone

The title is a bit confusing...I'll explain
I have an NSMutableArray I am populating with NSMutableDictionary objects. What I am trying to do is before the dictionary object is added to the array, I need to check whether any of the dictionaries contain a value equal to an id that is already set.
Example:
Step 1: A button is clicked setting the id of an object for use in establishing a view.
Step 2: Another button is pressed inside said view to save some of its contents into a dictionary, then add said dictionary to an array. But if the established ID already exists as a value to one of the dictionaries keys, do not insert this dictionary.
Here is some code I have that is currently not working:
-(IBAction)addToFavorites:(id)sender{
NSMutableDictionary *fav = [[NSMutableDictionary alloc] init];
[fav setObject:[NSNumber numberWithInt:anObject.anId] forKey:#"id"];
[fav setObject:#"w" forKey:#"cat"];
if ([dataManager.anArray count]==0) { //Nothing exists, so just add it
[dataManager.anArray addObject:fav];
}else {
for (int i=0; i<[dataManager.anArray count]; i++) {
if (![[[dataManager.anArray objectAtIndex:i] objectForKey:#"id"] isEqualToNumber:[NSNumber numberWithInt:anObject.anId]]) {
[dataManager.anArray addObject:fav];
}
}
}
[fav release];
}

One fairly easy way to do this kind of check is to filter the array using an NSPredicate. If there's no match, the result of filtering will be an empty array. So for example:
NSArray *objs = [dataManager anArray];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %#", [NSNumber numberWithInt:i]];
NSArray *matchingObjs = [objs filteredArrayUsingPredicate:predicate];
if ([matchingObjs count] == 0)
{
NSLog(#"No match");
}

Related

Use existing NSArray object properties to create a new NSArray for sectioned tableView

So I have the kind of classic situation where I want to group my tableView by Month/Year. I have a member of my conference object called beginDateSearchString that I use to put different conference into buckets; my problem is in the next part where I try and fail to use a NSSortDescriptor to sort each bucket by beginDate (which is a date).
I am getting an error related to unsorted not being able to receive sort descriptor type selectors.
Here is the disgusting code:
- (NSArray *)arrayOfDateSortedEvents {
NSMutableArray *sortedArray = [[NSMutableArray alloc] init];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
//place into buckets
for (WSConference *conference in self.arrayOfEvents) {
if (![dictionary objectForKey:[conference beginDateSearchString]]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:conference,nil];
[dictionary setObject:array forKey:[conference beginDateSearchString]];
}
else {
[[dictionary objectForKey:[conference beginDateSearchString]] addObject:conference];
}
}
//sort each bucket by descriptor beginDate
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"beginDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:descriptor];
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
// now, unkey and add dictionary in order
while ([dictionary count] > 0) {
NSString *lowest = nil;
for (NSMutableArray *array in dictionary) {
if (!lowest)
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
else {
if ([(WSConference *)[array objectAtIndex:0] beginDate] < [[dictionary objectForKey:lowest] beginDate])
lowest = [[dictionary allKeysForObject:array] objectAtIndex:0];
}
}
[sortedArray addObject:[dictionary objectForKey:lowest]];
[dictionary removeObjectForKey:lowest];
}
return sortedArray;
}
You want to probably filter the array in addition to sorting. See NSPredicate and the NSArray method -filteredArrayUsingPredicate: Then create an eventsByDateArray of the eventArrays created by the filter. Then in your table view delegate for creating the cells, if everything is ordered properly, the first section would represent the date of the events in the eventArray that is the first object of the eventsByDateArray and the table rows would consist of the events in the eventArray. And so on for each date.
Added
Your fast enumeration is incorrect. You enumerate through the keys of the dictionary. So in your code unsorted equals each of the keys as it enumerates. This is a GREAT lesson to everyone. It does not matter how you 'type' a variable. When Objective-C compiles it turns them all into id. So NSMutableArray *unsorted is not an NSMutableArray unless it is assigned to an NSMutableArray. If you assign unsorted to an NSString it will be an NSString. The fast enumerator for a dictionary works using the keys. So, in this case, unsorted becomes an NSString.
Instead of:
for (NSMutableArray *unsorted in dictionary) {
[unsorted sortUsingDescriptors:sortDescriptors];
}
you should have this:
for (id key in dictionary) {
NSMutableArray *unsorted = [dictionary objectForKey:key];
[unsorted sortUsingDescriptors:sortDescriptors];
}

Check two NSArrays for containing each other's objects (NSManagedObject)

I'm stuck at following problem for quite some time now:
I've got two NSArrays, both containing NSManagedObject subclass-objects.
They're fed by different sources but the objects in them still have the same properties/values.
What I want to do now is check if array A contains objects from array B and vice versa.
Unfortunately NSArray's containsObject-method doesn't seem to work here.
I think it uses id-testing for the equality check on each object, doesn't it?
So, does anybody have a clue, what to try?
I even tried to encapsulate my objects in NSSets, using member: as my comparison-method but this didn't work out as well, especially because "you must not override" isEqual etc. for NSManagedObject subclasses.
Here's a code snippet:
//manufacturers is an array, parsed out of some xml here...
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
if(![manufacturers containsObject:manu])
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
if(![[fetchedResultsController fetchedObjects] containsObject:manu])
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
Thanks in advance for any hint ;)
I'm not sure if this works, but you could try to match the dictionaries you get with dictionaryWithValuesForKeys:.
Something like this:
NSArray *keysToCompare = [NSArray arrayWithObjects:#"FooAttribute", #"BarAttribute", nil];
// create an array with the dictionary representation of the managedObject
NSMutableArray *fetchedObjectsDictionaries = [NSMutableArray arrayWithCapacity:[[fetchedResultsController fetchedObjects] count]];
for (NSManagedObject *object in [fetchedResultsController fetchedObjects]) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[fetchedObjectsDictionaries addObject:dictionaryRepresentation];
}
// another array with dictionaries for managedObjects
NSMutableArray *manufacturersDictionaries = [NSMutableArray arrayWithCapacity:[manufacturers count]];
for (NSManagedObject *object in manufacturers) {
NSDictionary *dictionaryRepresentation = [object dictionaryWithValuesForKeys:keysToCompare];
[manufacturersDictionaries addObject:dictionaryRepresentation];
}
// compare those dictionaries
for (NSInteger i = 0; i < [fetchedObjectsDictionaries count]; i++) {
NSDictionary *dictionary = [fetchedObjectsDictionaries objectAtIndex:i];
if (![manufacturersDictionaries containsObject:dictionary]) {
// get the corresponding managedObject
NSManagedObject *object = [[fetchedResultsController fetchedObjects] objectAtIndex:i];
[newArray addObject:object];
}
}
if that won't work you can write your own isEqualToManufacturer: method and enumerate trough the arrays manually.
There would be 3 types of equality you can check for: same memory address, managed object id equality, and value equality. Your current code already checks to see if the objects share the same memory address and this is most likely not what you are interested in. This leaves two possible options. Using the managed object id equality method you can check if the manufacturers point to the same row in the database. Using the value equality you can check if two manufacturers are equal based on the shared values. Below is a way to check for NSManagedObjectID equality.
for(Manufacturer *manu in [fetchedResultsController fetchedObjects])
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
if([manufacturers indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Deleting %#", manu.name);
[self.mContext deleteObject:manu];
}
}
for(Manufacturer *manu in manufacturers)
{
id databaseIDTest = ^(Manufacturer * checkManu, NSUInteger idx, BOOL *stop){
return [[checkManu objectID] isEqual:[manu objectID]];
};
NSArray * fetchedObjects = [fetchedResultsController fetchedObjects];
if([fetchedObjects indexOfObjectPassingTest:databaseIDTest] == NSIndexNotFound)
{
NSLog(#"Adding %#", manu.name);
[newArray addObject:manu];
}
}
You need to override -isEqual: since that's what -[NSArray containsObject:] calls into:
- (BOOL)isEqual:(id)other;
{
if (![other isKindOfClass:[Manufacturer class]]) {
return NO;
}
Manufacturer *otherManufacturer = other;
return ([self.name isEqual:otherManufacturer.name] &&
...
);
}
Checking for containment inside an NSSet is cheaper (and may make sense if you run into performance problems). It only works if you have a relatively decent -hash implementation, but it's easy to implement like this:
- (NSUInteger)hash;
{
return [self.name hash] + [self.foo hash] + ...;
}
Don't go trough too much trouble with the hash, just use 2 - 3 values that are most likely to uniquely identify the object.

How do I find (not remove) duplicates in an NSDictionary of NSArrays?

The title pretty much says it all, but just to clarify: I have an NSMutableDictonary containing several NSMutableArrays. What I would like to do is find any value that is present in multiple arrays (there will not be any duplicates in a single array) and return that value. Can someone please help? Thanks in advance!
Edit: For clarity's sake I will specify some of my variables:
linesMutableDictionary contains a list of Line objects (which are a custom NSObject subclass of mine)
pointsArray is an array inside each Line object and contains the values I am trying to search through.
Basically I am trying to find out which lines share common points (the purpose of my app is geometry based)
- (NSValue*)checkForDupes:(NSMutableDictionary*)dict {
NSMutableArray *derp = [NSMutableArray array];
for (NSString *key in [dict allKeys]) {
Line *temp = (Line*)[dict objectForKey:key];
for (NSValue *val in [temp pointsArray]) {
if ([derp containsObject:val])
return val;
}
[derp addObjectsFromArray:[temp pointsArray]];
}
return nil;
}
this should work
If by duplicates you mean returning YES to isEqual: you could first make an NSSet of all the elements (NSSet cannot, by definition, have duplicates):
NSMutableSet* allElements = [[NSMutableSet alloc] init];
for (NSArray* array in [dictionary allValues]) {
[allElements addObjectsFromArray:array];
}
Now you loop through the elements and check if they are in multiple arrays
NSMutableSet* allDuplicateElements = [[NSMutableSet alloc] init];
for (NSObject* element in allElements) {
NSUInteger count = 0;
for (NSArray* array in [dictionary allValues]) {
if ([array containsObject:element]) count++;
if (count > 1) {
[allDuplicateElements addObject:element];
break;
}
}
}
Then you have your duplicate elements and don't forget to release allElements and allDuplicateElements.

Error spliting NSarray into sections

Okay so I have been working through an example that closely matches what I am trying to achive, the sole difference being that in the example he is directly calling from his database the data he needs to be sectioned etc. Where as I already have a sorted NSArray.
This is the tutorial I am working off - iPhone Development: Creating Native Contacts like screen
I have created a Method that is capturing each entry in the NSArray and putting these results into a alpha based NSDictionary (so their will be a NSDictionary for A,B,C... etc)
here is my method.
//method to sort array and split for use with uitableview Index
- (IBAction)startSortingTheArray:(NSMutableArray *)arrayData
{
//Sort incoming array alphabetically
//sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
[self setSortedArray:[arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]];
arrayOfCharacters = [[NSMutableArray alloc]init];
objectsForCharacters = [[NSMutableDictionary alloc]init];
for(char c='A';c<='Z';c++)
{
if([sortedArray count] >0)
{
[arrayOfCharacters addObject:[NSString stringWithFormat:#"%c",c]];
[objectsForCharacters setObject:sortedArray forKey:[NSString stringWithFormat:#"%c",c]];
NSLog(#"%#", objectsForCharacters);
}
[sortedArray release];
//Reloads data in table
[self.tableView reloadData];
}
}
This is putting every value into every alpha section, I am hoping someone can help me with making it so that only alpha sections are established if there is a value in the array for it.. then only loading those values into each section, not every section.
This piece of code will do just that and will be much more efficient than filtering the array once for each letter.
//Sort incoming array alphabetically so that each sub-array will also be sorted.
NSArray *sortedArray = [arrayData sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
// Dictionary will hold our sub-arrays
NSMutableDictionary *arraysByLetter = [NSMutableDictionary dictionary];
// Iterate over all the values in our sorted array
for (NSString *value in sortedArray) {
// Get the first letter and its associated array from the dictionary.
// If the dictionary does not exist create one and associate it with the letter.
NSString *firstLetter = [value substringWithRange:NSMakeRange(0, 1)];
NSMutableArray *arrayForLetter = [arraysByLetter objectForKey:firstLetter];
if (arrayForLetter == nil) {
arrayForLetter = [NSMutableArray array];
[arraysByLetter setObject:arrayForLetter forKey:firstLetter];
}
// Add the value to the array for this letter
[arrayForLetter addObject:value];
}
// arraysByLetter will contain the result you expect
NSLog(#"Dictionary: %#", arraysByLetter);
Note that arraysByLetter is a dictionary that contains one array per "first letter" that exists in your initial data.
--- Added on 2011-09-23 ---
[sortedArray removeAllObjects];
NSArray *sortedKeys = [arraysByLetter.allKeys sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
for (NSString *key in sortedKeys) {
[sortedArray addObject:key];
[sortedArray addObjectsFromArray: [arraysByLetter objectForKey:key]];
}
NSLog(#"Sorted Array: %#", sortedArray);
The output is the following:
C,
Computer,
H,
Helene,
Hello,
J,
Jules,
W,
World
Looks like you need to filter sortedArray with a predicate for each letter. Something like this...
for(char c='A';c<='Z';c++) {
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"SELF beginswith[c] '%c'", c];
NSArray *objectsBeginningWithCurrentLetter = [array filteredArrayUsingPredicate:predicate];
if([sortedArray count] >0)
{
[arrayOfCharacters addObject:[NSString stringWithFormat:#"%c",c]];
if ([objectsBeginningWithCurrentLetter count] > 0) {
[objectsForCharacters setObject:objectsBeginningWithCurrentLetter forKey:[NSString stringWithFormat:#"%c",c]];
NSLog(#"%#", objectsForCharacters);
}
}
[sortedArray release];
//Reloads data in table
[self.tableView reloadData];
}

How to rename a Key in NSMutableDictionary?

I am having a NSMutableDictionary. I have to dynamically rename any Key in the dictionary to a new value, in my code.. I can't find any built-in API to do this..
How can I do this? Is there any built-in API available to do this?
Thanks everyone..
// assumes that olkdey and newkey won't be the same; they can't as
// constants... but...
[dict setObject: [dict objectForKey: #"oldkey"] forKey: #"newkey"];
[dict removeObjectForKey: #"oldkey"];
Think about what "directly editing an existing key" means. A dictionary is a hash; it hashes the contents of the keys to find a value.
What happens if you were to change the contents of a key? The key would need to be rehashed (and the dictionary's internal structures re-balanced) or the value would no longer be retrievable.
Why do you want to edit the contents of a key in the first place? I.e. what problem does that solve that the above does not?
This should work:
- (void) renameKey:(id<NSCopying>)oldKey toKey:(id<NSCopying>)newKey{
NSObject *object = [dictionary objectForKey:oldKey];
[object retain];
[dictionary removeObjectForKey:oldKey];
[dictionary setObject:object forKey:newKey];
[object release];
}
This does exactly the same as bbum's answer but, if you remove the old key first (like in this example) then you have to retain the object temporarily otherwise it might get deallocated in the way ;)
Conclusion: Unless you need explicitly to remove the old key first do as bbum.
#interface NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey;
#end
#implementation NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey
{
id value = [self objectForKey:oldKey];
if (value) {
[self setObject:value forKey:newKey];
[self removeObjectForKey:oldKey];
}
}
#end
This also handles the case where the dictionary doesn't have a value for the key nicely.
I have to navigate a complete JSON response object that holds fields, sub-dictionaries and sub-arrays. All because one of the JSON fields is called "return" which is an iOS reserved word, so can't be used with the JSONModel Cocoa Pod.
Here's the code:
+ (id) sanitizeJSON:(id) dictIn {
if (dictIn) //check not null
{
// if it's a dictionary item
if ([dictIn isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *dictOut = [dictIn mutableCopy];
// Do the fix replace "return" with "not_return"
if ([dictOut objectForKey: #"return"])
{[dictOut setObject: [dictIn objectForKey: #"return"] forKey: #"not_return"];
[dictOut removeObjectForKey: #"return"];}
// Continue the recursive walk through
NSArray*keys=[dictOut allKeys]; //get all the keys
for (int n=0;n<keys.count;n++)
{
NSString *key = [keys objectAtIndex:n];
//NSLog(#"key=%# value=%#", key, [dictOut objectForKey:key]);
if (([[dictOut objectForKey:key] isKindOfClass:[NSDictionary class]]) || ([[dictOut objectForKey:key] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[dictOut objectForKey:key]];
[dictOut removeObjectForKey: key];
[dictOut setObject:sanitizedObject forKey:key];
// replace returned (poss modified) item with this one
}
}
return dictOut; //return dict
}
else if ([dictIn isKindOfClass:[NSArray class]]) //Or if it's an array item
{
NSMutableArray *tempArray = [dictIn mutableCopy];
// Do the recursive walk across the array
for (int n=0;n< tempArray.count; n++)
{
// if array item is dictionary
if (([[tempArray objectAtIndex:n] isKindOfClass:[NSDictionary class]]) || ([[tempArray objectAtIndex:n] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[tempArray objectAtIndex:n]];
// replace with the possibly modified item
[tempArray replaceObjectAtIndex:n withObject:sanitizedObject];
}
}
return tempArray; //return array
}
return dictIn; //Not nil or dict or array
}
else
return dictIn; //return nil
}