I have an NSArray and I need to get data from two keys and put together in a NSMutableDictionary. One key has stringvalues and the other NSNumbervalues. When I try to create NSCountedSetwithout adding the keys I want to use to separate arrays, it doesn't work, because the objects are not identical, basically, I need to check if objectId is identical, don't matter if the other keys are different.
Here is the initial code:
for (PFObject *objeto in objects) {
PFObject *exercicio = objeto[#"exercicio"];
NSString *string = exercicio.objectId;
NSNumber *nota = objeto[#"nota"];
[exercicios addObject:string];
[notas addObject:nota];
So I create two NSMutableArraysand store the values I need. When I logthe arrays after this, they are perfectly ordered, meaning the NSStringis in the same indexof the NSNumberit belongs to in the other array. So far, so good.
Now, when I create the NSCountedSetwith the strings, it changes the order.
NSCountedSet *countedExercicios = [[NSCountedSet alloc] initWithArray:exercicios];.
My goal is to sum the NSNumbers pertaining to an specific object, therefore, when the order changes, I lose the connection between the two arrays.
I'm not sure what I could do to solve this problem, or even if there's a different approach to achieve the result I need.
You can create NSDictionary and add it to array. You will have just one array and you won't lose the connection, you can use objectId as a key and NSNumber as a value:
for (PFObject *objeto in objects) {
PFObject *exercicio = objeto[#"exercicio"];
NSString *string = exercicio.objectId;
NSNumber *nota = objeto[#"nota"];
NSDictionary *dict = #{string: nota};
[newArray addObject: dict];
}
When you need get all key (objectId) you can use NSPredictate.
Hope this help
Related
I have a UITableView and am displaying contents from my NSMutableArray. Following is array format
(
{
Name = "ANS";
VersionNo = 6;
},
{
Name = "O-Hydro";
Version = 6;
},
{
Name = "ANS";
Version = 6;
},
{
Name = "ANTIChorosAnticholinergic";
Version = 6;
}
)
From this I need to display only unique "Name" (like in this I can see 2 "ANS" I need only one).
How can I do this in iOS?
I tried following but its not working
uniqueArray= [[NSMutableSet setWithArray: groupDetails] allObjects];
but in this way I can do only for NSArray not NSMutableArray.
Pls help me
You can use following line of code to convert your NSArray to NSMutableArray,
NSArray *uniqueArray= [[NSMutableSet setWithArray:groupDetails] allObjects];
NSMutableArray *myMutableArray = [[NSMutableArray alloc] initWithArray:uniqueArray];
You could simply add mutableCopy.
But wait, before you do it. Arrays and sets have two differences:
Arrays can contain duplicates, sets cannot.
Arrays are ordered, sets are not.
So doing what you are doing, you lose the duplicates (intentionally), but the order, too (probably not intentionally).
I do not know, whether this is important for you, but for other readers it might be. So it is the better approach to do that with NSOrderedSet instead of NSSet:
NSOrderedSet *uniqueList = [NSOrderedSet orderedSetWithArray:array];
In many cases an ordered set is exactly what you want. (Probably it has been from the very beginning and the usage of NSArray was wrong. But sometimes you get an array.) If you really want an array at the end of the day, you can reconvert it:
array = [uniqueList.array mutableCopy];
If you just want an array of unique name values, you can use #distinctUnionOfObjects with valueForKeyPath -
NSArray *uniqueArray=[groupDetails valueForKeyPath:#"#distinctUnionOfObjects.name"];
But if you want the array to contain the dictionaries that correspond to the unique names then you need to do a little more work -
NSMutableArray *uniqueArray=[NSMutableArray new];
NSMutableSet *nameSet=[NSMutableSet new];
for (NSDictionary *dict in groupDetails) {
NSString *name=dict[#"name"];
if (![nameSet containsObject:name]) {
[uniqueArray addObject:dict];
[nameSet addObject:name];
}
}
I've got a .plist like this:
Click image for full resolution
How can I create different arrays for each subpart of items. like NSArray foodName with contents of item 0's item 1's to item n's foodName
There are lots of items.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
self.pFile = [[paths objectAtIndex:0]
stringByAppendingPathComponent:#"recipes.plist"];
self.plist = [[NSMutableDictionary alloc] initWithContentsOfFile:pFile];
if (!plist) {
self.plist = [NSMutableDictionary new];
[plist writeToFile:pFile atomically:YES];
}
An NSDictionary is probably what you want. Then do a 'for' on it for each recipe / recipeDetail / wherever you're at in the structure?
You don't need to create "different arrays".
A plist is a textual (or binary) representation of a collection of objects. The valid object kinds come from a small set which includes NSArray, NSDictionary, NSNumber & NSString. The collection is rooted either in a dictionary or an array, each element of which can be any of the valid object kinds, including further dictionaries and arrays.
When you read the plist in your application the collection of objects is re-created. So if there are nested arrays or dictionaries they are recreated. To access them you just need to use the appropriate sequence of indices (arrays) and/or keys (dictionaries) which specify the element you need. You can store the returned object reference into a variable.
So for example if your plist is a dictionary keyed by vegetable names, each element of which is an array, and the third element of that array is another array of observed weights of that vegetable then you can access that array as follows (code just typed into answer, expect errors):
NSDictionary *vegetableInfo = [NSDictionary dictionaryWithContentsofURL:urlOfVegtableInfoPlist"];
NSArray *carrrotObservedWeights = vegetableInfo[#"Carrot"][3];
You now have a reference to the required array stored in carrrotObservedWeights.
If you are concerned over memory management, first use ARC. Second if you just want the extracted arrays and not the whole plist to be kept you just need to drop the reference to the plist after you've stored strong references to the contained arrays and ARC will clean up for you.
HTH
Addendum - After question clarified
First your plist is a dictionary of dictionaries where the containing dictionary has the keys Item 1, Item 2, etc. These keys carry no information and you would be better off making your plist an array of dictionaries.
Next we assume you have read in your plist using one of the standard methods and have a reference to it in sample - which is either an NSDictionary * if your plist is as shown, or an NSArray * if you modify the plist as suggested.
How many ways to do this? Many, here are three.
Method 1 - Simple Iteration
The straightforward way to obtain your arrays is simple iteration - iterate over each item and build your arrays. Here is a code fragment for two of the fields assuming the original dictionary of dictionaries:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
for (NSString *itemKey in sample) // for-in on a dictionary returns the keys
{
NSDictionary *item = sample[itemKey]; // use the key to obtain the contained dictionary
[foodNames addObject:item[#"foodName"]]; // extract each item
[hardnesses addObject:item[#"hardness"]];
}
The above fragment if sample is an array is similar.
Method 2 - Keypaths
If you do switch your plist to an array you can use a keypath to obtain all the values in one go. The method valueforkeypath: creates an array from an array by extracting the keypath - a list of keys separated by dots which allows for dictionaries within dictionaries. In your case the keypath has just one item:
NSMutableArray *foodNames = [sample valueForKeyPath:#"foodName"];
NSMutableArray *hardnesses = [sample valueForKeyPath:#"hardness"];
This will not work for your plist as shown with a top-level dictionary as they keypath is different each time - Item 1.foodName, Item 2.foodName, etc. - and wildcards (e.g. *.foodName) are not supported. A good reason to change your plist to have a top-level array!
Method 3 - Encapsulated Iteration
This is just a variation of method 1, but shows how to use the supplied block enumeration methods on NSDictionary. Rather than write a for loop yourself you can pass the body of the loop as a block to enumerateKeysAndObjectsUsingBlock: and it will perform the iteration:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
[sample enumerateKeysAndObjectsUsingBlock:^(id key, id item, BOOL *stop)
{
[foodNames addObject:item[#"foodName"]];
[hardnesses addObject:item[#"hardness"]];
}];
I'd make the root of the plist an array, but still have each item as a dictionary, so i could do something like this:
//ASSUMING YOUR PLIST IS IN RESOURCES FOLDER
NSString *filepath = [[NSBundle mainBundle] pathForResource:#"YOUR_PLIST_FILE_NAME" ofType:#"plist"];
NSArray *foodArray = [NSArray arrayWithContentsOfFile:filepath];
NSMutableArray *foodNames = [[NSMutable Array] init];
NSInteger i = 0;
for (Item *item in foodArray) {
NSDictionary *itemDict = [foodArray objectAtIndex:i];
NSString *foodName = [itemDict objectForKey:#"foodName"];
[foodNames addObject:foodName];
i++;
}
Hope this helps you figure something out!
I'm working with a plist file at the moment but intend to switch over to json when the backend is finally built. So for the moment my plist is an array that contains a bunch of dictionaries.
I'd like to use this information to create a new array containing only the dictionaries with certain values.
For example. My plist contains a bunch of locations like so:
key: location value:example place name here
key: type value:indoor
I want to build an array containing only those with "indoor" set as the type value.
And then perhaps a second one containing all "outdoor" locations.
What's the best way to go about doing this, or perhaps I can be directed to a tutorial of some sort.
Thanks.
Simply loop through your array and add the qualifying dictionaries to a new array.
NSMutableArray *arrayIndoor = [NSMutableArray array];
NSMutableArray *arrayOutdoor = [NSMutableArray array];
NSString *type;
for (NSDictionary *dict in arrayPList) {
type = [dict objectForKey:#"type"];
if ([type isEqualToString:#"indoor"])
[arrayIndoor addObject:dict];
else if ([type isEqualToString:#"indoor"])
[arrayOutdoor addObject:dict];
}
All you are really needing to do is sort the array into two arrays. There isn't a direct method that I have seen that will do this for you. My suggestion would be to use a fast enumeration over the array and conditionally break it into two new arrays.
NSMutableArray *locations = [[NSMutableArray alloc] init];
NSMutableArray *type = [[NSMutableArray alloc] init];
for (NSDictionary *dict in MyPlistArray) {
if ([dict valueForKey:#"locationKey"]) {
[locations addObject:dict];
} else if ([dict valueForKey:#"typeKey"]) {
[type addObject:dict];
}
}
You might need to use a different method for determining which key to put in each array, but you get the general idea.
Also I'm assuming that you would want the arrays of dictionaries to persist after, so you can just set those up as properties instead of local variables.
I'm communicating with an API that sends back an NSDictionary as a response with data my app needs (the data is basically a feed). This data is sorted by newest to oldest, with the newest items at the front of the NSDictionary.
When I fast enumerate through them with for (NSString *key in articles) { ... } the order is seemingly random, and thus the order I operate on them isn't in order from newest to oldest, like I want it to be, but completely random instead.
I've read up, and when using fast enumeration with NSDictionary it is not guaranteed to iterate in order through the array.
However, I need it to. How do I make it iterate through the NSDictionary in the order that NSDictionary is in?
One way could be to get all keys in a mutable array:
NSMutableArray *allKeys = [[dictionary allKeys] mutableCopy];
And then sort the array to your needs:
[allKeys sortUsingComparator: ....,]; //or another sorting method
You can then iterate over the array (using fast enumeration here keeps the order, I think), and get the dictionary values for the current key:
for (NSString *key in allKeys) {
id object = [dictionary objectForKey: key];
//do your thing with the object
}
Dictionaries are, by definition, unordered. If you want to apply an order to the keys, you need to sort the keys.
NSArray *keys = [articles allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingSelector:#selector(compare:)];
for (NSString *key in sortedKeys) {
// process key
}
Update the way the keys are sorted to suit your needs.
As other people said, you cannot garantee order in NSDictionary. And sometimes ordering the allKeys property it's not what you really want. If what you really want is enumerate your dict by the order your keys were inserted in your dict, you can create a new NSMutableArray property/variable to store your keys, so they will preserve its order.
Everytime you will insert a new key in the dict, insert it to in your array:
[articles addObject:someArticle forKey:#"article1"];
[self.keys addObject:#"article1"];
To enumerate them in order, just do:
for (NSString *key in self.keys) {
id object = articles[key];
}
Let's say I have an NSArray of NSDictionaries that is 10 elements long. I want to create a second NSArray with the values for a single key on each dictionary. The best way I can figure to do this is:
NSMutableArray *nameArray = [[NSMutableArray alloc] initWithCapacity:[array count]];
for (NSDictionary *p in array) {
[nameArray addObject:[p objectForKey:#"name"]];
}
self.my_new_array = array;
[array release];
[nameArray release];
}
But in theory, I should be able to get away with not using a mutable array and using a counter in conjunction with [nameArray addObjectAtIndex:count], because the new list should be exactly as long as the old list. Please note that I am NOT trying to filter for a subset of the original array, but make a new array with exactly the same number of elements, just with values dredged up from the some arbitrary attribute of each element in the array.
In python one could solve this problem like this:
new_list = [p['name'] for p in old_list]
or if you were a masochist, like this:
new_list = map(lambda p: p['name'], old_list)
Having to be slightly more explicit in objective-c makes me wonder if there is an accepted common way of handling these situations.
In this particular case Cocoa is not outdone in succinctness :)
NSArray *newArray = [array valueForKey:#"name"];
From the NSArray documentation:
valueForKey:
Returns an array containing the
results of invoking valueForKey: using
key on each of the receiver's objects.