I am aware that there is a lot of questions about this topic, and i do apologize for that as i just cant get this to work for my NSMutableArray. I have problem to really understand the sorting and i have been reading documentation
I have an NSMutableArray with the following type of data:
Player name
score
Player name
score
Player name
score
...
It is a combination between a name and a score (NSNumber). I am trying to find a way to sort this based on the score. I have read a lot but i just do not get this to work, i also have problem to understand the sort concept. I have tried to sort the full array but...
I would very much appreciate if someone can give me a short, understandable, explanation of the sort scenario for this and also an example of how to sort this.
Edit: I changed to dictionary, selected the values and was thinking to sort the allObjects (stored as NSNumber in the dict) and then just select the key from the dict based on the sorted object.
NSArray *allPlayers = [playerResultInTheGame allKeys];
NSArray *allObjects = [playerResultInTheGame allValues];
NSLog(#"allPlayers: %#", allPlayers);
NSLog(#"allObjects: %#", allObjects);
NSMutableArray *sortedArray = [allObjects sortedArrayUsingSelector:#selector(Compare:)];
I get the following when i run this:
2011-01-16 21:10:08.417 XX[6640:207] playerResultInTheGame: {
Barnspelare = 3;
Vuxenspelare = 3;
}
2011-01-16 21:10:08.418 XX[6640:207] allPlayers: (
Barnspelare,
Vuxenspelare
)
2011-01-16 21:10:08.418 XX[6640:207] allObjects: (
3,
3
)
2011-01-16 21:10:08.419 XX[6640:207] -[NSCFNumber Compare:]: unrecognized selector sent to instance 0x5b26f10
2011-01-16 21:10:08.422 XX[6640:207] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSCFNumber Compare:]: unrecognized selector sent to instance 0x5b26f10'
Can someone please advice as i do not really understand this?
What you need to do here is to store your scores and player names in a dictionary. Use the player name as the key (unless the same player is included more than once) and the score as the value. Then, sorting the players is as easy as this:
NSDictionary *dict; // initialize dictionary with key/value pairs
NSArray *allPlayers = [dict allKeys];
Sort the allPlayers array however you want, then get the scores out of the dictionary.
Lets go.
1) You need to create array of dictionaries. 2) Then sort it. 3) Then create final array of strings and numbers from sorted array of dictionaries.
//for example you have this
NSArray *allPlayers = #[#"John",#"Carl",#"Elena",#"Anna"];
NSArray *allAges = #[#30, #25, #16, #21];
//created storage to sort results
NSMutableArray *sortedPlayers = [NSMutableArray new];
[allPlayers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
//I do not know if you have same count of player names and ages allways
//so just to prevent app crash check this situation
if (allAges.count <= idx){
*stop = YES;
return ;
}
[sortedPlayers addObject:#{
#"name":obj,
#"age":allAges[idx]
}];
}];
//let the sort begin
[sortedPlayers sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSNumber *age1 = obj1[#"age"];
NSNumber *age2 = obj2[#"age"];
//just leave comparing to the NSNumber object
return [age1 compare:age2];
}];
NSLog(#"Wow. It works!\n:%#",sortedPlayers);
//now you need the initial array with player\age values
NSMutableArray *result = [NSMutableArray new];
//append each pair to result
for (NSDictionary *obj in sortedPlayers){
NSString *name = obj[#"name"];
NSNumber *age = obj[#"age"];
[result addObject:name];
[result addObject:age];
}
NSLog(#"The final result is\n:%#",result);
This will produce the following result in console:
(
Elena,
16,
Anna,
21,
Carl,
25,
John,
30
)
Related
I have this snipped of code that results in an array with a whole bunch of "<null>" throughout and I need to figure out how to remove them. Obviously after smashing my head against the keyboard I'm asking for some help.
In my .h I have declared:
NSArray *sortedContacts;
NSArray *rawContacts;
And then in .m:
-(void) buildContacts {
ABAddressBook *addressBook = [ABAddressBook sharedAddressBook];
NSArray *contacts = [addressBook people];
rawContacts=contacts;
NSArray *firstNames = [rawContacts valueForKey:#"First"];
NSArray *lastNames = [rawContacts valueForKey:#"Last"];
NSArray *organization = [rawContacts valueForKey:#"Organization"];
NSMutableArray *fullNames = [NSMutableArray array];
for(int i = 0; i < [firstNames count]; i++)
{
NSString *fullName = [NSString stringWithFormat:#"%# %# %#",
[firstNames objectAtIndex:i],
[lastNames objectAtIndex:i],
[organization objectAtIndex:i]];
[fullNames addObject:fullName];
}
NSMutableArray *fullList = [[NSMutableArray alloc]initWithArray:fullNames];
[fullList removeObjectIdenticalTo: #"<null>"];
sortedContacts = [fullList sortedArrayUsingSelector:#selector(compare:)];
NSLog(#"%#",sortedContacts);
}
I've tried so many things that I just can't see the forest for the trees anymore.
The text <null> is how the singleton instance of NSNull describes itself. That is, it's what -[NSNull description] returns.
In turn, these NSNull objects are getting into your firstNames, lastNames, and organization arrays because that's what Key-Value Coding does when you call -valueForKey: on an array and some of the elements return nil when that message is forwarded on to them with the same key. That is, calling [rawContacts valueForKey:#"First"] causes NSArray to call [element valueForKey:#"First"] for each element in rawContacts and to put the result in the array it builds. But, since an array can't contain nil, if one of those elements returns nil from [element valueForKey:#"First"], an NSNull object is added in its place.
Then, you are formatting the string fullName from the corresponding elements of firstNames, lastNames, and organization. You need to check if any of those elements are NSNull using if ([value isKindOfClass:[NSNull class]]) and handling that. For instance, you might just skip that record. Or you might combine the available fields and leave out any unavailable ones.
In any case, none of the elements of fullList will be #"<null>" because formatting values into #"%# %# %#" can never result in that string. (It might be #"<null> <null> <null>" or something like that, but never just #"<null>".)
A quick look at your code suggests you cannot get any empty strings added to your array, (a) you add elements using:
[fullNames addObject:fullName];
and fullName is created using:
[NSString stringWithFormat:#"%# %# %#" ...
so even if the %#'s get replaced by nothing you'll still have 2 spaces...
Maybe this is why all the things you've tried fail, if you're looking for empty strings you won't find them.
(Addendum: Question now says you're looking for #"<null>", you won't get that either for the same reason - there is at least two spaces in your string.)
The simple answer to removing invalid entries in fullNames is not to add them in the first place. You are adding elements in a loop (for), and conditional logic (e.g. if) inside the loop to determine whether you have something valid to add - however you define "something valid" - and only add an item to fullNames if so.
HTH
I'm not really familiar with the AddressBook framework, however this might be what's causing the confusion:
The values you collect in your arrays firstNames, lastNames and organization can be of type NSString or NSNull. You have to do any null-checking within the for-loop, before the fullName-string is constructed.
Remove this useless line:
[fullList removeObjectIdenticalTo: #"<null>"];
And replace the contents of your for-loop with the following code:
for(int i = 0; i < [firstNames count]; i++)
{
NSString *firstName = [firstNames objectAtIndex:i];
NSString *lastName = [lastNames objectAtIndex:i];
NSString *org = [organization objectAtIndex:i];
NSMutableArray *namesArray = [NSMutableArray array];
if ([firstName isKindOfClass:[NSString class]])
[namesArray addObject:firstName];
if ([lastName isKindOfClass:[NSString class]])
[namesArray addObject:lastName];
if ([org isKindOfClass:[NSString class]])
[namesArray addObject:org];
if (namesArray.count > 0)
[fullNames addObject:[namesArray componentsJoinedByString:#" "]];
}
I have an NSMutableDictionary with a structure like:
Main Dictionary > Unknown Dictionary > Dictionaries 1,2,4,5,6...
My question is what is the best way to retrieve the Unknown Dictionary key and set it as a variable? This is what I've tried:
NSEnumerator *enumerator = [myMutableDict keyEnumerator];
id aKey = nil;
while ( (aKey = [enumerator nextObject]) != nil) {
id value = [myMutableDict objectForKey:aKey]; // changed to `aKey`
NSLog(#"%#: %#", aKey, value); // tip via rmaddy
}
What goes into objectForKey: if you don't know the name of the object in the key?
The other thought I had was to populate an NSArray, then pulling each of the keys out somehow.
for (NSString *object in myMutableDict)
myArray = [myArray arrayByAddingObject:MainDict];
}
If anyone can suggest a better way to get the object (unknown) from an NSMutableDictionary I'm interested to learn.
You can enumerate dictionaries like this:
NSDictionary * someDictionary = ... however you set your dictionary;
[someDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"Key: %#", key);
NSLog(#"Object: %#", obj);
}];
and set:
*stop = YES;
when you find the object you're looking for.
I'm not entirely sure if I understand your question correctly. I assume you have a "main" dictionary with exactly one (unknown) key that maps to another dictionary, which you want to retrieve. This would be a simple and concise way to do this:
NSDictionary *unknownDictionary = mainDictionary[mainDictionary.allKeys.firstObject];
(Yes, some people won't like the dot syntax here, but I find it easier to read in this case. You might also want to add some error checking, for the case that mainDictionary is empty etc.)
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'm a little confused: trying to take a list of player names and scores that I have in an NSDictionary, and sort them into score order (highest score first). I know I can't sort a Dictionary so I need to move the data into an array first, but in doing so, won't I have to lose one half of each dictionary key/value pair?
For example, let's say I have the following pairs:
Bill / 10000
John / 7500
Stan / 7500
Mark / 5000
If I go and take the scores out and sort them, then retrieve the keys later, won't John and Stan get mixed up since they had identical scores? Might it call one of them twice, etc?
I realise I can't sort the dictionary, but is there a smarter way to do this?
What you could do is to get a sorted array of your players based on their score, and then create a dictionary for each player and append them in another array. Something like this perhaps (I'm using the new literals syntax, use the old one if appropriate):
NSDictionary *scores = #{
#"Stan" : #7500,
#"Mark" : #5000,
#"John" : #7500,
#"Bill" : #10000
};
NSArray *sp = [scores keysSortedByValueUsingComparator:^(id obj1, id obj2){
return [obj2 compare:obj1];
}];
NSMutableArray *players = [NSMutableArray arrayWithCapacity:0];
for (NSString *p in sp) [players addObject:#{ #"name":p, #"score":scores[p] }];
Now your players array is:
(
{
name = Bill;
score = 10000;
},
{
name = John;
score = 7500;
},
{
name = Stan;
score = 7500;
},
{
name = Mark;
score = 5000;
}
)
PS. Is not a good idea to keep a dictionary where the keys are player names, consider that you got 2 different John players... what would happen then? Also a better solution imo would be to create a Player class and keep their data (score, name etc) as properties.
You'll have to create two arrays, one for the score and another for the player. The key point being that the player array is in the same order as the (sorted) score array. The implementation below assumes you keep the score using an NSNumber object, and is not particularly efficient as it sorts the values twice:
(untested)
NSDictionary *dict = ...; // key=player (NSString), value=score (NSNumber).
NSArray *scores = [[dict allValues] sortedArrayUsingComparator:^(id obj1, id obj2) {
return [(NSNumber *)obj2 compare:(NSNumber *)obj1];
}];
NSArray *players = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2) {
return [(NSNumber *)obj2 compare:(NSNumber *)obj1];
}];
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];