This question already has answers here:
Deleting objects within a for loop from a NSMutableArray
(2 answers)
Closed 8 years ago.
for (int i = 0; i< [optionDataArr count]; i++) {
NSString *sName = [[optionDataArr objectAtIndex:i] objectForKey:kOptionName];
NSString *sPrice = [[optionDataArr objectAtIndex:i] objectForKey:kOptionExtraPrice];
if (sName.length == 0 && sPrice.length == 0) {
[optionDataArr removeObjectAtIndex:i];
}
}
Suppose optionDataArr contains a dictionary having no values and when above code executes i receive:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
You can remove items when using a plain old for loop, you cannot when using fast enumeration.
Your code is buggy, though. When you delete the nth element, the next element will be (n+2)th. You need to manually decrement the index by one to account for the shifted elements.
Also keep in mind, that in this case you really need to do "real time" bounds checking of the array length in the loop, and not just use a temporary variable holding the length (or you need to decrement that one as well).
Below this line:
[optionDataArr removeObjectAtIndex:i];
add this line:
i--;
So, the code would be:
if (sName.length == 0 && sPrice.length == 0) {
[optionDataArr removeObjectAtIndex:i];
i--;
}
Reason: When you remove an item from an array while you are iterating on it, the indexes get changed. So, that's why you would need to manually decrement index.
Eiko answer is correct, but i wanted to show an other version using fast enumeration. You cannot remove items using fast enumeration, so you have do store the indexes and then remove the corresponding items later :
NSMutableIndexSet * indexesToRemove = [NSMutableIndexSet indexSet];
[optionDataArr enumerateObjectsUsingBlock:^(NSDictionary *dico, NSUInteger idx, BOOL *stop) {
if ([dico count] == 0)
[indexesToRemove addIndex:idx];
}];
[optionDataArr removeObjectsAtIndexes:indexesToRemove];
EDIT :
As Martin R sugested, you can also use indexesOfObjectsPassingTestmethod :
NSIndexSet * indexesToRemove = [optionDataArr indexesOfObjectsPassingTest:^BOOL(NSDictionary *dico, NSUInteger idx, BOOL *stop) {
return ([dico count] == 0);
}];
[optionDataArr removeObjectsAtIndexes:indexesToRemove];
You can certainly use a standard for loop for this, provided you make the modifications that Eiko has already mentioned.
However, the idomatic way to handle this in Objective C is to iterate over a copy of the array:
for (id obj in [optionDataArr copy]) {
// some processing code
if (condition) {
[optionDataArr removeObject:obj]
}
}
While this does require a copy of the array, unless you know for sure that you are dealing with a significant amount of data, I would start with the readable version and optimise to the plain for loop when and if necessary.
Related
I need to do something similar to python's enumerate() function with an NSArray in iOS (I have to build NSIndexPath objects as well as examine the object).
I don't see a built in method for doing something like this (i.e. no NSArray equivalent of NSDictionary's enumerateKeysAndObjectsUsingBlock: method). Which leaves me with two general approaches I can think of.
for (NSUInteger index = 0; index < mySequence.count; index++) {
MyElementType *element = mySequence[index];
//
// code that works with both index and element
//
}
or
NSUInteger index = 0;
for (MyElementType *element in mySequence) {
//
// code that works with both index and element
//
index++;
}
Is there a good reason to prefer on or the other? Or is there a third approach that is better than either of these?
There is following API present in NSArray:
- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))
I am making an app which asks the user a series of questions. The question asked depends on the random int produced. When an int is used, I want to add it to an NSMutableArray, and then check if the array contains a number the next time a random number is chosen. I am currently using the following code to do this:
- (void) selectQuestionNumber {
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([previousQuestions containsObject:[NSNumber numberWithInt:textNum]]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
}
[previousQuestions addObject:[NSNumber numberWithInt:textNum]];
}
However, the code NSLog(#"The same question number appeared!"); is never shown in the console, even when the same question will appear twice.
My code is obviously non-functional, so what code can I use to check if an NSMutable array contains an int?
Original solution (works with Array and Set):
-(void)selectQuestionNumber
{
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"intValue=%i",textNum];
NSArray *filteredArray = [previousQuestions filteredArrayUsingPredicate:predicate];
if ([filteredArray count]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
}
[previousQuestions addObject:[NSNumber numberWithInt:textNum]];
}
Best solution, and better performance, especialy with mutableSet ( According to Duncan C).
-(void)selectQuestionNumber
{
textNum = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([previousQuestions containsObject:[NSNumber numberWithInteger:textNum]]) {
[self selectQuestionNumber];
NSLog(#"The same question number appeared!");
} else {
questionLabel.text = [self nextQuestion];
[self questionTitleChange];
NSLog(#"New question made");
// And add the new number to mutableSet of mutableArray.
[previousQuestions addObject:[NSNumber numberWithInteger:textNum]];
}
}
Your problem is likely something other than detecting membership of NSNumbers in an NSArray. It can take a large number of tries for a set of random numbers to repeat. It is theoretically possible for it to not repeat until every possible value has been generated once. For a large range of legal values it can take quite a while.
I suggest logging the values that you add to the array on each pass, and the new value.
Your code above always adds the new value to the array even it if matched, so your array is going to grow with duplicates. You would be better off only adding the new number to the array if it did not match. you would probably also be better off using an NSMutableSet instead of an array. NSSets contain at most one instance of an object, and their containsObject method is faster than that of NSArray.
Instead of using NSArray, you can use NSMutableIndexSet. This is the same as NSSet, with just NSUIntegers instead of objects. Very useful.
//during init
NSMutableIndexSet *tSet = [[NSMutableIndexSet alloc] init];
//...
//later in the code, in whatever loop you have on new values
NSUInteger newInt = lowerBounds + arc4random() % (upperBounds - lowerBounds);
if ([tSet containsIndex:newInt]){
//value already exists in the set
}
else {
//value does not exist, add it
[tSet addIndex:newInt];
}
NSMutableSet *myNumbers = [NSMutableSet Set]; // or NSMutableArray..
NSNumber *aNumber = [NSNumber numberWithInt:getRandomInt() ]; //let us say it returns 1.
[myNumbers addObject:aNumber];
-(BOOL)succesfullyAddNewUniqueRandomMember{
NSInteger randInt = getRandomInt(); //let us say it returns 1 again..
NSNumber *aSubsequentNumber = [NSNumber numberWithInt:randInt;
for (NSNumber *previousEntry in myNumbers){
if ([previousEntry isEqual:aSubsequentNumber]) return NO;
}
[myNumbers addObject:aSubsequentNumber];
return YES;
}
^ are these objects equal (aNumber, aSubsequentNumber) ? YES
^ are they the same object ? NO, two different NSNumbers made with equal integer..
NSSet will also happily add both, because they are not the same object.
therefore you need to loop through and compare directly to each previous member, the (already contains object) filter of NSSet will not do the trick.
by wrapping this in a -(BOOL) type method we can repeat it with a
while(![self succesfullyAddNewUniqueRandomMember])
In other words, in your code
if ([previousQuestions containsObject:[NSNumber numberWithInt:textNum]])
always returns NO because it is comparing NSNumber objects, not their integerValue.
This question already has answers here:
Avoiding "NSArray was mutated while being enumerated"
(11 answers)
Closed 8 years ago.
Can anyone pls suggest how to string element of an NSArray using fast enumeration. Its very easy to do the same with normal for loop. Below code works fine
_tempArray = [[NSMutableArray alloc] initWithObjects:#"A",#"B",#"C",#"D",#"E",#"F", nil];
NSUInteger i=0;
for (; i<[_tempArray count]; i++) {
if ([[_tempArray objectAtIndex:i] isEqualToString:#"A"]) {
[_tempArray replaceObjectAtIndex:i withObject:#"a"];
}
}
I want to do the same as above code does with fast enumeration. I tried the below code but it gives *****Error: Collection <__NSArrayM: 0x610000043090> was mutated while being enumerated.******, which means it is not possible to modify the collection while enumerating it. Can anyone suggest any way to achieve it.**
NSUInteger j=0;
for (NSString *temp in _tempArray) {
if ([temp isEqualToString:#"A"]) {
[_tempArray replaceObjectAtIndex:j withObject:#"a"];
}
j++;
}
How to modify string element of a NSArray using fast enumeration?
Don't. You are not supposed to modify the array during enumeration. It's not an arbitrary restriction -- doing so may result in logical errors.
If you want to modify/transform an array, then use an explicitly indexed for loop instead.
You can tighten up the code with the array indexing syntax:
for (int i=0; i<_tempArray.count; i++) {
if ([_tempArray[i] isEqualToString:#"A"]) {
_tempArray[i] = #"a";
}
}
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).
For instance, how could I verify if there is an item at the index 3?
The objects in the NSArray are instantiated from the class "Animal."
Well, since NSMutableArray has to hold non-nil objects, as long as the array is big enough, you know there's something at index i:
if ([myArray count] > 3) {
id myObj = [myArray objectAtIndex:3];
...
}
If you needed to check something elsek, like say make sure it didn't have a reference to the NSNull singleton, you could then check
if (myObj != [NSNull null]) ...
Since there can be no 'gaps' in a NSMutableArray's storage, if your index is less than [array count], you can be certain an object is present at that index.
try this code
for(int j = 0; j < [yourArray count]; j++)
{
if(obj isKindOfClass:[Animal class]]) {
return
}
}