I have the following scenario:
I have two collection classes (could be NSArray, NSMutableArray, NSSet, NSOrderedSet or whatever would be best suited for this case), which hold unique objects of the same type (unique in the sense that for all objects in the collections for no two elements the isEqual method would return true).
Lets say the first collection instance holds the following objects (1,2,3,4,5) and the second one (2,3,4,6,7). Now I need a method that returns the difference between the two collections, with the extra info what exactly the difference from each collection was.
An example result for the example would be: (1,5) was removed from the first collection and (6,7) added two the second collection.
I know if I use the NSMutableArray with a sorted list and decide which list has more elements than the other, I could use removeObjectsInArray to get a list of the different objects (like described in Compare two arrays with the same value but with a different order or in How to compare and remove common objects( NSDictionaries) from 2 NSMutableArray?), but don't really know which objects was in which collection. I could create a temporary collection and put the result of removeObjectsInArray in that array and compare the other two initial arrays with the temporary array. Seems little verbose though. Is there a better way that I don't know of?
I found a much slicker way for you to do what you want by using NSPredicate. When I run the following code:
NSArray *firstArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7", nil];
NSArray *secondArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"8",nil];
NSArray *itemsMissingFromSecondArray = [firstArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT SELF IN %#", secondArray]];
NSArray *itemsMissingFromFirstArray = [secondArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"NOT SELF IN %#", firstArray]];
NSLog(#"itemsMissingFromFirstArray=%#\nitemsMissingFromSecondArray=%#", itemsMissingFromFirstArray, itemsMissingFromSecondArray);
I get the following output showing what was missing from each array that was in the other array:
itemsMissingFromFirstArray=(
8
)
itemsMissingFromSecondArray=(
4,
5,
6,
7
)
Less code than sorting and merging, doesn't use a bunch of temporary arrays, and simple enough to read.
NOTE: If someone also wants to know the items that are in both arrays, the solution is similarly simple:
NSArray *itemsFoundInBothArrays = [firstArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF IN %#", secondArray]];
I think if you want to know the difference, you can make use of NSMutableSet and the minusSet function.
[set1 minusSet:set2];
will give you the elements in set1 but not in set2 straight away. So you don't need any temp collection and compare with original collection again.
Otherwise, if you only want to remove the elements, you can make use of NSArray and do sth like:
[secondArray removeObjectsInArray:firstArray];
Edited:
To find all the diff in one shot:
[ [set1 unionSet:set2] minusSet: [set1 intersectSet:set2] ];
+ (NSArray *) removeObjectsFromArray :(NSArray *)arrayToRemoveFrom thatAreAlsoIn:(NSArray *)arrayOfItemsToRemove
{
NSMutableArray *newArray = [[NSMutableArray alloc] initWithArray:arrayToRemoveFrom];
[newArray removeObjectsInArray:arrayOfItemsToRemove];
return newArray;
}
+(void) findArrayDifferences
{
NSArray *bigArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7", nil];
NSArray *smallArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"8",nil];
NSArray *itemsInBigArrayThatAreNotInSmallArray = [self removeObjectsFromArray:bigArray thatAreAlsoIn:smallArray];
NSArray *itemsThatAreInBothArrays = [self removeObjectsFromArray:bigArray thatAreAlsoIn:itemsInBigArrayThatAreNotInSmallArray];
NSArray *itemsInSmallArrayThatAreNotInBigArray = [self removeObjectsFromArray:smallArray thatAreAlsoIn:itemsThatAreInBothArrays];
NSLog(#"itemsInBigArrayThatAreNotInSmallArray=%#\nitemsThatAreInBothArrays=%#\nitemsInSmallArrayThatAreNotInBigArray=%#", itemsInBigArrayThatAreNotInSmallArray, itemsThatAreInBothArrays, itemsInSmallArrayThatAreNotInBigArray);
}
This results in the following output:
itemsInBigArrayThatAreNotInSmallArray=(
4,
5,
6,
7
)
itemsThatAreInBothArrays=(
1,
2,
3
)
itemsInSmallArrayThatAreNotInBigArray=(
8
)
Related
Consider I have three arrays.
NSArray *array1 = #[#"4",#"3",#"2"];
NSArray *array2 = #[#"2",#"1"];
NSArray *array3 = #[#"3",#"1",#"5",#"2"];
I want to append these arrays. Conditions are:
Order should not change. Means, Array1's order has high priority than Array2.
Duplicate values should be removed (When appending Array2 with Array1, I want to remove duplicate values from Array2)
So I expect the result as like:
#[#"4",#"3",#"2",#"1",#"5"];
Question:
Should I iterate each and every values to construct the expected result? Is there any simple way to achieve it?
Thanks
You can use NSMutableOrderedSet to achieve this:
NSMutableOrderedSet *mSet = [NSMutableOrderedSet new];
[mSet addObjectsFromArray:array1];
[mSet addObjectsFromArray:array2];
[mSet addObjectsFromArray:array3];
NSArray *array = [mSet array];
I have a UITableView and am displaying contents from my NSMutableArray. Following is array format
(
{
Name = "ANS";
VersionNo = 6;
},
{
Name = "O-Hydro";
Version = 6;
},
{
Name = "ANS";
Version = 6;
},
{
Name = "ANTIChorosAnticholinergic";
Version = 6;
}
)
From this I need to display only unique "Name" (like in this I can see 2 "ANS" I need only one).
How can I do this in iOS?
I tried following but its not working
uniqueArray= [[NSMutableSet setWithArray: groupDetails] allObjects];
but in this way I can do only for NSArray not NSMutableArray.
Pls help me
You can use following line of code to convert your NSArray to NSMutableArray,
NSArray *uniqueArray= [[NSMutableSet setWithArray:groupDetails] allObjects];
NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithArray:uniqueArray];
You could simply add mutableCopy.
But wait, before you do it. Arrays and sets have two differences:
Arrays can contain duplicates, sets cannot.
Arrays are ordered, sets are not.
So doing what you are doing, you lose the duplicates (intentionally), but the order, too (probably not intentionally).
I do not know, whether this is important for you, but for other readers it might be. So it is the better approach to do that with NSOrderedSet instead of NSSet:
NSOrderedSet *uniqueList = [NSOrderedSet orderedSetWithArray:array];
In many cases an ordered set is exactly what you want. (Probably it has been from the very beginning and the usage of NSArray was wrong. But sometimes you get an array.) If you really want an array at the end of the day, you can reconvert it:
array = [uniqueList.array mutableCopy];
If you just want an array of unique name values, you can use #distinctUnionOfObjects with valueForKeyPath -
NSArray *uniqueArray=[groupDetails valueForKeyPath:#"#distinctUnionOfObjects.name"];
But if you want the array to contain the dictionaries that correspond to the unique names then you need to do a little more work -
NSMutableArray *uniqueArray=[NSMutableArray new];
NSMutableSet *nameSet=[NSMutableSet new];
for (NSDictionary *dict in groupDetails) {
NSString *name=dict[#"name"];
if (![nameSet containsObject:name]) {
[uniqueArray addObject:dict];
[nameSet addObject:name];
}
}
I have two NSMutableArrays, filled with dictionary objects, like
NSMutableArray * bigArray = #[dictionary1,dictionary2,dictionary3];
NSMutableArray * smallArray = #[dictionary1];
I want to remove the common elements (dictionay1) from the big array
Note: small array is subset of big array and elements are distinct
[bigArray removeObjectsInArray:smallArray];
removes all objects in bigArray that are contained in smallArray.
If each element is distinct (as you say in the comments) then you can filter the array using a predicate that removes all the objects that inside of the smaller subset. Since you are using an array in your question, I assume that the order of the objects matter.
If you need to preserve the order, then you will need to filter the array. Converting to a set will break the order (unless you are able to resort it afterwards.
You are using mutable arrays in your question, so I will do the same. Just be aware that the original array is actually modified:
NSMutableArray *original = [NSMutableArray arrayWithArray:#[#"A", #"E", #"C", #"B", #"D"]];
NSMutableArray *subset = [NSMutableArray arrayWithArray:#[#"C", #"B"]];
NSLog(#"before : %#", original);
[original filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
// return YES for objects to keep
return ![subset containsObject:evaluatedObject];
}]];
NSLog(#"after : %#", original);
The output of this code is:
before : (
A,
E,
C,
B,
D
)
after : (
A,
E,
D
)
You can see that the order is preserved.
If you don't want to modify the original array you can produce a new filtered array instead. The small difference is that you call filteredArrayUsingPredicate: instead of filterUsingPredicate:. The predicate is the same:
NSArray *filtered = [original filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
// return YES for objects to keep
return ![subset containsObject:evaluatedObject];
}]];
NSSet is perfect class to do this kind of job. Try that;
NSMutableSet *bigSet = [NSMutableSet setWithArray:bigArray];
[bigSet minusSet:[NSMutableSet setWithArray:smallArray]];
bigArray = [[bigSet allObjects] mutableCopy];
The thing worth to point is that if your array will store duplicate and you convert it to NSSet the duplicate will be removed in that case you shouldn't use it. The second thing is it can change order of the elements in your array.
This question already has answers here:
Removing duplicates from NSMutableArray
(9 answers)
Closed 10 years ago.
I have an NSMutableArray which has entity class object as its objects.
Now I want to remove the distinct objects from it. Consider following example
Entity *entity = [[Entity alloc] init];
entity.iUserId = 1;
entity.iUserName = #"Giri"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 2;
entity.iUserName = #"Sunil"
[arr addObject:entity];
Entity *entity = [[Entity alloc] init];
entity.iUserId = 3;
entity.iUserName = #"Giri"
[arr addObject:entity];
Now I want only two objects in the Array by removing the duplicate iUserName. I know the way by iteration but I want it without iterating it like predicate or some other way.
If anyone knows then please help me.
I had tried using [arr valueForKeyPath:#"distinctUnionOfObjects.iUsername"]; but it does not return me the entired object.
This question is totally different than the questions which are asked previously. Previously asked question is for getting the distinct objects is correct but they uses looping & I don't want this. I want it from NSPredicate or any other simple option which avoids looping.
EDIT: You can't do what you want to without looping over the array manually and building up a new array. The answer below won't work because it assumes that there are only at most two duplicates.
NSMutableArray *filteredArray = [NSMutableArray array];
for (Entity *entity in arr)
{
BOOL hasDuplicate = [[filteredArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", entity.iUserName]] count] > 0;
if (!hasDuplicate)
{
[filteredArray addObject:entity];
}
}
This will look for duplicates in the filtered array as it builds it.
Begin Original Answer
You can't use an NSSet because the Entity instances would have to return NSOrderedSame in compareTo:, which isn't a good idea since you shouldn't use names as unique identifiers.
You can use predicates, but they'll still loop over the array in an O(n^2) time without some optimization.
NSArray *filteredArray = [arr filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Entity *evaluatedObject, NSDictionary *bindings) {
return [[arr filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"iUserName == %#", evaluatedObject.iUserName]] count] > 1;
}]];
That'll work fine. You could make it even faster by sorting the array by the iUserName property first and doing a linear scan over the sorted array (stopping when you see the first duplicate). That's a lot of work if you're dealing with small sample sizes (say, under ten thousand or so). It's probably not worth your time, so just use the code above.
Well you have a few options (that I can think of).
Use a NSSet instead of a NSArray.
Use a for loop (but you don't want to iterate through the array)
Use a predicate search iUserName to see if the name exists before adding it to the array.
Something like:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"iUserName == 'Giri'"];
NSArray *searchArray = [arr filterUsingPredicate:predicate];
Let's say I have an NSArray of NSDictionaries that is 10 elements long. I want to create a second NSArray with the values for a single key on each dictionary. The best way I can figure to do this is:
NSMutableArray *nameArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
for (NSDictionary *p in array) {
[nameArray addObject:[p objectForKey:#"name"]];
}
self.my_new_array = array;
[array release];
[nameArray release];
}
But in theory, I should be able to get away with not using a mutable array and using a counter in conjunction with [nameArray addObjectAtIndex:count], because the new list should be exactly as long as the old list. Please note that I am NOT trying to filter for a subset of the original array, but make a new array with exactly the same number of elements, just with values dredged up from the some arbitrary attribute of each element in the array.
In python one could solve this problem like this:
new_list = [p['name'] for p in old_list]
or if you were a masochist, like this:
new_list = map(lambda p: p['name'], old_list)
Having to be slightly more explicit in objective-c makes me wonder if there is an accepted common way of handling these situations.
In this particular case Cocoa is not outdone in succinctness :)
NSArray *newArray = [array valueForKey:#"name"];
From the NSArray documentation:
valueForKey:
Returns an array containing the
results of invoking valueForKey: using
key on each of the receiver's objects.