i have created NSMutableDictionary with 10 keys.Now i want to access NSMutableDictionary keys in a same order as it was added to NSMutableDictionary (using SetValue:* forKey:* );
How can i achieve that ?
If you absolutely must use a dictionary container, you have to use a key that is sortable by the order in which you add key-value pairs. Thus, when creating your dictionary, you use a key that is an auto-incrementing integer or similar. You can then sort on the (integer) keys and retrieve the values associated with those keys.
If you do all of that, however, you may as well just use an NSMutableArray and add values to the array directly! It will be much faster and require less code. You just retrieve objects in order:
for (id obj in myArray) { /* do stuff with obj... */ }
NSMutableDictionary can't do that. Take a look at e.g. Matt Gallaghers OrderedDictionary.
I wrote a quick method to take a source array (of objects that are all out of order) and a reference array (that has objects in a desired (and totally arbitrary) order), and returns an array where the items of the source array have been reorganized to match the reference array.
- (NSArray *) reorderArray:(NSArray *)sourceArray toArray:(NSArray *)referenceArray
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [referenceArray count]; i++)
{
if ([sourceArray containsObject:[referenceArray objectAtIndex:i]])
{
[returnArray addObject:[arrReference objectAtIndex:i]];
}
}
return [returnArray copy];
}
Note that this is very fragile. It uses NSArray's containsObject: method, which ultimately will call NSObject's isEqual:. Basically, it should work great for arrays of NSStrings, NSNumbers, and maybe NSDates (haven't tried that one yet), but outside of that, YMMV. I imagine if you tried to pass arrays of UITableViewCells or some other really complex object, it would totally sh*t itself, and either crash or return total garbage. Likewise if you were to do something like pass an array of NSDates as the reference array and an array of NSStrings as the source array. Also, if the source array contains items not covered in the reference array, they'll just get discarded. One could address some of these issues by adding a little extra code.
All that said, if you're trying to do something simple, it should work nicely. In your case, you could build up the reference array as you are looping through your setValue:forKey:.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (//whatever you are looping through here)
{
[yourDictionary setValue://whatever forKey:key];
[referenceArray addObject:key];
}
Then, when you want to loop over your items in the order they came in, you just
for (NSString *key in [self reorderArray:[myDict allKeys] toArray:referenceArray])
Actually you have a reference array in order manner then why you have to add to one more array.So i guess this approach is not good.Please consider my opinion.
Although #GenralMike 's answer works a breeze, it could be optimized by leaving off the unnecessary code as follows:
1) Keep an array to hold reference to the dictionary keys in the order they are added.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (id object in someArray) {
[yourDictionary setObject:object forKey:someKey];
[referenceArray addObject:someKey]; // add key to reference array
}
2) Now the "referenceArray" holds all of the keys in order, So you can retrieve objects from your dictionary in the same order as they were originally added to the dictionary.
for (NSString *key in referenceArray){
//get object from dictionary in order
id object = [yourDictionary objectForKey:key];
}
Related
I learn objective-C from Stanford iTunes and i wonder how i should copy a NSMutableArray to NSArray without initialization. I mean:
Is this is correct? with "lazy initialization".
-(void)copyAnArray:(NSMutableArray*)listOfElements {
if(privateElementsLists == nil)
privateElementsLists = [[NSArray alloc] initWithArray:listOfElements copyItems:YES];
else
privateElementsLists = listOfElements;
}
is this a bad design?
I want to addobjects to mutable array in one class, and then when i'm finish copy entire NSMutableArray to NSArray.
And another question: Why i have to use copyItems:YES when I use initWithArray? And what's deep copy?
You can copy a mutable array to a new array with initWithArray: or this way:
privateElementsLists = [NSArray arrayWithArray:listOfElements];
then you are creating a new array where each of its elements is the same object that figures in the original array. If you write:
privateElementsLists = [NSArray arrayWithArray:listOfElements copyItems:YES];
then the new array have, for each element, a copy of the element in original array. They are not the same object but a copy. Of course, that objects have to be able to respond to copy.
You can even do this:
privateElementsLists = (NSArray*) listOfElements ;
Then the array is exactly the same as the original one. No new array here. But as you have casted it with NSArray pointer class, you can use it as if it is a NSArray instead of a NSMutableArray. As you know, every NSMutableArray is a NSArray (inherited class).
As Joseph DeCarlo stated, you don't need to copy NSMutableArray to NSArray if the only thing you do is to create the array in one place to use it somewhere else. For example this statement is valid:
NSArray* newArray = [NSMutableArray array];
Or in the code:
-(NSArray*)returnAnArray
{
NSMutableArray* editableArray = [NSMutableArray array];
[editableArray addObject:[[NSObject alloc] init]]; //an exemplary object added to the array
return editableArray;
}
That said, however, in some specific cases casting NSMutableArray to NSArray may not be safe, e.g. if the original array was stored in an instance variable. Adding or removing objects to/from that array may cause a crash if the returned array is enumerated at the same time. For example:
-(void)createArray
{
self->editableArray = [NSMutableArray array]; // instance variable: NSMutableArray* editableArray
}
-(void)addObjectToArray
{
[self->editableArray addObject:[[NSObject alloc] init]];
}
-(NSArray*)getArray
{
return self->editableArray;
}
-(void)enumerateArray
{
for(NSObject obj in [self getArray])
{
// do something with obj
}
}
If addObjectToArray is called at the same time as enumerateArray (e.g. from a background thread) the application will crash because the underlying array is changing while it is being enumerated. It doesn't matter that it was returned as NSArray*. In a case like this you would need to either add #synchronized to synchronize access to the same object by multiple threads, or copy the entire array with arrayWithArray: as suggested. Note, however, that the documentation doesn't say if arrayWithArray: is thread safe so I would add #synchronized around the call to arrayWithArray: anyway.
I am using one NSMutableArray with same string object.
Here is the code
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
NSObject *obj = [arr objectAtIndex:2];
[arr removeObject:obj];
NSLog(#"%#",arr);
When i try to remove 3rd object of array, its removing all object with "hi" string.
I am not getting why its happening.
my doubt is while removing object, NSMutableArray match string or address.
It's because you're using removeObject which removes all objects that are "equal" to the one you pass in. As per this Apple documentation:
This method uses indexOfObject: to locate matches and then removes
them by using removeObjectAtIndex:. Thus, matches are determined on
the basis of an object’s response to the isEqual: message. If the
array does not contain anObject, the method has no effect (although it
does incur the overhead of searching the contents).
You're seeing the effects of literal strings here where each of those #"hi" objects will turn out to be the same object just added many times.
What you really want to do is this:
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
[arr removeObjectAtIndex:2];
NSLog(#"%#",arr);
Then you're specifically removing the object at index 2.
According to https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html
removeObject:
Removes all occurrences in the array of a given object.
which is exactly the behaviour you're seeing. If you want to remove the object at a particular position, you want removeObjectAtIndex:.
NSMutableArray *arr = [[NSMutableArray alloc]initWithObjects:#"hello",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",#"hi",nil];
NSUInteger obj = [arr indexOfObject:#"hi"]; //Returns the lowest integer of the specified object
[arr removeObjectAtIndex:obj]; //removes the object from the array
NSLog(#"%#",arr);
I am having an NSMutableDictionary with, being filled dynamically. It looks like this:
{
SomeKey = 6;
AnotherKey = 2;
JustAnotherKey = 28;
}
I need to sort this, so it will be like this one:
{
JustAnotherKey = 28;
SomeKey = 6;
AnotherKey = 2;
}
Is there any way to achieve this? Thanks in advance.
No, sorry!
An NSDictionary doesn't support sorting it's keys - you would have to do that yourself.
Get the keys array from your dictionary, sort that and then go through and get the values from your dictionary. Something like :
NSArray *keys = [myDictionary allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingSelector:#selector(compare:)];
NSArray *values = [NSMutableArray array];
for (id key in sortedKeys)
[values addObject:[myDictionary objectforKey:key]];
Now, values are in the correct order.
However, that's quite a lot of work; if you want them sorted, I would look at storing them in an array to start with?
A Dictionary is an unordered set, which means it doesn't have any order of its elements.So even if you insert the first object as say "one":"first value", and then "two":"second value", when you iterate over the keys, you might get it in any random order(eg: "two' and then "one").
However, if all you want is the values in sorted order, you can iterate over all the keys, fetch the values and store it in an array, and then sort them.
NSArray *values=[myDict allValues];
NSMutableArray *sortedKeys=[[NSMutableArray alloc] init];
NSArray *sortedValues = [values sortedArrayUsingSelector:#selector(yourSelector)];
for (val in sortedArray){
NSString *key=(NSString*)[[myDict allKeysForObject:val] objectAtIndex:0];
[sortedKeys addObject:key]
}
This would be starter for getting first the values in sorted order, and then the corresponding keys. (It is not doing any error checks. So beware of OutOfIndex exceptions).
I am not sure how good would be the efficiency of this code be, coz allKeysForObject would be iterating over all the keys.
Instead of using a NSMutableDictionary, you might consider to use a NSMutableArray and populate it with your own model class which contains a property with the key string and a property with the value.
NSMutableArray provides methods to sort these objects.
Is this helpful enough?
I have a NSMutableArray (tripHistory) that gets a NSMutableDictionary (currentUpdate) added to it every second with new data.
[currentUpdate setObject:testVariable forKey:#"Test"];
[tripHistory addObject:currentUpdate];
[currentUpdate removeAllObjects];
Yet when I loop through tripHistory calling [[tripHistory objectAtIndex:i] description] everything is null.
My loop is as follows:
for (int i=0; i<[tripHistory count]; i++)
{
NSLog(#"%#", [[tripHistory objectAtIndex:i] description]);
}
To initialize my variables, the following code is called only for the first update.
tripHistory = [[NSMutableArray alloc] init];
currentUpdate = [[NSMutableDictionary alloc] init];
Any ideas?
Adding an object to an NSArray doesn't make a copy of it. All that happens is its reference count is incremented. Therefore currentUpdate and the NSDictionary added to tripHistory are one-and-the-same. If you remove objects from currentUpdate you are also removing objects from the NSDictionary in tripHistory.
After adding currentUpdate to tripHistory all you need to do is release currentUpdate and start again with a new empty NSDictionary for the next update.
Do not remove objects. Dictionary and array are keeping references to the same objects. If you want to add another dict to array then release first dict and create a new one
You are removing all of the contents of the dictionary. Adding the dictionary to your array doesn't make a copy: The array will retain a reference to the dictionary, which you proceed to empty out each time you add something to it.
You should allocate a new dictionary each time through the loop and then add that dictionary o the array. If you are not using garbage collection or ARC you should also release the dictionary after it is added to the array.
Ok so I want to create a temporary NSDictionary from a NSDictionary of nested dictionaries, but I want to deep copy individual items(dictionaries) from the top level dictionary.
The end result is to have a filtered dictionary that i can process and discard without effecting the main dictionary.
That sounds really confusing, so how about a little code to show you what I mean, heres the function i'm working on, this is a rough coding layout, but basically complete in its path of process.
I've looked at reference books and various samples online with no joy.
Cheers,
Darren
- (void)setPricingData
{
// get selected lens option
NSDictionary *aOption = [self.lensOptionsDict objectAtIndex:self._lensOptionsIndex];
if ( aOption == nil )
return;
// get selected lens type
NSDictionary *aType = [self.lensTypesDict objectAtIndex:self._lensTypesIndex];
if ( aType == nil )
return;
// get lens option id and variation_id
NSString *option_id = [aOption valueForKey:#"id"];
NSString *option_variation_id = [aOption valueForKey:#"variation_id"];
// create temp dictionary for type pricing selection
int count = [self.lensTypesDict count];
NSMutableDictionary *aPrices = [[NSMutableDictionary alloc] initWithCapacity:count];
// cycle prices for option id and variation_id matches
for ( NSDictionary *item in self.pricesDict )
{
NSString *variation_id = [item valueForKey:#"variation_id"];
NSString *value_id = [item valueForKey:#"value_id"];
// add matches to temp dictionary
if ( [option_variation_id isEqualToString: variation_id] )
{
if ( [option_id isEqualToString: value_id] )
[aPrices addObject: item];
}
}
// get price from temp dictionary for selected lens type index
NSDictionary *price = [aPrices objectAtIndex:self._lensTypesIndex];
if ( price != nil )
{
// assign values to outlet
self.priceAndStockId = [price valueForKey:#"price"];
self.priceSelected = [price valueForKey:#"price"];
}
// release temp dictionary
[aPrices release];
}
It looks like you're mixing up dictionaries with arrays.
Arrays respond to objectAtIndex whereas dictionaries respond to objectForKeys. Remember that an array is a set of cells that you can index into, starting from 0 all the way up to [array count] - 1.
A dictionary is similar to an array, except that a hash function is used as the indexing method. This means that you need a key to get, or set, and object.
Setting an object in an NSMutableDictionary
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] init];
[myDictionary setObject:anObject forKey:aKey];
Or, you can have an array of keys and corresponding array of objects, and do:
NSDictionary *completeDictionary;
completeDictionary = [NSDictionary dictionaryWithObjects:objectArray
forkeys:keyArray count:[keyArray count]];
In either case, you must have keys for objects. This is in contrast to a regular array in which you can simply do
[myArray addObject:myObject];
To get objects from a dictionary, do
myObject = [myDictionary objectForKey:key];
To get objects from an array, do
myObject = [myArray objectAtIndex:anIntegerIndex];
Finally, your original question pertained to deep copying. To have your dictionary keep an object that won't change, ie, a deep copy, you can do the following:
Assuming I want to store a dictionary within a dictionary, and I have an associated key for the top-level dictionary, I can do the following:
I have an NSMutableDictionary, called topLevelDictionary
I have an NSDictionary, called dictionaryTwo
I have an NSString, which is my key, called myKey.
To make a deep copy of dictionaryTwo, I can do
// assuming topLevelDictionary is previously defined
[topLevelDictionary setObject:[[dictionaryTwo copy] autorelease] forKey:myKey];
In this manner topLevelDictionary will contain a copy of dictionaryTwo whereby if dictionaryTwo changes, the object in topLevelDictionary will not.