Currently using NSExpression in swift to take a string formula from a database and calculate it.
Have a range of inputs (buttons / textfields etc) that when inputted hold an array of values for each input. Then I have a string formula held in a database with '$' as identification of each value.
Example
Inputs in array may be: 5,3,2,6
String formula is: (($*$)-$)/$
Code then substitutes the $ in order to the input array so produces string: ((5*3)-2)/6
This is used by NSExpression to calculate. Works fine.
let formulaArray = formula.components(separatedBy: "$")
var totalScoreFormula = ""
let sep = 0.0
let explandedScoreArrayDouble = Array(scoreArrayDouble.map { [$0] }.joined(separator: [sep]))
for i in 0..<formulaArray.count {
if (i == 0){totalScoreFormula = "(" + String(explandedScoreArrayDouble[i])}
else if (formulaArray[i] == "") {totalScoreFormula = totalScoreFormula + String(explandedScoreArrayDouble[i])}
else {totalScoreFormula = totalScoreFormula + formulaArray[i]}
}
let mathExpression = NSExpression(format: totalScoreFormula)
totalScoreDouble = (mathExpression.expressionValue(with: nil, context: nil) as? Double)!
But I have more complicated formula that I would like to use that in essence contain IF statements, for example:
If first in array value = 1 then formula is (($*$)-$)/$
however if value = 2 then formula should be (($*$)+$)*$
I would like to hold this IF statement in the string on the database and not in the code.
So for example database string something like: IF $==1: use(($*$)-$)/$, ELSE (($*$)+$)*$
Also I have a few formula that contain multiple IF or CASE aspects: For example: If Age <5 then this formula for male and this for female. If >5 then this this formula for male and this for female....etc
Wondered if use of TERNARY may be answer:
NSExpression(format: "TERNARY($ == 1, '(($*$)-$)/$', '(($*$)+$)*$')")
Struggling to get this to work and can't see how I would do nestled calculations via one string.
Any advice, different approach?
The NSExpression
format string works with property names, like NSPredicate
. The property names are used to get the values from the object against which the expression is evaluated. This object can be a dictionary (or a KVC compliant class). So instead of an array of values and lots of $
s, use a dictionary and keys. This way you can use a value multiple times. The formulas are longer but they are more readable. Example:
let values1 = ["MALE": true, "AGE": 8, "POINTS": 5, "BONUS": 10] as [String : Any]
let values2 = ["MALE": false, "AGE": 9, "POINTS": 8, "BONUS": 5] as [String : Any]
let values3 = ["MALE": true, "AGE": 3, "POINTS": 11, "BONUS": 0] as [String : Any]
let expression = NSExpression(format: "TERNARY(AGE > 5, TERNARY(MALE = TRUE, POINTS * 2, POINTS * 2 + 1), POINTS * 3)")
print("Result 1: \(expression.expressionValue(with: values1, context: nil))")
print("Result 2: \(expression.expressionValue(with: values2, context: nil))")
print("Result 3: \(expression.expressionValue(with: values3, context: nil))")
prints
Result 1: Optional(10)
Result 2: Optional(17)
Result 3: Optional(33)
Note: If the expression format is incorrect then NSExpression(format:)
will throw an Objective-C NSException
.