OBJ-C: using valueForKeyPath when key is not a string - objective-c

I have an NSArray which contains NSDictionary objects with keys that are NSNumber objects. I would like to calculate the max value using valueForKeyPath. If I were using strings in the following example, I would use valueForKeyPath:#"#max.OHLCClose". How do I do the same with NSNumber objects as keys?
typedef enum _OHLCField {
OHLCOpen,
OHLCClose
} OHLCField;
NSMutableArray *newData = [NSMutableArray array];
newData addObject: [NSDictionary dictionaryWithObjectsAndKeys:
[NSDecimalNumber numberWithDouble:fOpen], [NSNumber numberWithInt:OHLCOpen],
[NSDecimalNumber numberWithDouble:fClose], [NSNumber numberWithInt:OHLCClose]];

KVC requires keys to be strings:
A key is a string that identifies a specific property of an object. Typically, a key corresponds to the name of an accessor method or instance variable in the receiving object. Keys must use ASCII encoding, begin with a lowercase letter, and may not contain whitespace.
So the answer is unfortunately you can't do this with valueForKeyPath:.
If you need to use NSNumber's as your keys you will have to code the algorithm yourself - just iterate over the array and find the maximum value associated with your key. You could wrap the algorithm in a category so it becomes "part" of NSArray.

Related

Get list of Values for an NSArray of NSDictionary

I've got the following NSArray :
NSArray myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
I want the list of value of my dictionaries :
#[#"5 min",#"15 min",#"30 min",#"1 hour"]
And the list of key of my dictionaries :
#[#300, #900, #1800, #3600]
What is the best way to do that ? I was thinking about predicate, but I don't know how to use it ?
Without some code to show how you'd want to go about this it is difficult to be sure exactly what you are after, and there is a bit of confusion in the question.
First, a predicate is exactly that - a statement that can be proven true or false. Predicates are hence used in logic expressions, including those employed implicitly in database queries - such as Core Data.
That is not what you want, if I read your question correctly. What you want is to reduce the complexity of your data model, removing some excess (one would hope) information in the process. A sort of flattening of an array of dictionaries.
Fair enough.
I can also see how the confusion with predicates came about - they are most often constructed using Key-Value Coding. KVC, as it is also known, is a very powerful technique that can accomplish what you are after. It just does not have much to do with a logic statement.
Having cleared that up, with KVC you can do what you want, and with minimal fuss. It goes like this:
NSArray *values = [myArray valueForKeyPath: #"#unionOfArrays.#allValues"];
NSArray *keys = [myArray valueForKeyPath: #"#unionOfArrays.#allKeys"];
A brief explanation might be in order:
The results that we want are
All the values (or keys) of each dictionary, obtaining an array of arrays of values (or keys)
Then we want to flatten these arrays into a single array.
To obtain all values (or keys) from a dictionary using KVC, the special key is #allValues or #allKeys, respectively.
The #unionOfArrays operator makes a union of the arrays obtained from the expression that follows it, i.e., flattens it into the array you wanted.
The price you pay for this coding simplicity is that you have to use KVC key paths with collection operators, which are just strings in your code. You therefore lose any help from the compiler with syntax and it doesn't check that the keys you enter exist in the objects. Similarly, the debugger and error messages are unhelpful if you mistype or use the wrong operator, for instance.
You can use dictionary property allValues to get all values of dictionary.
Try this code in your case
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arr = [NSMutableArray array];
for (NSDictionary *dict in myArray) {
[arr addObject:[[dict allValues] objectAtIndex:0]];
}
NSLog(#"%#",arr);
Note : Make sure you have only one value in each dictionary.
it will return
[
5 min,
15 min,
30 min,
1 hour
]
#johnyu's answers is technically correct, but I don't see any reason to include the secondary loop, especially if the data structure will remain the same.
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *arrayOfValues = [NSMutableArray new];
NSMutableArray *arrayOfKeys = [NSMutableArray new];
for (NSDictionary *dictionary in myArray) {
[arrayOfValues addObject:dictionary.allValues[0]];
[arrayOfKeys addObject:dictionary.allKeys[0]];
}
NSLog(#"%#",arrayOfKeys);
NSLog(#"%#",arrayOfValues);
Try this:
NSArray *myArray = #[#{#300:#"5 min"},
#{#900:#"15 min"},
#{#1800:#"30 min"},
#{#3600:#"1 hour"}];
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
NSMutableArray *valueArray = [[NSMutableArray alloc] init];
for (NSDictionary *dictionary in myArray) {
for (NSString *key in dictionary) {
[keyArray addObject:key];
[valueArray addObject:[dictionary objectForKey:key]];
}
}

What's the real type? NSString or NSNumber

I have a NSDictionary that contains data converted from json data, like {"message_id":21}.
then I use NSNumber *message_id = [dictionary valueForKey:#"message_id"] to get the data.
but when I use this message_id,
Message *message = [NSEntityDescription ....
message.messageId = message_id;
I got the runtime error, assigning _NSCFString to NSNumber,
so I have to use NSNumberFormatter to do the conversion.
NSString *messageId = [dictionary valueForKey:#"message_id"];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterNoStyle];
message.messageId = [f numberFromString:messageId];
this code works.
but when I was debugging, I saw message_id of
NSNumber *message_id = [dictionary valueForKey:#"message_id"]
has a valid value, 21.
Can anyone see the problem here?
You are trying to save a NSString to a NSNumber. If you want it as an NSNumber you can do:
NSNumber *message_id = [NSNumber numberWithInt:[[dictionary valueForKey:#"message_id"] intValue]];
This should solve your problem.
What library are you using to do the conversion? {"message_id":21} means that an NSNumber with a value of 21 should be returned as an NSNumber, {"message_id":"21"} should return it as an NSString.
Using a number formatter is total overkill. Use the method "integerValue" which works just fine both with NSString* and with NSNumber* - you will get the integer 21, whether the object is NSString or NSNumber. The formatter code will obviously run into trouble if your object is an NSNumber and not an NSString.
So: message.messageId = [[NSNumber numberWithInteger:[messageId integerValue]];
I'd probably add a category to NSDictionary
(NSNumber*)nsIntegerNumberForKey:(NSString*)key
which handles the situations where the key is not present, or where the value is a null value or a dictionary or array, so you can use it everywhere you need an NSNumber with an integer value from a JSON document and have error checking everywhere.
Read here SAVING JSON TO CORE DATA and JSON official page
The JSON standard is quite clear about how to distinguish strings from
numbers– basically, strings are surrounded by quotes and numbers are
not. JSON web services however, are not always good about following this requirement. And even when they are, they are not always consistent from one record to another.
So if you have receive NSNumber where NSString is preferred, you must inspect and fix yourself

NSMutableArray contains Objects

I have to check whether an NSMutableArray contains an object multiple times (for e.g. the array contains 1,2,3,1,4), I want to know how many times 1 is present in the array. I am aware of containsObject: but how to use it for this kind of check?
NSCountedSet may help as you want to track how many times a duplicate value occurs.
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSCountedSet_Class/Reference/Reference.html#//apple_ref/occ/cl/NSCountedSet
A quick way would be to convert it to an NSSet and then back to an array. NSSets cannot contain duplicates. Alternatively copy the values one by one into a new array using a loop, and each time check that the new array does not contain a copy of the object before adding it.
It depends on your object types, but if they can be used as keys for an NSDictionary, I would create an NSMutableDictionary that points to NSNumber objects containing counts for each object instance. Something like:
NSArray *array = whatever;
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:array.count];
for ( id obj in array )
{
NSNumber *number = [d objectForKey:obj];
if ( number == nil )
{
[d setObject:[NSNumber numberWithInt:1] forKey:obj];
}
else
{
[d setObject:[NSNumber numberWithInt:([number intValue]+1) forKey:obj];
}
}
At the end of this code, you are left with an NSDictionary where the keys are your original objects and the values are NSNumbers that contain the number of times that key exists in the original.

NSNumber as key for NSDictionary

I was wondering how keys work in a NSDictionary. Usually, I will use a NSString as a key for example:
NSString *stringKey = #"stringKey";
[mydict objectForKey:stringKey];
What if I wanted to use a NSNumber:
NSNumber *numberKey = [NSNumber numberWithInt:3];
[mydict objectForKey:numberKey];
Does the dictionary go look for the key with number 3? or would it just compare the address of the numberKey?
Two keys are equal if and only if [key1 isEqual:key2]. Some classes may go with the -[NSObject isEqual:] implementation of return self == other;, but it's quite common for classes (such as NSString, NSNumber, etc) to override it to do more context-specific comparison.
I think it will look at the value of NSNumber. i use NSNumber as key for dictionary.
1. store the key in view.tag
view.tag = [self getUniqueNSIntegerKey]; --> get a unique key and save in view.tag
[dictionary setOjbect: object forKey: [NSNumber numberWithInt:view.tag]]; // add the object
2. retrieve the object with the view.tag later
object = [dictionary objectForKey:[NSNumber numberWithInt:view.tag]]; // get the object via view.tag
it just works.....

Changing value of a NSNumber

Is there a way to change the value contained in an NSNumber after it is created without making it point to a different NSNumber?
NSNumber *num = [NSNumber numberWithInt:0];
num = [NSNumber numberWithInt: 1]; // now num points to a different object, which I don't want. I want it the same object still, but different value.
NSNumber is immutable. Actually, it's a subclass of NSValue, and all NSValues are immutable.
No, you can't change the value of NSNumber.
See, for example, this post.