Overview
I am using fast enumeration to iterate through an NSDictionary instance
I expected the NSDictionary instance to be enumerated based on the ascending order of the key but that doesn't seem to be the case
What I want to do:
I want to be able iterate through the NSDictionary instance in the ascending order of the key using fast enumeration
Note: Pls see expected output vs actual output
Questions
Am i making a mistake with my implementation ?
Does NSDictionary's fast enumeration guarantee ordering based on keys ?
If not then is there a work around for this and yet use fast enumeration ?
Example
#import<Foundation/Foundation.h>
int main()
{
system("clear");
NSDictionary *d1 = nil;
#autoreleasepool
{
d1 = [[NSDictionary alloc] initWithObjectsAndKeys: #"AAA", [NSNumber numberWithInt:10],
#"BBB", [NSNumber numberWithInt:20],
#"CCC", [NSNumber numberWithInt:30],
nil];
}
for(NSNumber* n1 in d1) //I expected fast enumeration for NSDictionary to be based on the
//ascending order of the key but that doesn't seem to be the case
{
printf("key = %p"
"\t [key intValue] = %i"
"\t value = %s\n",
n1,
[n1 intValue],
[[d1 objectForKey:n1] UTF8String]);
}
return(0);
}
Expected Output
key = 0xa83 [key intValue] = 10 value = AAA
key = 0x1483 [key intValue] = 20 value = BBB
key = 0x1e83 [key intValue] = 30 value = CCC
Actual Output
key = 0x1e83 [key intValue] = 30 value = CCC
key = 0xa83 [key intValue] = 10 value = AAA
key = 0x1483 [key intValue] = 20 value = BBB
for (NSString *key in [[d1 allKeys] sortedArrayUsingSelector:#selector(compare:)])
{
id value = [d1 valueForKey:key];
...
}
No your implementation is correct.
NSDictionary fast enumeration does not guarantee sorting (and it will not output anything in order because of implementation as hashed container).
No, you have to sort it yourself.
There is no guaranties about the order in which you will receive your object.
allKeys
Returns a new array containing the dictionary’s keys.
- (NSArray *)allKeys
Return Value
A new array containing the dictionary’s keys, or an empty array if the dictionary has no entries.
Discussion
The order of the elements in the array is not defined.
So my suggestion is, if your dictionary doesn't change often, cache an NSArray with the key in the order you want them.
If your dictionary often change, you may have to sort allKeys when you need them.
Related
I'm trying to figure out the best way to sort an NSMutableDictionary. I have a dictionary of Card keys (i.e. aceSpades) that store Card values (i.e. 14). I have then been using an NSMutableArray to shuffle the 52 Card keys into an array called shuffledCards. Finally I make another array from shuffledCards thats takes a portion (15) of shuffledCards and puts them into an array called computerHand.
The new array computerHand is not good enough because I need to be able to connect the Card values with the Card keys. What I really need to do is create a new NSMutableDictionary for computerHand from the array shuffledCards so that I can sort it by Card values and still be able to retrieve the Card keys.
I'm thinking I need something like this, where currentCard is the first card of the shuffedCards array:
if (currentCard == 1) {
[compHandDictionary setObject:[[highCardDictionary
valueForKey:[shuffledCards objectAtIndex:currentCard]] intValue]
forKey:[cardsShuffled objectAtIndex:currentCard]];
}
However this is not allowed because "int" to "id" is not allowed.
There might be a better way but I have not been able to find anything. Any help would be appreciated.
...
I got this to work by modifying jstevenco's answer. I created two arrays and formed a new dictionary for the computer hand of just the 15 cards. Then to sort I used:
NSArray* sortedKeys = [newDict keysSortedByValueUsingComparator: ^(id obj1, id obj2) {
if ([obj1 integerValue] > [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
if ([obj1 integerValue] < [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Thanks all!
You can sort a dictionary's keys using:
NSArray* sortedKeys = [[dict allKeys] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
If you then want to create a sorted array from this you can use:
NSArray* objects = [dict objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
Consider using an array of dictionaries, where each dictionary contains the name and value for a given card, for example like so:
NSArray *cards = #[#{#"name" : #"Queen", #"value" : #12},
#{#"name" : #"Jack", #"value" : #11},
#{#"name" : #"Ace", #"value" : #14},
#{#"name" : #"King", #"value" : #13}];
You could then easily sort the cards array as shown below:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"value" ascending:YES];
NSArray *sortedCards = [cards sortedArrayUsingDescriptors:#[sortDescriptor]];
NSLog(#"\n\n%#\n\n", sortedCards);
Output of the NSLog statement above would be as follows:
(
{
name = Jack;
value = 11;
},
{
name = Queen;
value = 12;
},
{
name = King;
value = 13;
},
{
name = Ace;
value = 14;
}
)
To obtain an array of card names, you could then simply send a valueForKey: message to the sorted array of card dictionaries:
NSArray *sortedNames = [sortedCards valueForKey:#"name"];
NSLog(#"\n\n%#\n\n", sortedNames);
The output of the preceding NSLog statement would be:
(
Jack,
Queen,
King,
Ace
)
I have a Person NSDictionary, whose key is the Name of the person, and the object is an NSDictionary with two keys: his nickname (NSString) and his age (NSNumber).
I would like to end up with the Person dictionary sorted by the ascending order of their age, so that I could get the name of the youngest and the oldest person.
What is the best way to do it?
Thanks!
There are a few convenience methods defined in NSDictionary to sort items by values and get back the sorted keys.
See docs,
keysSortedByValueUsingComparator:
keysSortedByValueUsingSelector:
keysSortedByValueWithOptions:usingComparator:
I'm guessing you're using the modern Objective-C syntax and the age is actually represented as numbers. Here's how it looks:
[people keysSortedByValueUsingComparator:(NSDictionary *firstPerson, NSDictionary *secondPerson) {
return [firstPerson[#"age"] compare:secondPerson[#"age"]];
}];
Some languages offer sorted dictionaries, but the standard NSDictionary is inherently unsorted. You can get all the keys, sort the key array and then walk over the dictionary according to the sorted keys. (NSDictionary has several convenience methods for this use case that I didn’t know about, see Anurag’s answer.)
Your case is a bit more complex, one way to solve it is to introduce a temporary dictionary mapping ages to names. But if you’re only after the minimum and maximum ages, just iterate over all persons and keep track of the maximum & minimum ages and names:
NSString *oldestName = nil;
float maxAge = -1;
for (NSString *name in [persons allKeys]) {
NSDictionary *info = persons[name];
float age = [info[#"age"] floatValue];
if (age > maxAge) {
oldestName = info[#"nick"];
maxAge = age;
}
}
And if we get back to the idea of sorting the dictionary, this could work:
NSArray *peopleByAge = [people keysSortedByValueUsingComparator:^(id a, id b) {
// Again, see Anurag’s answer for a more concise
// solution using the compare: method on NSNumbers.
float ageA = [a objectForKey:#"age"];
float ageB = [b objectForKey:#"age"];
return (ageA > ageB) ? NSOrderedDescending
: (ageB > ageA) ? NSOrderedAscending
: NSOrderedSame;
}];
As #Zoul said the standard NSDictionary is unsorted.
To sort it you can use an array, and I do things like that
//the dictionary is called dict : in my case it is loaded from a plist file
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
//make a dicoArray that is sorted so the results are sorted
NSArray *dicoArray = [[dict allKeys] sortedArrayUsingComparator:^(id firstObject, id secondObject) {
return [((NSString *)firstObject) compare:((NSString *)secondObject) options:NSNumericSearch];
}];
check the help for all the sort options. In the presented case the dictionary is sorted with keys treated as numeric value (which was the case for me).
If you need to sort another way the list of sort possibilities is
enum {
NSCaseInsensitiveSearch = 1,
NSLiteralSearch = 2,
NSBackwardsSearch = 4,
NSAnchoredSearch = 8,
NSNumericSearch = 64,
NSDiacriticInsensitiveSearch = 128,
NSWidthInsensitiveSearch = 256,
NSForcedOrderingSearch = 512,
NSRegularExpressionSearch = 1024
};
In iOS 9.2
// Dictionary of NSNumbers
NSDictionary * phoneNumbersDict = #{#"400-234-090":67,#"701-080-080":150};
// In Ascending Order
NSArray * keysArraySortedByValue = [phoneNumbersDict keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 compare:obj2];
}];
// In Descending Order
NSArray * keysArraySortedByValue = [phoneNumbersDict keysSortedByValueUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj2 compare:obj1];
}];
Here is the enum for NSComparisonResults.
enum {
NSOrderedAscending = -1,
NSOrderedSame,
NSOrderedDescending
};
typedef NSInteger NSComparisonResult;
Look at the NSDictionary's method that returns keys sorted by a selector. There are more than one such method. You get an array of sorted keys, then access the first and last and have your youngest and oldest person.
I have an NSArray containing several NSDictionary instances. Each NSDictionary has, among other fields, an object named rating and one numberOfVotes(both are int). How can I sort the array so it gets sorted by rating/numberOfVotes? More generically, can I sort it by doing an operation like mentioned above? Or would it be better to just add another object to each NSDictionary with the value of each operation and then sort by that?
Thanks
EDIT - I have added the following. Still not sorting properly. One question: Should this work for more than 2 objects in my array. (The number of objects will vary)
[sortedArray sortedArrayUsingComparator:^NSComparisonResult(id dict1, id dict2)
{
MyObj *obj1 = (MyObj *)dict1;
MyObj *obj2 = (MyObj *)dict2;
int rating1 = obj1.rating.intValue;
int rating2 = obj2.rating.intValue;
int number1 = obj1.number_of_votes.intValue;
int number2 = obj2.number_of_votes.intValue;
double key1 = ((double)rating1)/number1;
double key2 = ((double)rating2)/number2;
if (key1 < key2)
{
return NSOrderedDescending;
}
if (key2 < key1)
{
return NSOrderedAscending;
}
return NSOrderedSame;
}];
You can define a custom comparator to use a composite sorting key of the kind that you are looking for. If there is no good reason to have that sorting key in the dictionary, other than performing the sort, do not add an item to the dictionary.
array = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
int rating1 = [[obj1 objectForKey:#"rating"] intValue];
int numberOfVotes1 = [[obj1 objectForKey:#"numberOfVotes"] intValue];
int rating2 = [[obj2 objectForKey:#"rating"] intValue];
int numberOfVotes2 = [[obj2 objectForKey:#"numberOfVotes"] intValue];
double key1 = ((double)rating1)/numberOfVotes1;
double key2 = ((double)rating2)/numberOfVotes2;
if (key1 > key2) {
return (NSComparisonResult)NSOrderedDescending;
}
if (key1 < key2) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Note: The sortedArrayUsingComparator: method does not sort the array in place; instead, it returns a sorted copy. If you would like an in-place sorting, use NSMutableArray.
The adequate way would be using blocks like this
NSArray *sortedArray;
sortedArray = [unsortedArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
float first = [[(NSDictionary*)a objectForKey:#"rating"] floatValue]/
[[(NSDictionary*)a objectForKey:#"numberOfVotes"] floatValue];
float second = [[(NSDictionary*)a objectForKey:#"rating"] floatValue]/
[[(NSDictionary*)a objectForKey:#"numberOfVotes"] floatValue];
return [[NSNumber numberWithFloat:first] compare: [NSNumber numberWithFloat:second]];
}];
you may find more ways to compare:
How to sort an NSMutableArray with custom objects in it?
The family of methods you seek are prefixed with sortedArray.
For a function (which is one of multiple options), see: -[NSArray sortedArrayUsingFunction:context:].
Edit: If you want to do rating divided by numberOfVotes, you will have to sort by a function or comparator block or replace the dictionary with a model object which has a method to calculate rating / numberOfVotes.
Use NSSortDescriptor.
NSArray *sorted = [array sortedArrayUsingDescriptors:[NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:#"rating" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"numberOfVotes" ascending:YES],
nil]
];
Filtering data is one of the essential tasks in computing.
You can sort it several ways.
NSPredicate
You can see examples here
NSSortDescriptor
You can see examples here
Regular Expressions
Set Operations
Key-Value Coding
Ideally, you should substitute NSDictionary by a custom object, with properties like rating and numberOfVotes. Then, you could declare a method compare: on this custom object and use as the selector to sort the array.
You can use a block code to sort the objects but abstracting the implementation into a custom object is much cleaner.
you can use NSSortDescriptor here for sorting for example your array that containing several NSDictionnaries name is dataArray.
NSSortDescriptor * descriptor = [[NSSortDescriptor alloc] initWithKey:rating
ascending:YES];
NSArray * keyArrray = [NSArray arrayWithObjects:descriptor, nil];
NSArray * sortedArray = [dataArray sortedArrayUsingDescriptors:keyArrray];
With the help of NSSortDescriptor we can sort any data type key value pair.
I know there are many topics with similar issues, but I have not been able to find a topic addressing my question.
I want to store a plist of highscores.
Every entry of highscores must have two elements
an NSString* and an int.
I want to store the top 20 high scores (pairs of strings and ints) and do that in a plist.
I start with:
NSMutableArray *arr = [[NSMutableArray alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:#"Mylist" ofType:#"plist"]];
I want the item 0 of the array to be a dictionary, where I can insert key value pairs of
(string, int)
How do I do that?
You can always call [arr addObject:score];, sort it, and remove the final item until there are 10.
To sort:
[arr sortUsingComparator:^(id firstObject, id secondObject) {
NSDictionary *firstDict = (NSDictionary *)firstObject;
NSDictionary *secondDict = (NSDictionary *)secondObject;
int firstScore = [[firstDict objectForKey:#"score"] intValue];
int secondScore = [[secondDict objectForKey:#"score"] intValue];
return firstScore < secondScore ? NSOrderedAscending : firstScore > secondScore : NSOrderedDescending : NSOrderedSame;
}];
If you want the scores to be the other way around, change the '>' to '<' and vice-versa. To keep the list down to 10:
while ([arr count] > 10) {
[arr removeLastObject];
}
You may have to sort when you load from your plist. For 10 scores the performance hit will be minimal, so I suggest you do it just in case.
Property List Serialization
You will want to make notice of: the mutability option, as your method probably returns immutable arrays...
storing in a plist is done with the writeToFile:... or writeToURL:... methods
[arr insertObject:[NSMutableDictionary dictionary] atIndex:0];
Suppose I have 4 integers.
int a = 4;
int b = 2;
int c = 4;
int d = 1;
How can I sort these integers from smallest to biggest. The output needs to be something like this: d, b, a, c Most methods of sorting only give me the value of the sorted integer. I need to know the name.
Edit: Well, I'm writing an AI algorithm. I have 4 ints that store direction priority. (If the AI comes into a wall, it chooses the next best direction). So, I need to find the lowest int, and if the AI can't move that way, I choose the second to lowest etc.
There appears to be some confusion here; in your example a is not the "name" for the value 4, it is the name of an integer variable which currently contains 4. In other words "a" is not part of the data of your program.
What I assume you mean is you have name/value pairs which you wish to sort using the value as key. A common way to do this is to define a type for your pair, create a collection, and sort the collection.
In plain C you can declare:
typedef struct
{
char *name;
int value;
} MyPair;
You can create an array of these and sort it using standard C functions for array sorting, using just the value field as the key.
In Objective-C you can declare a class for your pair:
#interface MyPair : NSObject
{
NSString *name;
int value;
}
// methods/properties
#end
You can create an NSMutableArray of instances of MyPair and then sort the array, again you just use the value property (or instance variable) when doing the comparisons for the sort algorithm.
There are other variations of course. Once sorted you can iterate through the sorted array and display the name field/property.
Here is an objective-c approach. Unfortunately, you will not have the fun of writing the AI portion, the sorting is built into the libraries already.
int north = 1, south = 3, east = 2, west =4;
NSDictionary * nDict = [NSDictionary dictionaryWithObjectsAndKeys:#"north", #"name", [NSNumber numberWithInt:north], #"value", nil];
NSDictionary * sDict = [NSDictionary dictionaryWithObjectsAndKeys:#"south", #"name", [NSNumber numberWithInt:south], #"value", nil];
NSDictionary * eDict = [NSDictionary dictionaryWithObjectsAndKeys:#"east", #"name", [NSNumber numberWithInt:east], #"value", nil];
NSDictionary * wDict = [NSDictionary dictionaryWithObjectsAndKeys:#"west", #"name", [NSNumber numberWithInt:west], #"value", nil];
NSArray * toBeSorted = [NSArray arrayWithObjects:nDict,sDict,eDict,wDict,nil];
NSArray * sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"value" ascending:NO]];
NSArray * sorted = [toBeSorted sortedArrayUsingDescriptors:sortDescriptors];
NSLog(#"sorted %#", sorted);
Output
2012-01-23 19:50:21.079 TestEnvironment[19792:207] sorted (
{
name = west;
value = 4;
},
{
name = south;
value = 3;
},
{
name = east;
value = 2;
},
{
name = north;
value = 1;
}
)
Now you can check the highest priority by
NSString * highestPriority = [[sorted objectAtIndex:0] objectForKey:#"name"];
Now you have some classes you can look up (NSArray, NSDictionary, NSSortDescriptor, NSNumber)
You've tagged this Objective-C, but you haven't written anything that suggests using Objective-C. If you want to use Objective-C, I'd put the elements into an NSMutableArray (they'll need to be converted to NSNumbers to do that), and have the array sort them, as seen here.
If you just want to put them into a straight C array, you could sort them using heapsort (), qsort(), or mergesort().