Task:
I am planning to parse a formula string in NSPredicate and to replace variables in the string by their numeric values. The variables are names for properties of existing object instances in my data model, for instance I have a class “company” with an instance “Apple Corp.”
Set-up:
My formula would like look like this: “Profitability_2011_in% = [Profit 2011] / [Revenue 2011]”
The instance “Apple Corp” would have the following properties:
Revenue 2009 = 10, Revenue 2010 = 20, Revenue 2011 = 30,
Profit 2009 = 5, Profit 2010 = 10, Profit 2011 = 20.
Hence, the formula would yield 20 / 30 = 67%.
Variables are usually two-dimensional, for instance defined by “profit” as the financial statement item and “year” (for instance 2011).
The variables are enclosed in [ ] and the dimensions are separated by ” ” (whitespace).
How I would do it
My implementation would begin with NSRegularExpression’s matchesInString:options:range: to get an array of all variables in the formula (Profit 2011, Revenue 2011) and then construct an NSDictionary (key = variable name) out of this array by querying my data model.
What do you think?
- Is there a better way to do it in your view?
- In the formula, how would you replace the variables by their values?
- How would you parse the formula?
Thank you!!
Yes, you can do this. This falls under the category of “Using
NSPredicatefor things for which it was not intended”, but will work just fine.You’ll need to replace your variables with a single word that start with a
$, since that’s howNSPredicatedenotes variables:However you want to do that, great.
NSRegularExpressionis a fine way to do that.Once you do that, you’ll have something like this:
You can then pop this through
+predicateWithFormat:. You’ll get back anNSComparisonPredicate. The-leftExpressionwill be of typeNSVariableExpressionType, and the-rightExpressionwill be of typeNSFunctionExpressionType.This is where things start to get hairy. If you were to
-evaluteWithObject:substitutionVariables:, you’d simply get back aYESorNOvalue, since a predicate is simply a statement that evaluates to true or false. I haven’t explored how you could just evaluate one side (in this case, the-rightExpression), but it’s possible that-[NSExpression expressionValueWithObject:context:]might help you. I don’t know, because I’m not sure what that “context” parameter is for. It doesn’t seem like it’s a substitution dictionary, but I could be wrong.So if that doesn’t work (and I have no idea if it will or not), you could use my parser: DDMathParser. It has a parser, similar to
NSPredicate‘s parser, but is specifically tuned for parsing and evaluating mathematical expressions. In your case, you’d do:The documentation for
DDMathParseris quite extensive, and it can do quite a bit.edit Dynamic variable resolution
I just pushed a change that allows DDMathParser to resolve functions dynamically. It’s important to understand that a function is different from a variable. A function is evaluated, whereas a variable is simply substituted. However, the change only does dynamic resolution for functions, not variables. That’s ok, because DDMathParser has this neat thing called argumentless functions.
An argumentless function is a function name that’s not followed by an opening parenthesis. For convenience, it’s inserted for you. This means that
@"pi"is correctly parsed as@"pi()"(since the constant for π is implemented as a function).In your case, you can do this:
Instead of regexing your string to make variables, simply use the names of the terms:
This will be parsed as if you had entered:
You can the set up your
DDMathEvaluatorobject with a function resolver. There are two examples of this in theDDMathParserrepository:Once you implement a resolver function, you can forego having to package all your variables up into a dictionary.