Objective-C- Getting the object with the greatest property value - objective-c

I have a Car class. It's got a property called enginePower. On the other hand I have an NSArray that contains more than 50 car objects. How would I select the car with the highest enginePower which at the same time is less than the given value. To be more clear:
BMW X5-345 hp
Audi A3-200 hp
BMW 525i -225 hp
VW Passat CC-175 hp
.....
Now, from this array of cars when I ask for the car with the highest enginePower which is less than 320 it should give me BMW 525i. Is it possible to achieve. I mean is there a nice and easy way or does it need lots of lines of code?

The answer provided by #Jasarien would work just fine, but if you want to avoid writing the code to loop through you might try something like:
- (Car *)getHighestPowerFromCars:(NSArray *)carArray atLeast:(NSNumber *)minPower {
NSArray *descriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"enginePower" ascending:NO]];
NSArray *sortedArray = [carArray sortedArrayUsingDescriptors:descriptors];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"enginePower >= %#", minPower];
NSArray *filteredArray = [sortedArray filteredArrayUsingPredicate:predicate];
return [filteredArray objectAtIndex:0];
}

Sort the array using the engine power property, then loop through the array until you find an enginePower value that is greater than the one specified. Then take the value of the object in currentArrayIndex - 1.
You can do this by using -compareUsingSelector: on an NSMutableArray or -sortedArrayUsingSelector: on an NSArray (which will return a new, sorted, autoreleased array).
You can create a method on your Car class named something like -compareUsingEnginePower: which will take another Car object as the parameter.
Then you can compare the two enginePower properties and return one of NSOrderedSame, NSOrderedAscending or NSOrderedDescending.

Straightforward iteration through the array, looking only at cars below the power limit, and picking the one with the highest power:
— (Car *)getHighestPowerFromCars:(NSArray *)carArray lessThan:(NSNumber *)maxPower
{
NSNumber* highest = nil;
Car* highestCar = nil;
for (Car* car in carArray)
{
NSNumber* power = car.enginePower;
if ([power compare:maxPower] == NSOrderedAscending])
{
if (highest == nil || [power compare:highest] == NSOrderedDescending])
{
highest = power;
highestCar = car;
}
}
}
return highestCar;
}
But there isn't any reason really why enginePower should be an NSNumber and not for example a double. Assuming that the power is never negative:
— (Car *)getHighestPowerFromCars:(NSArray *)carArray lessThan:(double)maxPower
{
double highestPower = -1.0;
Car* highestCar = nil;
for (Car* car in carArray)
{
double power = car.enginePower;
if (power < maxPower && power > highestPower))
{
highestPower = power;
highestCar = car;
}
}
return highestCar;
}
But then sorting and predicates are soooo fast...

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

KVC - Filter Sets

I have a simple data model (Core Data), but here is a stripped down version of it:
Account
----------------------------------------------------
NSSet<Transaction> transactions
Transaction
----------------------------------------------------
Account account
NSNumber amount
NSNumber type ( type ∈ ( 0->'Credit', 1->'Debit' ) )
I want to get an account's total balance, however, I don't want to mark a debit as a negative number. I just want it to be an amount with a type of debit.
Currently, before I added the debits in, I could get the balance via this function in Account:
- (NSNumber *)currentBalance
{
return [self.transactions valueForKeyPath:#"#sum.amount"];
}
Obviously, that will only work if I make debits negative. Is there an elegant solution here, to perhaps filter the set into credits and debits, sum those, and perform a difference? Will that maintain accuracy? I've looked into NSPredicate, but I'm not sure how exactly to proceed.
Here is what my research has led me to:
- (NSNumber *)currentBalance
{
// credits
NSPredicate *creditPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [((Transaction *)evaluatedObject).type intValue] == kTransactionTypeCredit;
}];
NSSet *credits = [self.transactions filteredSetUsingPredicate:creditPredicate];
NSDecimalNumber *totalCredits = [credits valueForKeyPath:#"#sum.amount"];
// debits
NSPredicate *debitPredicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [((Transaction *)evaluatedObject).type intValue] == kTransactionTypeDebit;
}];
NSSet *debits = [self.transactions filteredSetUsingPredicate:debitPredicate];
NSDecimalNumber *totalDebits = [debits valueForKeyPath:#"#sum.amount"];
// do the math
return [totalCredits decimalNumberBySubtracting:totalDebits];
}
Whilst it works, it seems like an excessive amount of work. Is it the best way?

Objective-C Sorting an Array by the values of an inner Array

I want to sort a NSMutableArray which has NSArrays with respect to the value at the index 1 of the NSArray. I'll try to draw a picture of arrays.
NSMutableArray *ultimateHighscoreArray = {
( (NSString) userName, (double) score, (int) numOfCorrectAnswers ) ,
( John , 4.5 , 3 ) ,
( Terry , 7.5 , 1) ,
... }
The first array within the NSMutableArray is an example which shows how examples are located. Second and third are how the values actually are. So, what I want is to sort these arrays having the array containing higher value at first index to go higher up in the ranking. For this example the array that has 7.5 which is Terry Array should go before the one that has 4.5 . I want the results to be held in a NSMutableArray in decreasing order. Thank you.
This should do it:
[ultimateHighscoreArray sortUsingComparator:^(id obj1, id obj2) {
NSNumber *score1 = [obj1 objectAtIndex:1];
NSNumber *score2 = [obj2 objectAtIndex:2];
// Reverse the comparison here (compare score2 to score1)
// in order to get a descending order
return [score2 compare:score1];
}];
A general advice: your data structure would be clearer if it were an array of NSDictionary instances or even an array of custom objects (e.g., your custom Score class). In those cases, you could also use NSSortDescriptor to sort the array, which would result in cleaner, easier-to-read code.
The sortUsingComparator: method allows you to sort an array using a block, e.g.:
[ultimateHighscoreArray sortUsingComparator:^(id obj1, id obj2) {
return [[obj1 objectAtIndex:1] compare:[obj2 objectAtIndex:1]];
}];
For clarity, it would probably be better to use an array of dictionaries (or instances of a custom class) for this data structure.
You can write a custom comparator like this :)
[ultimateHighscoreArray sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSArray *arrayA = (NSArray *)obj1;
NSArray *arrayB = (NSArray *)obj2;
double scoreA = [arrayA objectAtIndex:1];
double scoreB = [arrayB objectAtIndex:1];
if (scoreA > scoreB) {
return NSOrderedDescending;
} else if (scoreA < scoreB) {
return NSOrderedAscending;
} else {
return NSOrderedSame;
}
}];

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)