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).
Related
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
Here is my array :
<__NSCFArray 0x7b6ca390>(
{
name = kumar;
no = 158;
},
{
name = rajdeep;
no = 338;
},
{
name = smitha;
no = 361;
},
{
name = uma;
no = 422;
}
)
This is what I am trying to do
NSInteger selectedIndexpath=[self.arrLoadCell indexOfObject:_txtfield.text];
where
_txtfield.text = #"rajdeep"
and i get some random junk value like 2147483647 stored in selectedIndexPath.
Am i missing something? Or is there anyother way to handle it?
This "junk value" seems to be NSNotFound, which you should check against when using this method. Long story short, your array does not contain the value you are looking for. It does not work, because you are looking for a string but the array contains dictionaries, so you would have too search for e.g. { "name" : "rajdeep", "no" : #338 }.
Alternatively, to make this work with strings only, use NSPredicate for filtering, e.g.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", valueFromTextbox];
NSArray result = [array filteredArrayUsingPredicate: predicate];
If your array is very short you can also make a loop for comparison.
Update to get the index, use the result of the predicate as input, e.g.
NSInteger selectedIndexpath = [self.arrLoadCell indexOfObject:result.firstObject];
indexOfObject: requires the actual object that is stored in the array. You are providing only the value of a key for one of the Dictionaries.
One of the ways you could achieve what you want, is to loop through the array and for each dictionary, test the value.
The "junk value" you got is NSNotFound, which means that the object was not found.
That's because your array contains Dictionaries, not Strings. You cannot compare your String (textfield.text) with the dictionaries that are in your array.
I think what you want is:
int idx = NSNotFound;
for (int i=0; i<self.arrLoadCell.count; i++) {
NSDictionary *dict = [self.arrLoadCell objectAtIndex:i];
if ([[dict objectForKey:#"name"] isEqualToString:_txtfield.text]) {
idx = i;
break;
}
}
NSInteger selectedIndexpath=idx;
NSUInteger indexPath = 0;
for (NSDictionary *dic in arrLoadCell){
if ([[dic objectForKey:#"name"] isEqualToString: _txtfield.text]) {
indexPath=[arrLoadCell indexOfObject:dic];
break;
}
}
NSLog(#"%lu is the indexPATH",(unsigned long)indexPath);
Assuming that your array of dictionaries is arrLoadCell this should work.
This chunk of code is a method that creates an array for use by multiple other classes. Input is an array from a CoreData fetch, of type NSDictionaryResultType.
3 of the fields are strings that I need to break into arrays, thus the componentsSeparatedByString.
The resulting array, _dataProductionArray, works great --- BUT --- this chunk of code takes a FULL 5 SECONDS to process for about 32,000 records.
Any help pointing out glaring mistakes that are causing this slow performance would be greatly appreciated!!
NSMutableArray *dataArray = [NSMutableArray array];
int j = 0;
int maxNumMonths = 0;
for (id obj in _dictionaries) {
if ([_dictionaries[j] [#"month"] length] >0 ) {
// get production values
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
NSArray *monthItems = [_dictionaries[j] [#"month"] componentsSeparatedByString:#","];
NSMutableArray *productionAArray = [NSMutableArray array];
NSMutableArray *productionBArray = [NSMutableArray array];
int monthLoop = 1;
for (NSNumber *month in monthItems) {
if (monthLoop <= MONTHS_OF_PRODUCTION) {
if ([month intValue] == monthLoop) {
[productionAArray addObject:[aItems objectAtIndex:monthLoop-1]];
[productionBArray addObject:[bItems objectAtIndex:monthLoop-1]];
productionCount ++;
if (monthLoop > maxNumMonths)
maxNumMonths = monthLoop;
}
}
monthLoop++;
}
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
[dataArray addObject:arrayItem];
}
j++;
}
_dataProductionArray = [NSArray arrayWithArray:dataArray];
I can see a few optimizations you could do in the loop, but I'm not sure how much these would help (especially if the compiler is doing them anyway). The root problem is that 32k is a lot of iterations.
Do you need all 32k results at once? You could get a dramatic improvement in user experience by doing this work lazily, as the UI demands the transformed record.
This approach would be to make dataProductionArray a mutable dictionary, indexed by an NSNumber index. Then, instead of ...
// replace this
self.dataProductionArray[128];
// with this
[self dataProductionAtIndex:#128];
That new getter method calls the code you wrote lazily, like this ...
- (id)dataProductionAtIndex:(NSNumber *)index {
// replace dataProductionArray with dataProductionDictionary
id result = self.dataProductionDictionary[index];
if (!result) {
result = [self getDataAt:index];
self.dataProductionDictionary[index] = result;
}
return result;
}
Then getDataAt: is a simple refactor of the code you posted, except instead of looping 32k elements, it does the work for just one index that gets passed in....
- (id)getDataAt:(NSNumber *)index {
int j = [index intValue];
// no loop, just skip to iteration j
NSArray *aItems = [_dictionaries[j] [#"prodA"] componentsSeparatedByString:#","];
NSArray *bItems = [_dictionaries[j] [#"prodB"] componentsSeparatedByString:#","];
// and so on, then at the end, don't save arrayItem, just return it
NSDictionary *arrayItem = #{#"name":_dictionaries[j] [#"name"],
#"type":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"type"]],
#"height":[NSString stringWithFormat:#"%#",_dictionaries[j] [#"height"]],
#"aArray":productionAArray,
#"bArray":productionBArray,
};
return arrayItem;
}
PS - A mutable dictionary is a good data structure for lazy evaluation. The next level of sophistication is NSCache, which acts like a mutable dictionary and also manages memory (class ref here).
Your for loop is daft. Just write
for (NSDictionary* dict in _dictionaries)...
and use dict instead of _dictionaries [j]. One method call saved each time.
stringWithFormat: creates a new string each time. Can't you just add the item itself instead of turning it into a string?
Instead of extracting all the items into productionAArray and productionBArray, create an NSIndexSet, fill it in the loop -- or better yet using a block -- and create the arrays in one go.
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;
The goal is to compare two arrays as and check if they contain the same objects (as fast as possible - there are lots of objects in the arrays). The arrays cannot be checked with isEqual: as they are differently sorted.
I already tried the solution posted here (https://stackoverflow.com/a/1138417 - see last code snippet of the post by Peter Hosey). But this doesn't work with differently sorted arrays.
The code I'm using now is the following:
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
BOOL objectFoundInArray2 = NO;
for (id objectInArray2 in array2) {
if ([objectInArray1 isEqual:objectInArray2]) {
objectFoundInArray2 = YES;
break;
}
}
if (!objectFoundInArray2) {
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
This works, but those are two nested fast enumerations. Is there a way to do a faster comparison?
As per your code, you are strict to same number of elements and each object of first array should be there in second array and vice versa.
The fastest way would be to sort both the array and compare them.
Ex:
NSArray *array1=#[#"a",#"b",#"c"];
NSArray *array2=#[#"c",#"b",#"a"];
array1=[array1 sortedArrayUsingSelector:#selector(compare:)];
array2=[array2 sortedArrayUsingSelector:#selector(compare:)];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both have same elements");
}
else{
NSLog(#"both having different elements");
}
How about converting both arrays to sets and comparing them.
NSSet *set1 = [NSSet setWithArray:arr1];
NSSet *set2 = [NSSet setWithArray:arr2];
Compare the two using
if([set1 isEqualToSet:set2]) {
}
Use containsObject: method instead of iterating the whole array.
NSArray *array;
array = [NSArray arrayWithObjects: #"Nicola", #"Margherita", #"Luciano", #"Silvia", nil];
if ([array containsObject: #"Nicola"]) // YES
{
// Do something
}
like this
+ (BOOL)arraysContainSameObjects:(NSArray *)array1 andOtherArray:(NSArray *)array2 {
// quit if array count is different
if ([array1 count] != [array2 count]) return NO;
BOOL bothArraysContainTheSameObjects = YES;
for (id objectInArray1 in array1) {
if (![array2 containsObject:objectInArray1])
{
bothArraysContainTheSameObjects = NO;
break;
}
}
return bothArraysContainTheSameObjects;
}
Tried to get the accepted answer working but it wasn't quite the best fit for my situation.
I found this answer and all credit goes to #joel kravets for the method.
Basically sorting using a comparator enables you to sort using objects more easily - hence the problem I was facing when trying to use the above solution.
NSArray * array1 = [NSArray arrayWithArray:users];
NSArray * array2 = [NSArray arrayWithArray:threadUsers];
id mySort = ^(BUser * user1, BUser * user2){
return [user1.name compare:user2.name];
};
array1 = [array1 sortedArrayUsingComparator:mySort];
array2 = [array2 sortedArrayUsingComparator:mySort];
if ([array1 isEqualToArray:array2]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
}
Previously I had tried to use other answers like those above, using break to go through loops but in the end this answer came out easiest probably due to its speed and also that in the end we have the if statement allowing us to put code depending on if they are the same or different.
Thanks to Anoop for getting me on the right track and Joel for helping me to tighten the efficiency of it
If you want to check whether both arrays contain the same duplicates, just use NSCountedSet. It's like an NSSet, but each object in the set also has a count telling you how often it has been added. So
BOOL same = (array1.count == array2.count);
if (same && array.count > 0)
{
NSCountedSet* set1 = [[NSCountedSet alloc] initWithArray:array1];
NSCountedSet* set2 = [[NSCountedSet alloc] initWithArray:array2];
same = ([set1 isEqual: set2]);
}
No matter how you do it, this will be time consuming, so you might consider if there are special cases that can be handled quicker. Are these arrays usually the same, or almost the same, or is it true 99% of the time that they are different and that 99% of the time a random element of array1 is not in array2? Are the arrays often sorted? In that case, you could check whether there are identical objects in identical positions, and then consider only those objects that are not the same. If one array contains objects a, b, c, d, e and the other contains a, b, x, d, y, then you only need to compare the array [c, e] vs. [x, y].
[docTypes containsObject:#"Object"];
It will works for your req. As early as fast it will return boolean value for it.
This way the complexity is O(N^2), if you follow this approach you can't do it with a lower complexity. While instead you can do it with O(N log(N)) if you sort both arrays and then compare them. This way after having them sorted you will do it using isEqualToArray: in other N operations.
NSArray *filtered = [someArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"someParamter == %#", paramValue]]];
if (filtered.count) {
}
the main plus is you can use it for any kind of objects: custom, system, NSDictionary. for example I need to know is my UINavigationController's stack contains MySearchResultsVC and MyTopMenuItemsVC or not:
NSArray *filtered = [self.navigationController.viewControllers filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"class IN %#",
[NSArray arrayWithObjects:
[MySearchResultsVC class],
[MyTopMenuItemsVC class],
nil]]];
if (filtered) {
/* ok, now we can handle it! */
}
I know it's late but i just wanna share what i did..
NSString *stringArr1 = [NSString stringWithFormat:#"%#", array1];
NSString *stringArr2 = [NSString stringWithFormat:#"%#", array2];
if ([stringArr1 isEqual: stringArr2])
NSLog(#"identical");
else
NSLog(#"not");
this is just like comparing "#[#1,#2,#3,#4]" == "[#3,#2,#1,#4]".. which is obviously false..
i guess this will do:
[array1 isEqualToArray:array2];
returns bool;