How to modify string element of a NSArray using fast enumeration? [duplicate] - ios

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";
}
}

Related

Check if NSMutableArray contains an int

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.

Is it possible to delete dictionary from mutablearray while iterating? [duplicate]

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.

Add array to second column of another array

I'm new at Objective C. I have a list of questions, and each question has multiple answers, so I need to have an array which contains another array with a question number, then an array of answers.
I have an NSMutableArray which I'm instantiating it with this line:
_randomQuestionNumberArray = [[NSMutableArray alloc] initWithCapacity:2];
I fill this array with numbers:
for (int i = 0; i < numberOfQuestions; i++) {
NSNumber* xWrapped = [NSNumber numberWithInt:i];
[_randomQuestionNumberArray addObject:xWrapped];
}
Then I have another NSMutableArray which is just a regular array of numbers. I want to add this array to the second column of _randomQuestionNumberArray at a specific row. I'm using this code for this but it doesn't seem to work properly.
[_randomQuestionNumberArray insertObject:_tempAnswers atIndex:position];
Can anyone offer solution to this? Thank you!
Don't use an array of arrays. It will work initially, but it isn't flexible or clear (so hard to maintain).
Instead, use an array of dictionaries, where each dictionary has a number of keys:
_randomQuestionNumberArray = [NSMutableArray array];
for (int i = 0; i < numberOfQuestions; i++) {
[_randomQuestionNumberArray addObject:[#{ #"questionNumber" : #(i) } mutableCopy]];
}
then, when you have your answers:
NSMutableDictionary *questionDict = [_randomQuestionNumberArray objectAtIndex:...];
[questionDict setObject:_tempAnswers forKey:#"answers"];
and now it's obvious what each piece of information is for.
Note: the index of each item in the array could work as the question number if you wanted it to...
Try using this:
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject;
in place of anObject put your array.

How to test if there is an _NSCFConstantString in an NSMutableArray?

Alright, so I have a Popover, containing a tableView, that is populated by an NSMutableArray filled with strings. But there is always one blank/empty string in my NSMutableArray and in turn always an empty cell in my popover table. I've single stepped my project and found that the empty string is a string constant(_NSCFConstantString).
I've tried to get rid of the empty string occurrence by doing a simple empty string test:
[str isEqualToString:#""]
But this doesn't work, I'm assuming because the empty string in my array is of type _NSCFConstantString...?
So what I'm wondering is if there is a way to test if an object is of type _NSCFConstantString, or if you guys have a better way to test if a string is empty...
Here is my full code that pertains to my issue:
NSString *str;
for (int i = 0; i < [self.flattenedDocList count]; i++) {
str = [self.flattenedDocList objectAtIndex:i];
if(![str isKindOfClass:[NSString class]]){
[self.flattenedDocList removeObject: str];
NSLog(#"Just Deleted:%#",str);
}else if([str isEqualToString:#""]){
[self.flattenedDocList removeObject: str];
NSLog(#"Just Deleted:%#",str);
}
}
The first if-statement is a check to get rid of any NSNull objects in my array. Unfortunately this doesn't get rid of the string constants :/
Thank you, any help is greatly appreciated.
Alright, so I made the rookie mistake of modifying an NSMutableArray while enumerating. Also, H2CO3 was right, _NSCFConstantString IS a concrete subclass of NSString, so we can use all NSString methods on them.
Here is a good way to modify an NSMutableArray while enumerating it.
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:self.docList];
self.listForThePopover = [[NSMutableArray alloc] init];
NSString *str;
for (int i = 0; i < [tempArray count]; i++) {
str = [tempArray objectAtIndex:i];
//NSLog(#"~str:%#~",str);
//check if the str is of the NSString class AND if it's NOT empty
if(([str isKindOfClass:[NSString class]]) && (![str isEqualToString:#""])){
//add the string to the list that we want to actually use.
[self.listForThePopover addObject:str];
//NSLog(#"Just Added:%#",str);
}
}
But this does not work, I'm assuming because the empty string in my array is of type _NSCFConstantString...?
Not quite. _NSCFConstantString is a concrete subclass of NSString, so it should work.
Maybe the string is not really and empty string, or it isn't in the array. Check if it's a space (or more of them), by accident. Examine its length property, etc.
By the way, it's a very bad idea to modify a mutable collection while enumerating it, it can lead to logic errors. Maybe that's also part of your current problem in this case.

iOS Searching an array for a string and adding it if missing

I'm trying to go through a mutable array searching for a given string. If the string does not exist in the array, I want to add it ONCE. The issue I'm having right now is that the string gets added a number of times.
Here's the code I'm using
NSMutableArray *array;
array=[self.storedData getNames];
if([array count]!=0){
for (int i=0; i<[array count]; i++) {
MyUser *user=[array objectAtIndex:i];
if(![user.firstName isEqualToString:self.nameField.text]){
[array addObject: object constructor method goes here];
[self.storedData setNames:array];
}
}
}
else{
[array addObject:object constructor method];
[self.storedData setNames:array];
}
Any help would be greatly appreciated.
You're adding new string on each loop iteration when enumerating array which is clearly wrong. When enumerating array just set a flag indicating whether string was found and after the loop ad your string to array if it was not:
NSMutableArray *array = [self.storedData getNames];
BOOL found = NO;
for (MyUser *user in array){
if(![user.firstName isEqualToString:self.nameField.text]){
found = YES;
break;
}
}
if (!found){
[array addObject: object constructor method goes here];
[self.storedData setNames:array];
}

Resources