How to use NSComparator? - objective-c

I would like to know if the below question is possible using NSComparator or not?
I have two arrays; both hold data models. I have a property named rank in the data model. Now I want to compare both arrays and want to know if one of them holds higher ranked data models.
If Yes I would like to get NSComparisonResult = NSOrderedAscending.
By the way I'm using another approach here: is "total of each data Model's rank in array and if the total is greater than second array's data Model's total rank."

Yes, it would look something like this:
NSArray *someArray = /* however you get an array */
NSArray *sortedArray = [someArray sortedArrayUsingComparator:^(id obj1, id obj2) {
NSNumber *rank1 = [obj1 valueForKeyPath:#"#sum.rank"];
NSNumber *rank2 = [obj2 valueForKeyPath:#"#sum.rank"];
return (NSComparisonResult)[rank1 compare:rank2];
}];
(updated to show actually using the comparator)

Related

Finding minimum and maximum values in a nested NSDictionary

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.

Sort an NSDictionary into an NSMutableArray but keep value and key?

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];
}];

best way to populate NSArray in this algorithm

I intend to make a program that does the following:
Create an NSArray populated with numbers from 1 to 100,000.
Loop over some code that deletes certain elements of the NSArray when certain conditions are met.
Store the resultant NSArray.
However the above steps will also be looped over many times and so I need a fast way of making this NSArray that has 100,000 number elements.
So what is the fastest way of doing it?
Is there an alternative to iteratively populating an Array using a for loop? Such as an NSArray method that could do this quickly for me?
Or perhaps I could make the NSArray with the 100,000 numbers by any means the first time. And then create every new NSArray (for step 1) by using method arraywithArray? (is it quicker way of doing it?)
Or perhaps you have something completely different in mind that will achieve what I want.
edit: replace NSArray with NSMutableArray in above post
It is difficult to tell in advance which method will be the fastest. I like the block based functions, e.g.
NSMutableArray *array = ...; // your mutable array
NSIndexSet *toBeRemoved = [array indexesOfObjectsPassingTest:^BOOL(NSNumber *num, NSUInteger idx, BOOL *stop) {
// Block is called for each number "num" in the array.
// return YES if the element should be removed and NO otherwise;
}];
[array removeObjectsAtIndexes:toBeRemoved];
You should probably start with a correctly working algorithm and then use Instruments for profiling.
You may want to look at NSMutableIndexSet. It is designed to efficiently store ranges of numbers.
You can initialize it like this:
NSMutableIndexSet *set = [[NSMutableIndexSet alloc]
initWithIndexesInRange:NSMakeRange(1, 100000)];
Then you can remove, for example, 123 from it like this:
[set removeIndex:123];
Or you can remove 400 through 409 like this:
[set removeIndexesInRange:NSMakeRange(400, 10)];
You can iterate through all of the remaining indexes in the set like this:
[set enumerateIndexesUsingBlock:^(NSUInteger i, BOOL *stop) {
NSLog(#"set still includes %lu", (unsigned long)i);
}];
or, more efficiently, like this:
[set enumerateRangesUsingBlock:^(NSRange range, BOOL *stop) {
NSLog(#"set still includes %lu indexes starting at %lu",
(unsigned long)range.length, (unsigned long)range.location);
}];
I'm quite certain it will be fastest to create the array using a c array, then creating an NSArray from that (benchmark coming soon). Depending on how you want to delete the numbers, it may be fastest to do that in the initial loop:
const int max_num = 100000;
...
id *nums = malloc(max_num * sizeof(*nums));
int c = 0;
for(int i = 1; i <= max_num; i++) {
if(!should_skip(i)) nums[c++] = #(i);
}
NSArray *nsa = [NSArray arrayWithObjects:nums count:c];
First benchmark was somewhat surprising. For 100M objects:
NSArray alloc init: 8.6s
NSArray alloc initWithCapacity: 8.6s
id *nums: 6.4s
So an array is faster, but not by as much as I expected.
You can use fast enumeration to search through the array.
for(NSNumber item in myArrayOfNumbers)
{
If(some condition)
{
NSLog(#"Found an Item: %#",item);
}
}
You might want to reconsider what you are doing here. Ask yourself why you want such an array. If your goal is to manipulate an arbitrarily large collection of integers, you'll likely prefer to use NSIndexSet (and its mutable counterpart).
If you really want to manipulate a NSArray in the most efficient way, you will want to implement a dedicated subclass that is especially optimized for this kind of job.

How to sort a search result using NSPredicate by relevance?

I have an array of products classes. Each product class has multiple properties such as name, functions, ingredients, etc.
I developed a simple search algorithm using NSPredicate that allows me to search for a specific string in the array of products.
In a nutshell, the predicate looks like: "string in product.name OR string in product.ingredients OR string in product.functions...".
I can get the result array correctly, i.e. list of products that contains the search string in one of its properties.
BUT, my question is, how to sort this result by RELEVANCE? i.e. if a search string hits multiple times in a product class, naturally I want to put this product at the top of the search result array?
Thanks!
I have coded an example that might answer your question. I am not saying this is the best implementation, but it works.
NSArray *strings = #[#"A",#"B",#"C",#"A",#"D",#"E",#"B",#"B",#"B"];
NSMutableDictionary *sortedDictionary = [NSMutableDictionary dictionary];
for (NSString *string in strings) {
NSNumber *count = [sortedDictionary objectForKey:string];
if (count) {
count = #(count.integerValue + 1);
} else {
count = #(1);
}
[sortedDictionary setObject:count forKey:string];
}
NSArray *result = [sortedDictionary keysSortedByValueWithOptions:NSNumericSearch usingComparator:^NSComparisonResult(id obj1, id obj2) {
return [obj2 compare:obj1];
}];
NSLog(#"%#", result); // (B,A,D,E,C)

Comparing 3 arrays of various NSObject Subclasses

I have an NSDictionary which contains 3 NSArrays,
- posts
- comments
- likes.
And in each array are consistent NSObject Subclasses:
- Post
- Comment
- Like
Usually, I would just put all objects from these three arrays into one array and compare them using the same variable which they all contain, but in this case, Posts has the variable dateOfUpload and Like and Comment have the same variable, date.
How can I compare the objects from these three arrays using the variable date and dateOfUpload to create one big array of all objects in descending date?
I would make them all implement method like:
-(NSComparsionResult) compareByDate : (NSObject *) obj;
Of course you'll need to implement this in different way in each class.
Then make one big array from all the tree arrays and call
[myArray sortUsingSelector:#selector(compareByDate:)];
Another way to do this is to add all your objects in one big array and sort using a block as follows:
// Create the array with all the objects
NSMutableArray *stuff = [NSMutableArray arrayWithArray:posts.allValues];
[stuff addObjectsFromArray:comments.allValues];
[stuff addObjectsFromArray:likes.allValues];
// Sort it by using a block
NSArray *sortedStuff = [stuff sortedArrayUsingComparator:^(id obj1, id obj2) {
NSDate *date1 = [obj1 respondsToSelector:#selector(date)]? [obj1 date] : [obj1 dateOfUpload];
NSDate *date2 = [obj2 respondsToSelector:#selector(date)]? [obj2 date] : [obj2 dateOfUpload];
return [date2 compare:date1]; // Objects are reversed to get descending order
}];