Can I force NSExpression and expressionValue to accept Doubles instead of Ints?

I am trying to do math from a string.

When I turn a string into a math problem using NSExpression and then get the result using the Value expression, Swift assumes I want Integer. Consider these two examples of a playground:

let currentCalculation = "10 / 6" let currentExpression = NSExpression(format: currentCalculation) print(currentExpression) // 10 / 6 if let result = currentExpression.expressionValue(with: nil, context: nil) as? Double { print(result) // 1 } let anotherCalculation = "10.0 / 6.0" let anotherExpression = NSExpression(format: anotherCalculation) print(anotherExpression) // 10 / 6 if let result = anotherExpression.expressionValue(with: nil, context: nil) as? Double { print(result) // 1.666666667 } 

What should I do so that as a result I always get Double? I do not want to parse a string ahead of time.

Interestingly enough, the second example turns "another expression" into integers, but still returns the result of Double.

+3
source share
2 answers

You might be better off using a third-party expression parser / evaluator, such as DDMathParser . NSExpression quite limited and does not have the ability to force point evaluation.

If you want (or should) stick with NSExpression : Here is a possible solution (recursively) to replace all the value constants in the expression with their floating point value:

 extension NSExpression { func toFloatingPoint() -> NSExpression { switch expressionType { case .constantValue: if let value = constantValue as? NSNumber { return NSExpression(forConstantValue: NSNumber(value: value.doubleValue)) } case .function: let newArgs = arguments.map { $0.map { $0.toFloatingPoint() } } return NSExpression(forFunction: operand, selectorName: function, arguments: newArgs) case .conditional: return NSExpression(forConditional: predicate, trueExpression: self.true.toFloatingPoint(), falseExpression: self.false.toFloatingPoint()) case .unionSet: return NSExpression(forUnionSet: left.toFloatingPoint(), with: right.toFloatingPoint()) case .intersectSet: return NSExpression(forIntersectSet: left.toFloatingPoint(), with: right.toFloatingPoint()) case .minusSet: return NSExpression(forMinusSet: left.toFloatingPoint(), with: right.toFloatingPoint()) case .subquery: if let subQuery = collection as? NSExpression { return NSExpression(forSubquery: subQuery.toFloatingPoint(), usingIteratorVariable: variable, predicate: predicate) } case .aggregate: if let subExpressions = collection as? [NSExpression] { return NSExpression(forAggregate: subExpressions.map { $0.toFloatingPoint() }) } case .anyKey: fatalError("anyKey not yet implemented") case .block: fatalError("block not yet implemented") case .evaluatedObject, .variable, .keyPath: break // Nothing to do here } return self } } 

Example:

 let expression = NSExpression(format: "10/6+3/4") if let result = expression.toFloatingPoint().expressionValue(with: nil, context: nil) as? Double { print("result:", result) // 2.41666666666667 } 

This works with "simple" expressions using arithmetic operators and functions and some "advanced" types of expressions (unions, intersections, ...). Other conversions can be added if necessary.

+3
source

Here's a variation of Martin R's answer, which has two important changes:

  • It only converts arguments to division. Any other functions may still receive integer arguments.
  • It processes expressions like count({1,2,3,4,5}) / count({1,2}) , where the division arguments are not constant values.

code:

 import Foundation extension NSExpression { func toFloatingPointDivision() -> NSExpression { switch expressionType { case .function where function == "divide:by:": guard let args = arguments else { break } let newArgs = args.map({ arg -> NSExpression in if arg.expressionType == .constantValue { if let value = arg.constantValue as? Double { return NSExpression(forConstantValue: value) } else { return arg } } else { return NSExpression(block: { (object, arguments, context) in // NB: The type of `+[NSExpression expressionForBlock:arguments]` is incorrect. // It claims the arguments is an array of NSExpressions, but it not, it's // actually an array of the evaluated values. We can work around this by going // through NSArray. guard let arg = (arguments as NSArray).firstObject else { return NSNull() } return (arg as? Double) ?? arg }, arguments: [arg.toFloatingPointDivision()]) } }) return NSExpression(forFunction: operand, selectorName: function, arguments: newArgs) case .function: guard let args = arguments else { break } let newArgs = args.map({ $0.toFloatingPointDivision() }) return NSExpression(forFunction: operand, selectorName: function, arguments: newArgs) case .conditional: return NSExpression(forConditional: predicate, trueExpression: self.true.toFloatingPointDivision(), falseExpression: self.false.toFloatingPointDivision()) case .unionSet: return NSExpression(forUnionSet: left.toFloatingPointDivision(), with: right.toFloatingPointDivision()) case .intersectSet: return NSExpression(forIntersectSet: left.toFloatingPointDivision(), with: right.toFloatingPointDivision()) case .minusSet: return NSExpression(forMinusSet: left.toFloatingPointDivision(), with: right.toFloatingPointDivision()) case .subquery: if let subQuery = collection as? NSExpression { return NSExpression(forSubquery: subQuery.toFloatingPointDivision(), usingIteratorVariable: variable, predicate: predicate) } case .aggregate: if let subExpressions = collection as? [NSExpression] { return NSExpression(forAggregate: subExpressions.map({ $0.toFloatingPointDivision() })) } case .block: guard let args = arguments else { break } let newArgs = args.map({ $0.toFloatingPointDivision() }) return NSExpression(block: expressionBlock, arguments: newArgs) case .constantValue, .anyKey: break // Nothing to do here case .evaluatedObject, .variable, .keyPath: // FIXME: These should probably be wrapped in blocks like the one // used in the `.function` case. break } return self } } 
+1
source

Source: https://habr.com/ru/post/1272294/


All Articles