How to check the duplicate NSObjects in NSMutableArray - ios

I receive data for an object person in sets of 5. Let's say name,age,gender,email,number. I did following to add the strings to NSobject:
DataObject *data=[DataObject new];
data.name=#"name";
data.age=#"age";
data.email=#"email";
//here i want to check for duplicates
[personArray addObject:data];
However, I want to check if the personArray is having the duplicate NSObjects or not.
I tried this,but it didnt work:
if(![personArray containsObject:data]){
//add data
}
Edit: Actually, this is what I am trying to do:
I am getting the JSON repsonse and I am adding the properties into array. Before I used to get only one property,in that case, I did the following to eliminate the duplicates:
[JSON[#"person"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (![obj[#"email"] isEqual:[NSNull null]] && ![personArray containsObject:obj[#"email"]] ) {
[personArray addObject:obj[#"email"]];
}
}];
Later I got 5 properties for person, so I thought instead of adding them all to the array, I used NSObject class to tie the properties together and add one person to the array.
[JSON[#"person"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (![obj[#"email"] isEqual:[NSNull null]] && ![personArray containsObject:obj[#"email"]] ) { //how to check for the duplicates here?
DataObject *data=[DataObject new];
data.name=#"name";
data.age=#"age";
data.email=#"email";
[personArray addObject:data];
}
}];

If you do this:
DataObject *data = [DataObject new];
You have just created a new instance of data. No other object inside the personArray can be equal to that new instance.
I assume you're actually trying to check to see if there is a data object that contains the same properties as other data objects in the personArray. There's a number of ways you could do this (I like Zaph's answer, it's clean), but for simplicity...
DataObject *data=[DataObject new];
data.name=#"name";
data.age=#"age";
data.email=#"email";
BOOL contains = NO;
for (DataObject *object in personArray) {
if ([object.name isEqualToString:data.name] && [object.age isEqualToString:data.age] && [object.email isEqualToString:data.email]) {
contains = YES;
break;
}
}
if (!contains) {
[personArray addObject:data];
}

You need to implements isEqual for the DataObject class. Then [personArray containsObject:data] should work.
For details see:
Equality by Mattt Thompson.
Implementing Equality and Hashing by Mike Ash

Related

Efficient means to enumerate an array looking for items in another array

Without unintentionally killing performance, does this appear at first glance to be acceptable for perhaps 200 guid strings in one list compared for equality with 100 guid strings from another list to find the matching indexes.
I have a method signature defined like so...
-(NSArray*)getItemsWithGuids:(NSArray*)guids
And I wanted to take that passed in array of guids and use it in conjunction with this array...
NSArray *allPossibleItems; // Has objects with a property named guid.
... to obtain the indexes of the items in allPossibleItems which have the matching guids from guids
My first instinct was to try indexesOfObjectsPassingTest but after putting together the block, I wondered whether the iOS framework already offers something for doing this type of compare more efficiently.
-(NSArray*)getItemsWithGuids:(NSArray*)guids
{
NSIndexSet *guidIndexes = [allPossibleItems indexesOfObjectsPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop)
{
SomeObjWithGuidProperty *someObject = obj;
for (NSString *guid in guids) {
if ([someObject.guid isEqualToString:guid]) {
return YES;
}
}
return NO;
}];
if (guidIndexes) {
// Have more fun here.
}
}
Since you're working with Objective-C (not Swift) check out YoloKit. In your case, you can do something like:
guids.find(^(NSString *guid){
return [someObject.guid isEqualToString:guid];
});
My thought would be to use a set -
-(NSArray*)getItemsWithGuids:(NSArray*)guids inAllObjects:(NSArray *)allObjects
{
NSSet *matchGuids=[NSSet setWithArray:guids];
NSMutableArray *matchingObjects=[NSMutableArray new];
for (SOmeObjectWithGuidProperty *someObject in allObjects) {
if ([matchGuids contains:someObject.guid]) {
[matchingObjects addObject:someObject];
}
}
return [matchingObjects copy];
}
Your code looks like it would have O(n^2) performance, which is bad. I think the solution of converting guids to an NSSet and then using NSSet's containsObject would likely be much more performant. You could rewrite your indexesOfObjectsPassingTest code to use an NSSet and containsObject pretty easily.
If order doesn't matter much, I would suggest to change data structure here. Instead of using NSArray, consider to use NSDictionary with guid as key and someObject as value. In this case, you should use -[NSDictionary objectsForKeys:notFoundMarker:] method to obtain objects.
It will work much faster, than enumeration trough 2 arrays. If the NSDictionary key have a good hash function, accessing an element, setting an element, and removing an element all take constant time. NSString has good hash.
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSArray *objectsAndNulls = [allPossibleItemsDictionary objectsForKeys:guids notFoundMarker:[NSNull null]];
if (objectsAndNulls) {
// Have more fun here.
// You should check that object in objectsAndNulls is not NSNull before using it
}
return objectsAndNulls;
}
UPD Unfortunately, there is no way to pass nil as notFoundMarker. If you can't provide usable notFoundMarker value and don't want to perform additional checks, you can query objects one by one and fill NSMutableArray. In this case you will avoid pass trough array to remove NSNulls:
-(NSArray*)getItemsWithGuids:(NSArray*)guids {
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:guids.count];
for (NSString *guid in guids) {
SomeObjWithGuidProperty *object = allPossibleItemsDictionary[guid];
if (nil != object) {
[objects addObject:object];
}
}
if (nil != objects) {
// Have more fun here.
}
return object;
}

Filter NSArray of objects to have unique property object

I have Manager object, and Account object. Manager object has a property Account.
There is a NSArray of Manager objects, which needs to be unique by Account!
example array:
NSArray *managers = #[Manager1(has Account1), Manager2(has Account1), Manager3(has Account1), Manager4(has Acconut2), Manager5(has Account2)];
I need an NSArray with only Manager1 and Manager4.
What you need is distinctUnionOfObjects
[managers valueForKeyPath:#"#distinctUnionOfObjects.Account"];
Here is the documentation
#distinctUnionOfObjects
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
The following example returns the payee property values for the transactions in transactions with any duplicate values removed:
NSArray *payees = [transactions valueForKeyPath:#"#distinctUnionOfObjects.payee"];
The resulting payees array contains the following strings: Car Loan, General Cable, Animal Hospital, Green Power, Mortgage.
The #unionOfObjects operator is similar, but does not remove duplicate objects.
You will need to override isEqual method inside Manager class (and possibly in Account class as well). After that add Managers to array by first checking, if array already contains Manager with such Account. Check possible solution below:
override Manager isEqual
- (BOOL)isEqual:(id)object {
if (object == self) {
return YES;
} else if (![object isKindOfClass:[Manager class]]) {
return NO;
}
Manager *manager = (Manager *)object;
return [self.account isEqual:manager.account];
}
override Account isEqual (NB! only if it is needed)
- (BOOL)isEqual:(id)object {
if (object == self) {
return YES;
} else if (![object isKindOfClass:[Account class]]) {
return NO;
}
Account *account = (Account *)object;
return [self.someUniqueId isEqual:account.someUniqueId];
}
filter array:
NSMutableArray *filteredManagers = [NSMutableArray array];
[managers enumerateObjectsUsingBlock:^(Manager *manager, NSUInteger idx, BOOL *stop) {
if (![filteredManagers containsObject:manager]) {
[filteredManagers addObject:manager];
}
}];
NSArray *managers = // The 5 Manager objects in your example
NSMutableArray *filteredManagers = [NSMutableArray array];
NSMutableArray *accountsManaged = [NSMutableArray array];
for (Manager *manager in managers) {
if (![accountsManaged containsObject:manager.account]) {
[filteredManagers addObject:manager];
[accountsManaged addObject:manager.account];
}
}
// filteredManagers now contains Manager1 and Manager4
Assuming you have a separate array uniquely (!) containing the Accounts and further assuming your classes are key value compliant, I'd enumerate through that accounts array and find all managers which manage each account. Then pick the first manager and add it to a new array, like so:
NSMutableArray *filteredManagers = [NSMutableArray array];
for (Account *account in accounts) {
NSArray *managersForAccount = [managers filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"account == %#", account];
[filteredManagers addObject:[managersForAccount firstObject]];
}
// do something with filteredManagers

Check if NSArray contains object with specific property

I have an array of UIView. I want to check if that array contains UIView with specific tag. If it does then I should get that view or else I should receive nil.
As of now I using following
// validCells is an array UIView
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *ignored){
return ((UIView *)obj).tag == i;
}];
UIView *cell = [[validCells filteredArrayUsingPredicate:p] lastObject];
This works fine but complexity is n^2. I was wondering if there is any other better way to do it.
Thanks.
I don't think the complexity of your method is O(n^2), it is more probably like O(n).
But there is no reason to create a temporary array if you just search for a specific
element. As #Josh said, you can do a simple enumeration.
If you want to be a bit more fancy, you can write it as
NSUInteger index = [validCells indexOfObjectPassingTest:^BOOL(UIView *view, NSUInteger idx, BOOL *stop) {
return view.tag == idx;
}];
if (index != NSNotFound) {
cell = validCells[index];
}

Check if unique object exists in NSArray

I have an NSMutableArray where objects can be added to. The objects added are SUPDataValue objects containing a bunch of SUP data.
In an other view, I take this object array and divide it into an NSMutableArray containing an NSMutableArray for every section in the tableview.
When add another SUPDataValue object to my initial Object array, and I switch back to my table view, I want to re-read all the object from the object array and see if those objects exist in my layered array.
I am using the "objectExists" and it works great... however... if I add the same object twice to my object array, it will always assume it exists in a layer.
This is my code:
- (void)setInitialComponents:(NSMutableArray*)components
{
if (self.componentLayer)
{
for (SUPDataValueList *val in components)
{
BOOL found = NO;
for (NSMutableArray *layer in self.componentLayer)
{
if ([layer containsObject:val])
{
found = YES;
}
}
if (!found)
{
[[self.componentLayer objectAtIndex:0] addObject:val];
}
}
}
else {
self.componentLayer = [NSMutableArray array];
// Add the no-layer layer (section 0)
[self.componentLayer addObject:[NSMutableArray array]];
if (self.addMode)
{
[[self.componentLayer objectAtIndex:0] addObjectsFromArray:components];
}
else {
for (SUPDataValueList * val in components)
{
int layer = [[NSString stringWithFormat:#"%#", [val item:38]] intValue];
if (self.componentLayer.count < layer)
{
[self.componentLayer insertObject:[NSMutableArray array] atIndex:layer-1];
}
[[self.componentLayer objectAtIndex:layer-1] addObject:val];
}
}
}
[self.tableView reloadData];
}
As you might have guessed, my problem is here:
if ([layer containsObject:val])
{
found = YES;
}
I would like to check if an unique object exist in that array (using memory allocation ID or something?)
How do I do that?
The containsObject method will invoke isEqual: on the underlying objects being compared.
Unless you implement isEqual: in the SUPDataValueList object, it will simply do a pointer comparison which is the default behavior of isEqual in NSObject.
You're looking for -[NSArray indexOfObjectIdenticalTo:], which uses the objects' addresses to determine a match.
found = [layer indexOfObjectIdenticalTo:val] != NSNotFound;

Best way to find object in Array

I have array with arrays ex.:
(
(
object,
object
),
(
object,
object,
object,
object
)
)
Each objecthas property .objectID.What is the best way to find object with specific objectID?
Here are two options for you:
Option 1: using nested for loops
CustomObject *searchingObject;
// searching through the first array (which has arrays inside of it)
// Note: this will stop looping if it searched through all the objects or if it found the object it was looking for
for (int i = 0; i < [firstArray count] && searchingObject; i++) {
// accessing the custom objects inside the nested arrays
for (CustomObject *co in firstArray[i]) {
if ([co.objectId == 9235) {
// you found your object
searchingObject = co; // or do whatever you wanted to do.
// kill the inside for-loop the outside one will be killed when it evaluates your 'searchingObject'
break;
}
}
}
Option 2: using blocks:
// you need __block to write to this object inside the block
__block CustomObject *searchingObject;
// enumerating through the first array (containing arrays)
[firstArray enumerateObjectsUsingBlock:^(NSArray *nestedArray, NSUInteger indx, BOOL *firstStop) {
// enumerating through the nested array
[nestedArray enumerateObjectsUsingBlock:^(CustomObject *co, NSUInteger nestedIndx, BOOL *secondStop) {
if ([co.objectId == 28935) {
searchingObject = co; // or do whatever you wanted to do.
// you found your object now kill both the blocks
*firstStop = *secondStop = YES;
}
}];
}];
Although still considered N^2 execution time these will only run as far as they need to. Once they find the object they cease searching.
try it with
[ary filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"objectID == %#", objectID]];
--
id object = nil;
NSPredicate *pred = [NSPredicate predicateWithFormat:#"objectID == %#", objectID];
for(NSArray *subAry in ary)
{
NSArray *result = [subAry filteredArrayUsingPredicate:pred];
if(result && result.count > 0)
{
object = [result objectAtIndex:0];
break;
}
}
for Zombies cares everyone himself :P
If you aren't concerned with order, you could instead use an array of dictionaries where the objectId is the key. That makes your search O(N).

Resources