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?
Related
I'm doing an aggregation on entities and the code is :
NSPredicate *betweenInterval = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date < %#)", [interval.start value], [interval.end value]];
NSNumber * nbPoints = [OMSPointsEventEntity MR_aggregateOperation:#"sum:" onAttribute:#"nbPoints" withPredicate:betweenInterval];
return [nbPoints intValue];
And NbPoints is nil, I don't know why...
Hint : attribute NbPoints on my entity is a NSNumber should it be an int ?
I don't know about MR, but in standard Cord Data the aggregate functions have to be preceded by #. Maybe you should look this up in the MR documentation.
...MR_aggregateOperation:#"#sum"... // ???
If you can get an array of all the PointsEvent entities, you could calculate it yourself after the fetch:
NSNumber *sum = [fetchedObjects valueForKeyPath:#"#sum.nbPoints"];
As for the attribute data type, your setup seems correct: int32 or similar or float in the Core Data model, NSNumber in your NSManagedObject subclass.
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 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...
I have a very basic question. I would like to know if here is a built-in function in Objective-C or C to help me find if a specific number it's in a certain range. I know that this is probably easy question but still I didn't found an answer. On short terms, would like to avoid using multiple "if"s and "else"s for this test.
NSLocationInRange(c, NSMakeRange(a, (b - a)))
This returns a BOOL if c lies within a and b. However a,b and c must be unsigned int. And this is really not very good looking. So I guess it is far better to compare myself.
c >= a && c <= b
Same way you do in C, C++, Java, C#...
if (theNumber >= SOME_MINIMUM_VALUE && theNumber <= SOME_MAXIMUM_VALUE)
{
// ...
}
That's an "inclusive" range check. Should be easy to figure out how to do an "exclusive" check.
There's no inbuilt function, but there's also no way to do it that's more efficient than two conditions on any architecture I'm familiar with. Any function or macro will ultimately boil down to the same as above.
If you're worried that it'll be slow, then don't. Only worry about performance if you actually see that this is somehow a bottleneck. Premature optimization is not worth your time.
Add this method:
- (BOOL)float:(float)aFloat between:(float)minValue and:(float)maxValue {
if (aFloat >= minValue && aFloat <= maxValue) {
return YES;
} else {
return NO;
}
}
And use it like this:
float myFloat = 3.45;
if ([self float:myFloat between:3 and:4]) {
//Do something
}
This is a very easy solution.
Well I don't believe there is a built in function, but you could write your own in seconds.
simply
return (theInt >= min && theInt <= max);
Doing like so will be interpreted as : c >= a && c < b
NSLocationInRange(c, NSMakeRange(a, (b - a)))
If you want to compare like this : c >= a && c <= b
You should do like so :
NSLocationInRange(c, NSMakeRange(a, (b - a) + 1))
NSRange is a structure :
{
location, // Starting point
length // Length from starting point to end range
}
So if you want a range from 5 to 15 included -----> NSMakeRange(5, 11);
*If you are a bit disturbed, just count with your finger from 5 to 15 ;). That's how I do when I get to this point of the night when its hard to think :p
If you are working with Signed int, i advise you to create a Macro ;)
#define MY_AWESOME_MACRO_COMPARE(c, a, b) ((c >= a) && (c <= b))
Hope I helped :)
Cheers ;) !
In Objective-C you can use this simple check:
if (variable >= MINVALUE && variable <= MAXVALUE) {
NSLog(#" the number fits between two numbers");
} else {
NSLog(#" the number not fits between two numbers");
}
BOOL FitsBetweenTwoNumber = NSLocationInRange(variable, NSMakeRange(MINVALUE, (MAXVALUE - MINVALUE)));
if (FitsBetweenTwoNumber) {
NSLog(#" the number fits between two numbers");
} else {
NSLog(#" the number not fits between two numbers");
}
you can use NSPredicate to judge whether a array is in range,if you use CoreData do like:
NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", MINVALUE, MAXVALUE];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"EntityName" inManagedObjectContext:context]];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if (error == nil) {
if(results.count >0) {
NSLog(#" the number fits between two numbers");
} else {
NSLog(#" the number not fits between two numbers");
}
} else if (error) {
NSLog(#"%#", error);
}
if you don't use coreData ,you can also use NSPredicate :
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(date >= %#) AND (date <= %#)", MINVALUE, MAXVALUE];
NSArray *results = [#[#(variable)] filteredArrayUsingPredicate:predicate];
if(results.count >0) {
NSLog(#" the number fits between two numbers");
} else {
NSLog(#" the number not fits between two numbers");
}
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)