Weird behaviour of NSMutableDictionary - objective-c

Take a look at this piece of code
- (NSMutableDictionary *)getUsersFromServer
{
//here we're getting list of users from the server
NSMutableDictionary * users = [[[NSMutableDictionary alloc] init] autorelease];
userresults = [[NSMutableArray alloc] init];
//all this will be replaced with users taken from the server. it's needed just for testing
for (int i = 0;i < 19;i++)
{
int wins = i ; float f_wins = (float)wins;
int losses = i * 2 ; float f_losses = (float)losses;
int withdr = i * 3 ; float f_withdr = (float)withdr;
float win_per = f_wins / ((f_wins + f_losses + f_withdr) / 100.0);
[userresults setArray:[NSMutableArray arrayWithObjects:[NSNumber numberWithInt:wins],
[NSNumber numberWithInt:losses],
[NSNumber numberWithInt:withdr],
[NSNumber numberWithFloat:win_per],
nil]];
[users setObject:userresults forKey:[NSString stringWithFormat:#"user number %i",i]];
}
[userresults release];
return users;
}
in each loop iteration i fill array with numbers and set it as value into NSMutableDictionary. As a key for each array serves formatted string which is unique by number of iteration counter. So... the problem is - the dictionary is always filled with SAME arrays for DIFFERENT keys. There are 19 arrays in the dictionary and ALL THEY ARE LAST ONES!!!! That is from the last iteration. And each one has different key!!! How could it happen??? What's going on???

userresults points to the same object, and you're modifying that same array with the setArray: method. Create a new array in each loop iteration instead:
NSArray *userData = [NSArray arrayWithObjects:[NSNumber numberWithInt:wins],
[NSNumber numberWithInt:losses],
[NSNumber numberWithInt:withdr],
[NSNumber numberWithFloat:win_per],
nil];
[users setObject:userData forKey:[NSString stringWithFormat:#"user number %i",i]];

userresults is declared outside the loop unnecessarily. You can forego mutable arrays here and just create a new NSArray with each iteration. Your problem stems from reusing the array declared outside the loop and putting new values in each time. Try this:
[users setObject: [NSArray arrayWithObjects:[NSNumber numberWithInt:wins], [NSNumber numberWithInt:losses], [NSNumber numberWithInt:withdr], [NSNumber numberWithFloat:win_per], nil];
[users setObject:userData forKey:[NSString stringWithFormat:#"user number %i",i]]
forKey:[NSString stringWithFormat:#"user number %i",i]];;

Related

Convert NSarray to twodimensional array

I have a an one dimensional array which contains a vary numbers of object (depending on the userinput)
The NSArray is called homePlayersArray. This could example contain 2, 3, 5, 6, 4
The thing is i want to convert this to a two dimensional array where example.
{2,0}, {3,}, {5,0}, {6,0},{4,0}
the first value in the object will me by NSarray (called homepPlayersArray) and the second value will be 0.
What is the best way to obtain this?
//Your original array
NSArray *homePlayersArray = [[NSArray alloc] initWithObjects:
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:5],
[NSNumber numberWithInt:6],
[NSNumber numberWithInt:4],nil];
//For your 2D array
NSMutableArray *secondArray = [[NSMutableArray alloc] initWithCapacity:[homePlayersArray count]];
//populate as required
for(int i=0;i<[homePlayersArray count];i++){
NSArray *tempArray = [[NSArray alloc] initWithObjects:[homePlayersArray objectAtIndex:i],[NSNumber numberWithInt:0], nil];
[secondArray addObject:tempArray];
}
//print out some results to show it worked
NSLog(#"%#%#",#"secondArray first object value 0: ",[[secondArray objectAtIndex:0] objectAtIndex:0] );
NSLog(#"%#%#",#"secondArray first object value 1: ",[[secondArray objectAtIndex:0] objectAtIndex:1] );
NSLog(#"%#%#",#"secondArray second object value 0: ",[[secondArray objectAtIndex:1] objectAtIndex:0] );
NSLog(#"%#%#",#"secondArray second object value 1: ",[[secondArray objectAtIndex:1] objectAtIndex:1] );

Releasing NSMutableArray not affecting the count of elements in the array

Could someone explain to me the following result?
//generate an array with 4 objects
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:4],
nil];
//release the array
[array release];
//get a count of the number of elements in the array
int count = [array count]; <--- count returns 4
Should my count not be zero? Does 'release' not remove all elements from the array?
The value of count is undefined, because after the last release accessing properties of the array is illegal: essentially, you are accessing a dangling pointer.
If you would like to clear out the array without invalidating it, use removeAllObjects method.

NSDictionary With Integer Values

I'm working on a game with monsters. Each one has a list of stats that are all going to be ints. I can set up each stat as it's own variable but I'd prefer to keep them in an NSDictionary since they are all related. I'm running into a problem when I'm trying to change the value's of each stat.
What I Have:
-(id) init {
self = [super init];
if(self) {
stats = [NSDictionary dictionaryWithObjectsAndKeys:
#"Attack", 0,
#"Defense", 0,
#"Special Attack", 0,
#"Special Defense", 0,
#"HP", 0, nil];
}
return self;
}
What I want to do
-(void) levelUp {
self.level++;
[self.stats objectForKey:#"Attack"] += (level * 5);
[self.stats objectForKey:#"Defense"] += (level * 5);
[self.stats objectForKey:#"Special Attack"] += (level * 5);
[self.stats objectForKey:#"Special Defense"] += (level * 5);
[self.stats objectForKey:#"HP"] += (level * 5);
}
Error I'm Getting
Arithmetic on pointer to interface 'id', which is not a constant size in non-fragile ABI
So it seems obvious to me that the reason I'm getting the problem is that I'm getting an object returned from objectForKey instead of an integer. So I tried to do the intValue method on the object I'm getting but that gave me another error, specifically:
Assigning to 'readonly' return result of an objective-c message not allowed
I'm out of ideas for how to fix this. Any help? Would it be better to just give up the idea to store them all together and just use an int property for each stat?
You can only store objects, not primitives, within Cocoa collection classes, so to store numbers you need to use NSNumber objects.
You need to use an NSMutableDictionary if you wish to change the contents later.
Your call to dictionaryWithObjectsAndKeys has the keys and values reversed.
Your stats object is not being retained, so it will be released next time round the run loop (if you're using manual reference counting, that is).
You want:
stats = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:0], #"Attack",
[NSNumber numberWithInt:0], #"Defense",
[NSNumber numberWithInt:0], #"Special Attack",
[NSNumber numberWithInt:0], #"Special Defense",
[NSNumber numberWithInt:0], #"HP",
nil] retain];
In order to change the values you need to create a new NSNumber object as they are immutable, so something like:
NSNumber *num = [stats objectForKey:#"Attack"];
NSNumber *newNum = [NSNumber numberWithInt:[num intValue] + (level * 5)];
[stats setObject:newNum forKey:#"Attack"];
All pretty tedious if you ask me; there must be an easier way, for example how about creating an Objective-C class to store and manipulate this stuff?
NSDictionarys store NSObject*s. In order to use them with integer values, you unfortunately need to use something like NSNumber. So your initialization would look like:
-(id) init {
self = [super init];
if(self) {
stats = [NSDictionary dictionaryWithObjectsAndKeys:
#"Attack", [NSNumber numberWithInt:0],
#"Defense", [NSNumber numberWithInt:0],
#"Special Attack", [NSNumber numberWithInt:0],
#"Special Defense", [NSNumber numberWithInt:0],
#"HP", [NSNumber numberWithInt:0], nil];
}
return self;
}
Then you would have to retrieve them as numbers:
NSNumber *atk = [self.stats objectForKey:#"Attack"];
int iAtk = [atk intValue];
[self.stats setObject:[NSNumber numberWithInt:iAtk] forKey:#"Attack"];
EDIT
Of course, in order to do this, self.stats needs to be an NSMutableDictionary
Adapting #trojanfoe's answer for modern Objective-C with nice syntax sugar:
stats = [#{#"Attack" : #0,
#"Defense" : #0,
#"Special Attack" : #0,
#"Special Defense" : #0,
#"HP" : #0} mutableCopy];
And to update a value:
stats[#"Attack"] = #([stats[#"Attack"] intValue] + (level * 5));

Whats the best way to convert an NSString to an NSInteger based on an array of values?

I want to convert characters into integers based on predetermined values, for example:
a = 0
b = 1
c = 2
d = 3
etc...
Right now I'm doing it with an If/Else If, I just want to know if there is a faster/better way I should be doing it because the list of conversions may get quite long.
Here's what I'm using now:
-(NSInteger)ConvertToInt:(NSString *)thestring {
NSInteger theint;
if([thestring isEqualToString:#"a"] == YES){
theint = 0;
} else if ([thestring isEqualToString:#"b"] == YES){
theint = 1;
} //etc...
return theint;
}
This works fine, but as I said, if it makes more sense can I create an array with all the key/values then just run through that to return the integers?
Please provide examples as I'm a beginner with Objective C/iOS. I come from Web languages.
Thanks!
EDIT: Thanks for the help everyone. I used taskinoors answer but I replaced the NSDictionary which was giving error messages with this:
NSDictionary *dict;
dict = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:0], #"a",
[NSNumber numberWithInt:1], #"b",
[NSNumber numberWithInt:2], #"c", nil];
unichar ch = [thestring characterAtIndex:0];
theint = ch - 'a';
Note that, 'a' with a single quote is character a, not string "a".
If the values are not regular like your example then you can store all predefined values into a dictionary. For example:
"a" = 5;
"b" = 1;
"c" = 102;
NSArray *values = [NSArray arrayWithObjects:[NSNumber numberWithInt:5],
[NSNumber numberWithInt:1], [NSNumber numberWithInt:102], nil];
NSArray *keys = [NSArray arrayWithObjects:#"a", #"b", #"c", nil];
NSDictionary *dic = [NSDictionary dictionaryWithObjects:values forKeys:keys];
theint = [[dic valueForKey:thestring] intValue];
If you wanted to keep some flexibility in what strings map to what integers, and your integers run from 0 to n-1 where you have n unique items in the array, you could do something like this:
-(NSInteger)ConvertToInt:(NSString *)thestring {
NSArray *arr = [NSArray arrayWithObjects:#"a", #"b", #"c", #"d", nil];
NSInteger theint = [arr indexOfObject:thestring];
return theint;
}
Now this will build the array each time, which would be very inefficient, the optimal way would be to build the array once in your class, and then just use a reference to that array with the indexOfObject method call.

Objective-C: Count the number of times an object occurs in an array?

I need to perform what I feel is a basic function but I can't find any documentation on how to do it. Please help!
I need to count how many times a certain object occurs in an array. See example:
array = NSArray arrayWithObjects:#"Apple", #"Banana", #"Cantaloupe", #"Apple", #"DragonFruit", #"Eggplant", #"Apple", #"Apple", #"Guava",nil]retain];
How can I iterate through the array and count the number of times it finds the string #"Apple"?
Any help is appreciated!
One more solution, using blocks (working example):
NSInteger occurrences = [[array indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {return [obj isEqual:#"Apple"];}] count];
NSLog(#"%d",occurrences);
As #bbum said, use an NSCounted set. There is an initializer thet will convert an array directly into a counted set:
NSArray *array = [[NSArray alloc] initWithObjects:#"A", #"B", #"X", #"B", #"C", #"D", #"B", #"E", #"M", #"X", nil];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:array];
NSLog(#"%#", countedSet);
NSLog output:
(D [1], M [1], E [1], A [1], B [3], X [2], C [1])
Just access items:
count = [countedSet countForObject: anObj]; ...
A Simple and specific answer:
int occurrences = 0;
for(NSString *string in array){
occurrences += ([string isEqualToString:#"Apple"]?1:0); //certain object is #"Apple"
}
NSLog(#"number of occurences %d", occurrences);
PS: Martin Babacaev's answer is quite good too. Iteration is faster with blocks but in this specific case with so few elements I guess there is no apparent gain. I would use that though :)
Use an NSCountedSet; it'll be faster than a dictionary and is designed to solve exactly that problem.
NSCountedSet *cs = [NSCountedSet new];
for(id anObj in someArray)
[cs addObject: anObj];
// then, you can access counts like this:
.... count = [cs countForObject: anObj]; ...
[cs release];
Just came across this pretty old question. I'd recommend using a NSCountedSet:
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:array];
NSLog(#"Occurrences of Apple: %u", [countedSet countForObject:#"Apple"]);
I would encourage you to put them into a Dictionary (Objective C's version of a map). The key to the dictionary is the object and the value should be the count. It should be a MutableDictionary of course. If the item is not found, add it and set the count to 1.
- (int) numberOfOccurrencesForString:(NSString*)needle inArray:(NSArray*)haystack {
int count = 0;
for(NSString *str in haystack) {
if([str isEqualToString:needle]) {
count++;
}
}
return count;
}
I up-voted Rob's answer, but I wanted to add some code that I hope will be of some assistance.
NSArray *array = [[NSArray alloc] initWithObjects:#"A", #"B", #"B", #"B", #"C", #"D", #"E", #"M", #"X", #"X", nil];
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc]init];
for(int i=0; i < [array count]; i++) {
NSString *s = [array objectAtIndex:i];
if (![dictionary objectForKey:s]) {
[dictionary setObject:[NSNumber numberWithInt:1] forKey:s];
} else {
[dictionary setObject:[NSNumber numberWithInt:[dictionary objectForKey:s] intValue]+1 forKey:s];
}
}
for(NSString *k in [dictionary keyEnumerator]) {
NSNumber *number = [dictionary objectForKey:k];
NSLog(#"Value of %#:%d", k, [number intValue]);
}
If the array is sorted as in the problem statement then you don't need to use a dictionary.
You can find the number of unique elements more efficiently by just doing 1 linear sweep and incrementing a counter when you see 2 consecutive elements being the same.
The dictionary solution is O(nlog(n)), while the linear solution is O(n).
Here's some pseudo-code for the linear solution:
array = A,B,B,B,B,C,C,D,E,M,X,X #original array
array = array + -1 # array with a dummy sentinel value to avoid testing corner cases.
# Start with the first element. You want to add some error checking here if array is empty.
last = array[0]
count = 1 # you have seen 1 element 'last' so far in the array.
for e in array[1..]: # go through all the elements starting from the 2nd one onwards
if e != last: # if you see a new element then reset the count
print "There are " + count + " " + last elements
count = 1 # unique element count
else:
count += 1
last = e
the complete code with reference to #bbum and #Zaph
NSArray *myArray = [[NSArray alloc] initWithObjects:#"A", #"B", #"X", #"B", #"C", #"D", #"B", #"E", #"M", #"X", nil];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:myArray];
for (NSString *item in countedSet) {
int count = [countedSet countForObject: item];
NSLog(#"the String ' %# ' appears %d times in the array",item,count);
}
Thank you.
If you want it more generic, or you want to count equals/different objects in array, try this:
Sign "!" count DIFFERENT values. If you want SAME values, remove "!"
int count = 0;
NSString *wordToCheck = [NSString string];
for (NSString *str in myArray) {
if( ![str isEqualToString:wordToCheck] ) {
wordToCheck = str;
count++;
}
}
hope this helps the community!
I've used it to add correct number of sections in uitableview!
You can do this way,
NSArray *array = [[NSArray alloc] initWithObjects:#"A", #"B", #"X", #"B", #"C", #"D", #"B", #"E", #"M", #"X", nil];
NSOrderedSet *orderedSet = [NSOrderedSet orderedSetWithArray:array];
NSArray *uniqueStates = [[orderedSet set] allObjects];
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:array];
for(int i=0;i<[uniqueStates count];i++){
NSLog(#"%# %d",[uniqueStates objectAtIndex:i], [countedSet countForObject: [uniqueStates objectAtIndex:i]]);
}
The result is like : A 1