Compare array elements with eachother, on duplicates add a certain property together? - iphone

I'm trying to loop through my array and find duplicate objects based on the name properties of the objects in the array. Once a duplicate is found I need to combine these objects, the name and unit values stay the same, I just want to add the quantity values together. At this point I want to remove the two duplicates and add the new object to the array instead.
If its two difficult to remove two objects and add another (might mess with the index?) then the new objects could be added to a filtered array, as long as when a duplicate isnt found that is also added to this array. So the new array will contain all of my previous values, with duplicate values combine on quantity.
I have this so far:
NSMutableSet* existingNames = [NSMutableSet set];
NSMutableArray* filteredArray = [NSMutableArray array];
for (id object in ingredientsArray)
{
if (![existingNames containsObject:[object name]])
{
[existingNames addObject:[object name]];
NSLog(#"%#", #"DUPLICATE FOUND");
//create new object to store the new info in.
ingredient *info = [[ingredient alloc] initWithname:[object name] quantity:[/* How do I add the quanitity values of the two objects that are duplicates here? */] unit:[object unit]];
//add this to filtered array.
[filteredArray addObject:object];
//remove object from array.
[ingredientsArray removeObject:object];
}
}
thanks

You cannot modify an array that you are enumerating. The compiler should complain about this, if not, it should crash at runtime.
I think the logic in your code is bit messed up. The if clause checks if the string is not contained in your duplicates array - so "DUPLICATE FOUND" is certainly not true.
It is bad practice to just iterate through id in this case. It would be better if you could type your objects more strongly. It is also advised to adhere to the conventions such as Capitalized class names.
One trick to reduce the iterations is to just iterate through the unique names. There is a trick with NSSet to accomplish this:
NSArray *names = [ingredientsList valueForKeyPath:#"name"];
NSSet *uniqueNames = [NSSet setWithArray:names];
NSArray *resultArray = [NSMutableArray array];
NSPredicate *nameFilter;
for (NSString *ingredientName in uniqueNames) {
predicate = [NSPredicate predicateWithFormat:#"name = %#", ingredientName];
NSArray *entries = [ingredientsList filteredArrayUsingPredicate:predicate];
Ingredient *ingredient = entries[0];
if (entries.count > 1) {
NSLog(#"Found %d instances of %#.", entries.count, ingredientName);
NSNumber *sum = [entries valueForKeyPath:#"#sum.quantity"];
ingredient.quantity = sum;
}
[resultsArray addObject:ingredient];
}
This assumes that the class Ingredient has at least two properties, name (NSString) and quantity (NSNumber). This also works with plain NSDictionaries.

Related

Sorting Multiple NSMutableArray's

I have 3 MutableArray's Named:
tvShows
tvNetworks
tvdbID
I need to sort them by the name of the tvShows.
But the need to stay linked.
So e.g.:
tvShows = Breaking Bad, House, Community;
tvNetworks = AMC, FOX, NBC;
tvdbID = 81189, 73255, 94571;
Needs To Become:
tvShows = Breaking Bad, Community, House;
tvNetworks = AMC, NBC, FOX;
tvdbID = 81189, 94571, 73255;
How would I do this? It's my first app so sorry if it's a realy easy question.
store them in an array of dictionaries then sort with an NSArray sort function: (below)
NSDictionary * dict1 = #{#"title":#"breaking bad",#"network":#"AMC",#"tvbdID":#(81189)};
NSDictionary * dict2 = #{#"title":#"house",#"network":#"FOX",#"tvbdID":#(73255)};
NSDictionary * dict3 = #{#"title":#"Community",#"network":#"NBC",#"tvbdID":#(94571)};
NSArray * array = #[dict1,dict2,dict3];
NSSortDescriptor * desc = [NSSortDescriptor sortDescriptorWithKey:#"title"ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray * sortedArray = [array sortedArrayUsingDescriptors:#[desc]];
I would personally create a custom NSObject called TVShow, that has properties of showName, network, and tvbdID. This way, you only have one array of each show. Assuming your array is called myShows, you could do something like this:
[allShows sortUsingComparitor:^NSComparisonResult(id a, id b) {
NSString *firstName = [(TVShow*)a showName];
NSString *secondName = [(TVShow*)b showName];
return [firstName compare: secondName];
}];
That is, if you wanted to sort by show name. You can swap network for showName if you wanted to sort by network!
No idea what your end goal is, but you should probably create a TVShow class that has properties (i.e., instance variables) for "title," "network", and "dbid." Then you can instantiate three TVShow objects with their appropriate properties, put them in a mutable array, and use one of the sorting methods on NSMutableArray -- I'd probably choose sortUsingComparator:.
you can't do it with 3 independent arrays but maybe with 1 dictionary where the keys are tv shows and the value is a dictionary with 2 keys: tvNetworks & tvdbIDs
sample:
NSDictionary *data = #{#"Breaking Bad":#{#"tv" : #"AMC", #"tvdb": #(81189)},
#"House":#{#"tv" : #"FOX", #"tvdb": #(73255)},
#"Community":#{#"tv" : #"NBC", #"tvdb": #(94571)}};
NSArray *sortedShows = [data.allKeys sortedArrayUsingSelector:#selector(compare:)];
for (id show in sortedShows) {
NSLog(#"%# = %#", show, data[show]);
}
One of the easiest and most straightforward ways to do this would be to create one array of dictionaries, like this:
NSMutableArray *tvShowInfos = [NSMutableArray array];
for (NSInteger i = 0; i < tvShows.count; i++) {
NSDictionary *info = #{#"show": [tvShows objectAtIndex:i],
#"network": [tvNetworks objectAtIndex:i],
#"id": [tvdbIDs objectAtIndex:i]};
[tvShowInfos addObject:info];
}
You can then sort that array easily:
[tvShowInfos sortUsingDescriptors:#[ [[NSSortDescriptor alloc] initWithKey:#"show" ascending:YES] ]];
If you need an array that contains all networks, sorted by show title, you can then use valueForKey: on the array of dictionaries:
NSArray *networksSortedByShow = [tvShowInfos valueForKey:#"network"];

error in array cleaning method

I am attempting to use this array cleaning method, and there seems to be an error. I can't spot it, I know the array goes in with 3116 items, comes out with 3116 (and I know for a fact there are three duplicates.
Please advice, thanks!
-(NSArray*) removeDuplicates:(NSArray*)inputArray{
NSMutableArray *arrayToClean = [NSMutableArray arrayWithArray:inputArray];
for (int i =0; i<[arrayToClean count]; i++) {
for (int j=(i+1); j < [arrayToClean count]; j++) {
if ([[arrayToClean objectAtIndex:i] isEqual:[arrayToClean
objectAtIndex:j]]) {
[arrayToClean removeObjectAtIndex:j];
j--;
}
}
}
NSArray *arrayToReturn = [NSArray arrayWithArray:arrayToClean];
return arrayToReturn;
}
NSSet will make this a lot easier:
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *unique = [NSSet setWithArray:inputArray];
return [unique allObjects];
}
Please note that a set has no guaranteed order. If you need the objects in the array to be in a specific order then you should sort the resulting array as needed.
It may also be appropriate to use an NSSet instead of the original array, then you don't need to worry about duplicates at all. But this depends on the other needs of your array.
Hey You can use another alternative for this.You can use the NSSet here for this task.
NSSet declares the programmatic interface for static sets of distinct objects
You can use sets as an alternative to arrays when the order of elements isn’t important and performance in testing whether an object is contained in the set is a consideration—while arrays are ordered, testing for membership is slower than with sets.
You Just need To call below method.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSSet *finalData = [NSSet setWithArray:inputArray];
return [finalData allObjects];
}
If really face any problem in above way of cleaning ducplicates then you can try another Alterantive.
-(NSArray *)removeDuplicates:(NSArray *)inputArray {
NSMutableArray *inputArray1=[NSMutableArray arrayWithArray:inputArray];
NSMutableArray *finalARray=[[NSMutableArray alloc]init];
for (id obj in inputArray1)
{
if (![finalARray containsObject:obj])
{
[finalARray addObject: obj];
}
NSLog(#"new array is %#",finalARray);
}
return finalARray;
}
I hope it may help you ...
Here is a helper function I had in a previous project to do the exact same thing
- (NSMutableArray *)removeDuplicates:(NSMutableArray *)sortedArray{
NSMutableSet* valuesAdded = [NSMutableSet set];
NSMutableArray* filteredArray = [[NSMutableArray alloc] init];
NSString* object;
/* Iterate over the array checking if the value is a member of the set. If its not add it
* to the set and to the returning array. If the value is already a member, skip over it.
*/
for (object in sortedArray){
if (![valuesAdded member:object]){
[valuesAdded addObject:object];
[filteredArray addObject:object];
}
}
return filteredArray;
}

NSMutableArray check if object already exists

I am not sure how to go about this. I have an NSMutableArray (addList) which holds all the items to be added to my datasource NSMutableArray.
I now want to check if the object to be added from the addList array already exists in the datasource array. If it does not exist add the item, if exists ignore.
Both the objects have a string variable called iName which i want to compare.
Here is my code snippet
-(void)doneClicked{
for (Item *item in addList){
/*
Here i want to loop through the datasource array
*/
for(Item *existingItem in appDelegate.list){
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
}
else{
[appDelegate insertItem:item];
}
}
}
But i find the item to be added even if it exists.
What am i doing wrong ?
There is a very useful method for this in NSArray i.e. containsObject.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
I found a solution, may not be the most efficient of all, but atleast works
NSMutableArray *add=[[NSMutableArray alloc]init];
for (Item *item in addList){
if ([appDelegate.list containsObject:item])
{}
else
[add addObject:item];
}
Then I iterate over the add array and insert items.
Use NSPredicate.
NSArray *list = [[appDelegate.list copy] autorelease];
for (Item *item in addList) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iName MATCHES %#", item.iName];
NSArray *filteredArray = [list filteredArrayUsingPredicate:predicate];
if ([filteredArray count] > 0) [appDelegate insertItem:item];
}
Did you try indexOfObject:?
-(void)doneClicked{
for (Item *item in addList){
if([appDelegate.list indexOfObject:item] == NSNotFound){
[appDelegate insertItem:item];
}
}
UPDATE: You have a logical mistake, not mistake in code. assume the first array is ['a', 'b', 'c'], and the second is ['a', 'x', 'y', 'z']. When you iterate with 'a' through the second array it won't add 'a' to second array in the first iteration (compare 'a' with 'a') but will add during the second (compare 'a' with 'x'). That is why you should implement isEqual: method (see below) in your 'Item' object and use the code above.
- (BOOL)isEqual:(id)anObject {
if ([anObject isKindOfClass:[Item class]])
return ([self.iName isEqualToString:((Item *)anObject).iName]);
else
return NO;
}
Have a look at NSSet. You can add objects and the object will only be added if the object is unique. You can create a NSSet from an NSArray or vise versa.
You can override isEquals and hash on the object so that it returns a YES / NO based on the comparison of the iName property.
Once you have that you can use...
- (void)removeObjectsInArray:(NSArray *)otherArray
To clean the list before adding all the remaining objects.
NR4TR said correctly but i think one break statement is sufficient
if([existingItem.iName isEqualToString:item.iName]){
// Do not add
break;
}
Convert Lowercase and Trim whitespace and then check..
[string lowercaseString];
and
NSString *trim = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
You compare the addList's first object and appDelegate.list's first object, if they are not equal, you insert the addList's object. The logic is wrong, you should compare one addList's object with every appDelegate.list's object.

Search NSArray for value matching value

I have an NSArray of objects, which has a particular property called name (type NSString).
I have a second NSArray of NSStrings which are names.
I'd like to get an NSArray of all the objects whose .name property matches one of the names in the second NSArray.
How do I go about this, fast and efficiently as this will be required quite often.
Why not just to use predicates to do that for you?:
// For number kind of values:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF = %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
// For string kind of values:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains[cd] %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
// For any object kind of value (yes, you can search objects also):
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF MATCHES %#", value];
NSArray *results = [array_to_search filteredArrayUsingPredicate:predicate];
Here's a simple way:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", nameToFind];
[listOfItems filteredArrayUsingPredicate:predicate];
With your current data structures, you can only do it in O(n^2) time by looping over the first array once for each member of the second array:
NSMutableArray * array = [NSMutableArray array];
for (NSString * name in names) {
for (MyObject * object in objects) {
if ([[myObject name] isEqualToString:name]) {
[array addObject:object];
}
}
}
(Alternate as suggested by Stefan: loop over the objects array and ask the names array if it containsObject: for the name of each object.)
But if this really needs to be faster (really depends on the size of the arrays as much as how often you do it), you can improve this by introducing an NSDictionary that maps the names in the first array to their objects. Then each of those lookups is O(1) and the overall time is O(n). (You'd have to keep this dictionary always in sync with the array of objects, which isn't hard with reasonable accessors. This technique also has the constraint that the same name can't appear on more than one object.)
An alternate way of getting this result (and which doesn't have that last constraint) is to use an NSSet for your second collection, then walk through the objects array calling containsObject: with each one on the set of names. Whether this technique is better depends on whether your two collections are roughly the same size, or if one is much larger than the other.
I like to use this method:
NSIndexSet *indexes = [_items indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return ((MyObject *)obj).name isEqualToString:name];
}];
if (indexes.count != 0) {
//extract your objects from the indexSet, and do what you like...
}
NSMutableArray * foundNames = [NSMutableArray array];
for (MyObject * objectWithName in objectCollection) {
if ([names containsObject:objectWithName.name]) {
[foundNames objectWithName];
}
}
The methods most helpful will be:
filteredArrayUsingPredicate:
and
indexesOfObjectsPassingTest:
The second one uses a code block, not available on iOS before 4.0
Both of these will be more efficient than iterating directly.
There's a good example here:
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxUsing.html
NSMutableArray* solutions = [NSMutableArray array];
for (Object* object in objects){
for (NSString* name in names){
if ([object.name isEqualToString:name]){
[solutions addObject:object];
break; // If this doesnt work remove this
}
}
}
int count=0;
if (range.location!=NSNotFound)
{
[searchindex addObject:[NSString stringWithFormat:#"%d",count]];
}

Restrict Duplicate entry in NSArray

I have an array, which contains some duplicate entries.
Firstly, is there any way to restrict duplicate entries when data getting inserted?
Secondly, if an array already having duplicate values than in some other way, we can retrieve only unique values from that array, I heard about NSSet about this, but I have no idea how to use it.
Don't use an NSSet.
You can only insert elements upon creation and cannot change the elements contained after you have created it.
If you want to add and remove objects on the fly, you can use an NSMutableSet.
Here is a demo of how to use it both NSSet and NSMutableSet, then converting the NSSet back to an NSArray (incase you want to do that):
- (void) NSMutableSetPrintTest
{
NSMutableSet *mutableSet = [[NSMutableSet alloc] init];
NSLog(#"Adding 5 objects (3 are duplicates) to NSMutableSet");
NSString *firstString = #"Hello World";
[mutableSet addObject:firstString];
[mutableSet addObject:#"Hello World"];
[mutableSet addObject:#"Goodbye World"];
[mutableSet addObject:#"Goodbye World"];
[mutableSet addObject:#"Goodbye World"];
NSLog(#"NSMutableSet now contains %d objects:", [mutableSet count]);
int j = 0;
for (NSString *string in mutableSet) {
NSLog(#"%d: %# <%p>", j, string, string);
j++;
}
NSLog(#"Now, if we are done adding and removing things (and only want to check what is in the Set) we should convert to an NSSet for increased performance.");
NSSet *immutableSet = [NSSet setWithSet:mutableSet];
NSLog(#"NSSet now contains %d objects:", [immutableSet count]);
int i = 0;
for (NSString *string in immutableSet) {
NSLog(#"%d: %# <%p>", i, string, string);
i++;
}
[mutableSet release]; mutableSet = nil;
NSLog(#"Now, if we are done with the sets, we can convert them back to an NSArray:");
NSArray *array = [immutableSet allObjects];
NSLog(#"NSArray contains %d objects", [array count]);
int k = 0;
for (NSString *string in array) {
NSLog(#"%d: %# <%p>", k, string, string);
k++;
}
}
NSMutableSet is probably the most logical thing to use.
However, be warned that a set does not maintain order of its elements (since a set, by definition, is unordered).
If that's a problem for you, then you have a couple of options:
duplicate set functionality with an NSMutableArray by invoking containsObject: before every call to addObject: (doable, but potentially slow, since arrays have O(n) search time)
use another object.
If you go with the second option, I would recommend taking a look at the excellent CHDataStructures framework, which has a subclass of NSMutableSet called CHOrderedSet, which is a set that maintains insertion order. (And since it's a subclass, it has the exact same API as an NSMutableSet)
If you've heard about NSSet, did you read the documentation? The API is similar to NSArray and very straightforward. Just like NSArray vs. NSMutableArray, you would use NSMutableSet if you need on the fly membership tests.