I am getting a stream from a live feed. It looks something like this as a mutableArray:
"Columbia Heights",
E04,
"Cleveland Park",
A05
The first line is a name and the second is a station code.
I need to turn this into a dictionary that is Plist compliant.
So the result in mind would look like this:
name:
"Columbia Heights",
code:
E04,
name:
"Cleveland Park",
code:
A05
What I've written so far is this:
NSMutableDictionary *stationDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"Name",#"name",#"Code",#"code", nil];
int i = 0;
for (i=0; i < counter; i++) {
// get names and codes for each of 3 fave stations
//[stationArray addObject:[mutableFaveArray objectAtIndex:i]];
NSString *tempName = [mutableFaveArray[i] valueForKey:#"name"];
NSString *tempCode = [mutableFaveArray[i] valueForKey:#"code"];
[stationDict setObject:tempName forKey:#"name"];
[stationDict setObject:tempCode forKey:#"code"];
}
I'm no longer getting an error, but I get as output, the same as my input.. I'm also only getting the last record..
"Cleveland Park",
A05
Updated Code:
NSMutableDictionary *stationDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:#"Name",#"name",#"Code",#"code", nil];
int i = 0;
for (i=0; i < mutableFaveArray.count; i++) {
NSString *tempName = [mutableFaveArray[i] valueForKey:#"name"];
NSString *tempCode = [mutableFaveArray[i] valueForKey:#"code"];
[stationDict setObject:tempName forKey:#"name"];
[stationDict setObject:tempCode forKey:#"code"];
[stationArray addObject:stationDict];
}
This results in the last dictionary entry being saved twice..
{
code = A05;
name = "Cleveland Park";
},
{
code = A05;
name = "Cleveland Park";
}
The problem is that you're only creating a single dictionary, stationDict, before the for loop. You then hit the for loop, which changes the values in stationDict, adds stationDict to the stationArray, changes the values in stationDict again, adds that same dictionary to stationArray, and so on. What you end up with is an array that contains the same object repeated mutableFaveArray.count times.
The solution is to either create a new dictionary at the beginning of the body of the for loop, or add a copy of the dictionary to add to the array. So, either this:
for (i=0; i < mutableFaveArray.count; i++) {
stationDict = [NSMutableDictionary dictionary];
//...continue with the rest of your code...
or this:
//...other for loop code precedes this...
[stationArray addObject:[stationDict copy]];
}
Either approach ensures that each thing you add to the array is a dictionary that's distinct from the others you've added. As a refinement, you could probably speed up the code a little by using a non-mutable dictionary. If you have to create a new dictionary each time through the loop, you can create it with the data you want it to contain rather than changing the objects, so you don't necessarily need it to be mutable. Also, you can declare the loop variable inside the for statement, which ensures that it'll go out of scope as soon as the for terminates. Also, you can use Objective-C's notation for dictionaries:
NSMutableArray *stationArray = [NSMutableArray array];
for (int i=0; i < mutableFaveArray.count; i++) {
[stationArray addObject:#{#"name":mutableFaveArray[i][#"name"],
#"code":mutableFaveArray[i][#"code"]}];
}
You can further abbreviate that and maybe speed it up a bit using fast enumeration:
NSMutableArray *stationArray = [NSMutableArray array];
for (NSDictionary *fave in mutableFaveArray) {
[stationArray addObject:#{#"name":fave[#"name"],
#"code":fave[#"code"]}];
}
Or you can let mutableFaveArray do the enumerating for you:
__block NSMutableArray *stationArray = [NSMutableArray array];
[mutableFaveArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop))
{
[stationArray addObject:#{#"name":obj[#"name"],
#"code":obj[#"code"]}];
}];
That last one might be a bit faster, but perhaps a bit harder to read.
Related
I am having some issues trying to wrap my head around why we can not mutate a collection during enumeration... Apparently, if you are doing any sort of fast enumeration, the system should throw an exception if you try to mutate. Below I have three examples where I am mutating during enumeration. One is a simple C-style loop, and the other two use some form of fast-enumeration. I am only getting enumeration exceptions thrown for case 2. Shouldn't I also be getting exceptions thrown for case 1? Why is case 1 valid? Also, people throughout stack overflow say my case 3 is bad practice, but why? It is simple and seems to work. Inconsistency in how the two different fast-enumeration loops are behaving and the general disgust with the C-style loop is screwing with my understanding here. Instead of vague generic rules of thumb, if someone can really break this down to a science this would really help. From a fundamental level I want to know why the exceptions are not consistent here and why case 3 works for me when it apparently "shouldn't" or is "bad practice."
//Case 1:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[array removeObjectAtIndex:idx];
}];
//Case 2:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
for (NSString *string in array) {
[array removeObject:string];
}
//Case 3:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"adgh", nil];
for (int i = 0; i < [array count]; i++) {
[array removeObjectAtIndex:i];
}
We love simple and straightforward code.
I won't talk about case 2, because it throws an exception and you can't mutate the array in it.
I want to start from an example, suppose we want to remove the "whub" item from a mutable array and the content of the array is "phuj, whub, whub, adgh", it has two whub as you see.
Let's start to write the code using c style loop:
for (int i = 0; i < [mArray1 count]; i++) {
NSString *str = mArray1[i] ;
if ([str isEqualToString:#"whub"]) {
[mArray1 removeObjectAtIndex:i] ;
}
}
The code has a bug, after it removes the first "whub", all the indexes of item located after it will decrease by 1, so the index of second whub should be 1 and i is 1 at the moment. In the next loop, i is 2, so it skips the second whub. You can change the code to make it correct.
for (int i = 0; i < [mArray1 count]; i++) {
NSString *str = mArray1[i] ;
if ([str isEqualToString:#"whub"]) {
[mArray1 removeObjectAtIndex:i] ;
--i ;
}
}
It works, but it's not straightforward, we modify the index i and it makes the code complex.
Let's try to write the code using enumerateObjectsUsingBlock method like case 1:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"phuj", #"whub", #"whub", #"adgh", nil] ;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isEqualToString:#"whub"]) {
[array removeObjectAtIndex:idx] ;
}
}];
If you build and run it, you will find the result array still contains one "whub" and this time, we can't modify the index to fix it.
Conclude: You talk much about that you can mutate the array in the loop, the fact you can mutate the array doesn't mean you will get the correct answer. In fact, with c style loop you can do what ever you want, it won't crash, but we say it is bad because we have a better way to go. In practice, we'd like to use simple code to achieve the target, so others can understand easily, ourselves can also benefit from it.
I'd like to use this way, I think it is simple and straightforward.
NSMutableArray *arrayRemove = [NSMutableArray array] ;
for (NSString *str in mArray1) {
if ([str isEqualToString:#"whub"]) {
[arrayRemove addObject:str] ;
}
}
[mArray1 removeObjectsInArray:arrayRemove] ;
I need to have an NSMutableArray with a constant count of 9 where I can make index-specific insertions and deletions. I know that array = [[NSMutableArray alloc] initWithCapacity:9]; will declare an array with a capacity of 9, but when you get the size of the array, it returns 0.
My first attempt at a solution was to declare an array with capacity 9 (see above) and then fill it with NSNull objects. This code crashes with the error
[NSMutableArray insertObjects:atIndexes:]: array argument is not an NSArray'
- (void) setBlankArray: (NSMutableArray*)array {
for (int i = 0; i < 9; i++) {
[array insertObjects:[NSNull null] atIndexes:i];
}
}
-(void) addCurrentTile: (TileView*)aTile {
[currentTurnTilesArray insertObject:aTile atIndex: aTile.getValue-1];
}
-(void) removeCurrentTile: (TileView*)aTile {
[currentTurnTilesArray removeObjectAtIndex: aTile.getValue-1];
}
Is there a better way to accomplish it?
Not sure what you are trying to accomplish or why, but your removeCurrentTile will break it, because it will reduce the size of the array by 1. What you need to do is wrap this array with a class that guards it such that it can never never never have any other number of elements than 9.
Personally, I think what you're trying to do is silly. If you know you will always have exactly 9 slots, then you should start with a normal array, not a mutable array. It is the objects at each index that you want to mutate - not the array itself. For example, if these things were to be strings, then you would make an immutable array of 9 NSMutableString objects:
NSArray* arr = #[
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string],
[NSMutableString string]
];
Now each string can be mutated into another value, but no strings can be added or removed such as to change the length of the array.
Of course that's just an example (using strings, I mean). For maximum flexibility, this would be an NSArray of nine NSMutableDictionary objects. Now every NSMutableDictionary can contain anything, or nothing. But the number of NSMutableDictionaries will always be exactly nine, because the array itself is immutable.
You're looking for insertObject:atIndex:, or more simply addObject:.
[[NSMutableArray alloc] initWithCapacity:9] does not create an array with 9 elements.
It creates an empty array initialized with enough memory to hold 9 objects.
The purpose of this method is to allocate that much memory at once as you declare, so you can add elements to this array and system has not to allocate memory every time. This is only for optimization.
NSMutableArray reference
I just read your question, and I think I understand exactly what you need. Here is the code:
Declare a property:
#property (nonatomic, retain) NSMutableArray *myArray;
Synthesize it:
#synthesize myArray = _myArray;
Overrride its getter like this:
- (NSMutableArray *)myArray
{
if (!_myArray)
{
_myArray = [[NSMutableArray alloc] initWithCapacity:9];
for (int i = 0; i < 9; i++)
{
[self.myArray addObject:[NSNull null]];
}
}
return _myArray;
}
The "setBlankArray" method will simly set the property to nil, and next time you call the getter of the array property it will get automatically initialized with exactly what you need:
- (void)setBlankArray:(NSMutableArray *)array
{
self.myArray = nil;
}
VERY IMPORTANT: Do not write this code:
for (int i = 0; i < 9; i++)
{
[self.myArray addObject:[NSNull null]];
}
in the method just mentioned as this will make the array to contain 18 elements.
Then write the other 2 methods:
// you can also change the parameter from "id" to "TileView *"
- (void)addCurrentTile:(id)sender
{
NSInteger tileIndex = 1; // replace 1 with ((TileView *) sender).getValue - 1
[self.myArray replaceObjectAtIndex:tileIndex
withObject:sender];
}
// you can also change the parameter from "id" to "TileView *"
- (void)removeCurrentTile:(id)sender
{
NSInteger tileIndex = 1; // replace 1 with ((TileView *) sender).getValue - 1
[self.myArray replaceObjectAtIndex:tileIndex
withObject:[NSNull null]];
}
But, DO NOT FORGET to replace "id" with "TileView *", and TO SET the value of tileIndex to "((TileView *) sender).getValue - 1".
Hope this all makes sense, and is helpful for you.
Best regards
I'm writing a for in loop to read a list of names from an NSArray, here is my code.
NSArray *names = [NSArray arrayWithObjects:#"Luke",#"James",#"Fred",#"Harry", nil];
for (NSString *name in names) {
NSLog(#"%#",name);
}
What I am trying to determine is wether there is an easier way to get the current loop number without adding a variable outside the loop like so..
int number = 0;
for (NSString *name in names) {
number++;
NSLog(#"%i - %#",number,name);
}
Is there a built in 'loop number' property that can be accessed during a for loop? - Am I barking up the wrong tree? Should I just use the variable and get over it?
What you've done is reasonable (although I'd put number++ at the end of the loop). Usually when I need a loop index, I avoid fast enumeration and use a more traditional for loop:
for (NSUInteger i=0; i<[names count]; i++) {
NSString *name = names[i];
NSLog(#"%i - %#", i, name);
}
If you need the index, use a standard 3-part for loop, or the number counter with a while loop. No way to magically get the index in the for...in style loop.
This seems quite a bit of a hack. You could either use a normal for loop instead of fast enumeration, or enumerate the objects using a block, or you can also use the indexOfObject: method inside the for loop (but this is really discouraged since it works only if you have only unique objects in the array, and anyways the repeated lookup makes it slower). All in all, try this:
int i;
for (i = 0; i < names.count; i++) {
NSString *name = [names objectAtIndex:i];
// and "i" already stores the index
}
or this:
[names enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// use obj and idx
}];
and this is also possible, but don't do it:
for (NSString *name in names) {
int idx = [names indexOfObject:name];
// etc.
}
I have this array, NSSMutableArray *myarray, which has five objects in it, and I am using a loop like this:
for( className *myObject in myarray)
{
myTextview.text = [NSString stringWithFormat:#"the name is %#", myObject];
}
When I build and run, only the last name shows in my UITextView *myTextview. I logged it, and my loop is working fine -- it's showing all five objects.
The problem seems to be that each time an object is sent to the myTextView, the next object replaces it; is there a way I can hold all of them, so the whole array can be shown?
Each time you pass the loop you are replacing myTextview.text. What you want is to add to the string each time. Try this:
NSMutableString *string = [NSMutableString string];
for( className *myObject in myarray) {
[string appendString:[NSString stringWithFormat:#"the name is %#\n", myObject]];
}
myTextview.text = string;
In Cocoa, if I want to loop through an NSMutableArray and remove multiple objects that fit a certain criteria, what's the best way to do this without restarting the loop each time I remove an object?
Thanks,
Edit: Just to clarify - I was looking for the best way, e.g. something more elegant than manually updating the index I'm at. For example in C++ I can do;
iterator it = someList.begin();
while (it != someList.end())
{
if (shouldRemove(it))
it = someList.erase(it);
}
For clarity I like to make an initial loop where I collect the items to delete. Then I delete them. Here's a sample using Objective-C 2.0 syntax:
NSMutableArray *discardedItems = [NSMutableArray array];
for (SomeObjectClass *item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addObject:item];
}
[originalArrayOfItems removeObjectsInArray:discardedItems];
Then there is no question about whether indices are being updated correctly, or other little bookkeeping details.
Edited to add:
It's been noted in other answers that the inverse formulation should be faster. i.e. If you iterate through the array and compose a new array of objects to keep, instead of objects to discard. That may be true (although what about the memory and processing cost of allocating a new array, and discarding the old one?) but even if it's faster it may not be as big a deal as it would be for a naive implementation, because NSArrays do not behave like "normal" arrays. They talk the talk but they walk a different walk. See a good analysis here:
The inverse formulation may be faster, but I've never needed to care whether it is, because the above formulation has always been fast enough for my needs.
For me the take-home message is to use whatever formulation is clearest to you. Optimize only if necessary. I personally find the above formulation clearest, which is why I use it. But if the inverse formulation is clearer to you, go for it.
One more variation. So you get readability and good performace:
NSMutableIndexSet *discardedItems = [NSMutableIndexSet indexSet];
SomeObjectClass *item;
NSUInteger index = 0;
for (item in originalArrayOfItems) {
if ([item shouldBeDiscarded])
[discardedItems addIndex:index];
index++;
}
[originalArrayOfItems removeObjectsAtIndexes:discardedItems];
This is a very simple problem. You just iterate backwards:
for (NSInteger i = array.count - 1; i >= 0; i--) {
ElementType* element = array[i];
if ([element shouldBeRemoved]) {
[array removeObjectAtIndex:i];
}
}
This is a very common pattern.
Some of the other answers would have poor performance on very large arrays, because methods like removeObject: and removeObjectsInArray: involve doing a linear search of the receiver, which is a waste because you already know where the object is. Also, any call to removeObjectAtIndex: will have to copy values from the index to the end of the array up by one slot at a time.
More efficient would be the following:
NSMutableArray *array = ...
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
if (! shouldRemove(object)) {
[itemsToKeep addObject:object];
}
}
[array setArray:itemsToKeep];
Because we set the capacity of itemsToKeep, we don't waste any time copying values during a resize. We don't modify the array in place, so we are free to use Fast Enumeration. Using setArray: to replace the contents of array with itemsToKeep will be efficient. Depending on your code, you could even replace the last line with:
[array release];
array = [itemsToKeep retain];
So there isn't even a need to copy values, only swap a pointer.
You can use NSpredicate to remove items from your mutable array. This requires no for loops.
For example if you have an NSMutableArray of names, you can create a predicate like this one:
NSPredicate *caseInsensitiveBNames =
[NSPredicate predicateWithFormat:#"SELF beginswith[c] 'b'"];
The following line will leave you with an array that contains only names starting with b.
[namesArray filterUsingPredicate:caseInsensitiveBNames];
If you have trouble creating the predicates you need, use this apple developer link.
I did a performance test using 4 different methods. Each test iterated through all elements in a 100,000 element array, and removed every 5th item. The results did not vary much with/ without optimization. These were done on an iPad 4:
(1) removeObjectAtIndex: -- 271 ms
(2) removeObjectsAtIndexes: -- 1010 ms (because building the index set takes ~700 ms; otherwise this is basically the same as calling removeObjectAtIndex: for each item)
(3) removeObjects: -- 326 ms
(4) make a new array with objects passing the test -- 17 ms
So, creating a new array is by far the fastest. The other methods are all comparable, except that using removeObjectsAtIndexes: will be worse with more items to remove, because of the time needed to build the index set.
Either use loop counting down over indices:
for (NSInteger i = array.count - 1; i >= 0; --i) {
or make a copy with the objects you want to keep.
In particular, do not use a for (id object in array) loop or NSEnumerator.
For iOS 4+ or OS X 10.6+, Apple added passingTest series of APIs in NSMutableArray, like – indexesOfObjectsPassingTest:. A solution with such API would be:
NSIndexSet *indexesToBeRemoved = [someList indexesOfObjectsPassingTest:
^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [self shouldRemove:obj];
}];
[someList removeObjectsAtIndexes:indexesToBeRemoved];
Nowadays you can use reversed block-based enumeration. A simple example code:
NSMutableArray *array = [#[#{#"name": #"a", #"shouldDelete": #(YES)},
#{#"name": #"b", #"shouldDelete": #(NO)},
#{#"name": #"c", #"shouldDelete": #(YES)},
#{#"name": #"d", #"shouldDelete": #(NO)}] mutableCopy];
[array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if([obj[#"shouldDelete"] boolValue])
[array removeObjectAtIndex:idx];
}];
Result:
(
{
name = b;
shouldDelete = 0;
},
{
name = d;
shouldDelete = 0;
}
)
another option with just one line of code:
[array filterUsingPredicate:[NSPredicate predicateWithFormat:#"shouldDelete == NO"]];
In a more declarative way, depending on the criteria matching the items to remove you could use:
[theArray filterUsingPredicate:aPredicate]
#Nathan should be very efficient
Here's the easy and clean way. I like to duplicate my array right in the fast enumeration call:
for (LineItem *item in [NSArray arrayWithArray:self.lineItems])
{
if ([item.toBeRemoved boolValue] == YES)
{
[self.lineItems removeObject:item];
}
}
This way you enumerate through a copy of the array being deleted from, both holding the same objects. An NSArray holds object pointers only so this is totally fine memory/performance wise.
Add the objects you want to remove to a second array and, after the loop, use -removeObjectsInArray:.
this should do it:
NSMutableArray* myArray = ....;
int i;
for(i=0; i<[myArray count]; i++) {
id element = [myArray objectAtIndex:i];
if(element == ...) {
[myArray removeObjectAtIndex:i];
i--;
}
}
hope this helps...
Why don't you add the objects to be removed to another NSMutableArray. When you are finished iterating, you can remove the objects that you have collected.
How about swapping the elements you want to delete with the 'n'th element, 'n-1'th element and so on?
When you're done you resize the array to 'previous size - number of swaps'
If all objects in your array are unique or you want to remove all occurrences of an object when found, you could fast enumerate on an array copy and use [NSMutableArray removeObject:] to remove the object from the original.
NSMutableArray *myArray;
NSArray *myArrayCopy = [NSArray arrayWithArray:myArray];
for (NSObject *anObject in myArrayCopy) {
if (shouldRemove(anObject)) {
[myArray removeObject:anObject];
}
}
benzado's anwser above is what you should do for preformace. In one of my applications removeObjectsInArray took a running time of 1 minute, just adding to a new array took .023 seconds.
I define a category that lets me filter using a block, like this:
#implementation NSMutableArray (Filtering)
- (void)filterUsingTest:(BOOL (^)(id obj, NSUInteger idx))predicate {
NSMutableIndexSet *indexesFailingTest = [[NSMutableIndexSet alloc] init];
NSUInteger index = 0;
for (id object in self) {
if (!predicate(object, index)) {
[indexesFailingTest addIndex:index];
}
++index;
}
[self removeObjectsAtIndexes:indexesFailingTest];
[indexesFailingTest release];
}
#end
which can then be used like this:
[myMutableArray filterUsingTest:^BOOL(id obj, NSUInteger idx) {
return [self doIWantToKeepThisObject:obj atIndex:idx];
}];
A nicer implementation could be to use the category method below on NSMutableArray.
#implementation NSMutableArray(BMCommons)
- (void)removeObjectsWithPredicate:(BOOL (^)(id obj))predicate {
if (predicate != nil) {
NSMutableArray *newArray = [[NSMutableArray alloc] initWithCapacity:self.count];
for (id obj in self) {
BOOL shouldRemove = predicate(obj);
if (!shouldRemove) {
[newArray addObject:obj];
}
}
[self setArray:newArray];
}
}
#end
The predicate block can be implemented to do processing on each object in the array. If the predicate returns true the object is removed.
An example for a date array to remove all dates that lie in the past:
NSMutableArray *dates = ...;
[dates removeObjectsWithPredicate:^BOOL(id obj) {
NSDate *date = (NSDate *)obj;
return [date timeIntervalSinceNow] < 0;
}];
Iterating backwards-ly was my favourite for years , but for a long time I never encountered the case where the 'deepest' ( highest count) object was removed first. Momentarily before the pointer moves on to the next index there ain't anything and it crashes.
Benzado's way is the closest to what i do now but I never realised there would be the stack reshuffle after every remove.
under Xcode 6 this works
NSMutableArray *itemsToKeep = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array)
{
if ( [object isNotEqualTo:#"whatever"]) {
[itemsToKeep addObject:object ];
}
}
array = nil;
array = [[NSMutableArray alloc]initWithArray:itemsToKeep];