nicklockwood / Expression

A cross-platform Swift library for evaluating mathematical expressions at runtime
MIT License
829 stars 51 forks source link

AnyExpression using array variables can't be evaluated with error 0 #34

Closed triple7 closed 3 years ago

triple7 commented 3 years ago

Hello,

Maybe I haven't read the readme well enough, but it mentions that for AnyExpression, I can just add a variable in the dictionary of constants as below, and explicitly cast the return. I'm not sure if the evaluation can consider say an array with a constant term (X + 1 where X say is an array), but I also tried something like X + X which would normally add the elements of X individually, like a numpy array addition. Maybe I'm being overtly optimistic with what Expression can do?

func expression(_ X: [CGFloat], _ expression: String) {
    let x = X.map{Double($0)}
    let expression = expression.lowercased()
    let eval = AnyExpression(expression, constants: ["x": x])
    do {
    let result:[CGFloat] = try eval.evaluate()
    print(result)
    } catch let error {
        print(error.localizedDescription)
    }
}
nicklockwood commented 3 years ago

AnyExpression doesn't have built-in member-wise operators for arrays. The built-in + implementation concatenates arrays rather than summing the elements (like in Swift).

You can provide your own implementation for operators though. This is a slightly advanced usage, but the following code should do what you want:

func expression(_ X: [CGFloat], _ expression: String) {
    let x = X.map { Double($0) }
    let eval = AnyExpression(
        Expression.parse(expression.lowercased()),
        impureSymbols: { _ in nil },
        pureSymbols: { symbol in
            switch symbol {
            case .variable("x"):
                return { _ in x }
            case let symbol:
                guard let scalarFn = Expression.mathSymbols[symbol] else {
                    return nil
                }
                return { args in
                    guard args.count == 2, let arrays = args as? [[Double]] else {
                        guard let args = args as? [Double] else {
                            throw Expression.Error.typeMismatch(symbol, args)
                        }
                        return try scalarFn(args)
                    }
                    return try zip(arrays[0], arrays[1]).map { try scalarFn([$0, $1]) }
                }
            }
        }
    )
    do {
        let result: [CGFloat] = try eval.evaluate()
        print(result)
    } catch {
        print(error)
    }
}