nicklockwood / Expression

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

max/min function with n number of argument #12

Closed varun-naharia closed 6 years ago

varun-naharia commented 6 years ago

Why there is no n number of argument for min/max function ?

nicklockwood commented 6 years ago

Expression doesn't support variadic functions right now, but I suppose it could in theory.

If you want a variadic min/max, you could add it yourself using a custom evaluator function, e.g.

func customExpression(_ string: String) -> Expression {
    return Expression(string) { symbol, args in
        switch symbol {
        case .function("min", arity: _):
            if args.count < 2 { return nil }
            var lowest = args[0]
            for arg in args where arg < lowest {
               lowest = arg
            }
            return lowest
        case .function("max", arity: _):
            if args.count < 2 { return nil }
            var highest = args[0]
            for arg in args where arg > highest {
                highest = arg
            }
            return highest
        default:
            return nil
        }
    }
}

let expression = customExpression("min(1, 3, 5)")
let result = try! expression.evaluate() // 1
varun-naharia commented 6 years ago

But I want that min/max function works as it working right now but with n number of argument, as now I am unable to fully understand the code you have written in expression.swift I can't add that functionality myself. Please help me to understand it, Another functionality that I want in this expression library is dynamic variable support like

1+5+num+min(5,8,6,3)

Where num is a variable with value 10 or something else, and it's changing dynamically on run time, and I have to solve the expression with updated value each time. So expression always look on screen as it is above but when we solve the expression it will put value from variable num to expression and solve it and it will better If variable can be highlighted

nicklockwood commented 6 years ago

The code I’ve given you implements the feature you asked for. You just have to use customExpression() instead of Expression() in your project.

nicklockwood commented 6 years ago

Expression also supports variables of the type you describe, but without seeing your project I don’t know what you would need to implement that in your code.

varun-naharia commented 6 years ago

Thanks for reply let me create demo may that will explain what I want to implement

varun-naharia commented 6 years ago

Please check this url https://github.com/varun-naharia/ExpressionDemo for demo app

I want to solve the expression written in textview, Please let me know what changes I have to do.

varun-naharia commented 6 years ago

Will you please help me I am waiting for your reply?

nicklockwood commented 6 years ago

@varun-naharia apologies, I’m away with my family for Christmas without access to a computer. I can’t quite work out what your example does from viewing the source code on my phone. I will take a look in a few days.

varun-naharia commented 6 years ago

Thanks, I'll be waiting for your reply, Merry Christmas to you and your family. Enjoy

nicklockwood commented 6 years ago

@varun-naharia OK, I've run the sample, but I'm still unclear on how it is supposed to work. The "Add variable" button appends a new variable to the expression, selected from a list - but how would the value of those variables get set?

nicklockwood commented 6 years ago

@varun-naharia anyway, regardless of the answer to that, I think this is what you need. Replace the textViewDidChange method with the following:

    func customExpression(_ string: String) -> Expression {
        return Expression(string) { symbol, args in
            switch symbol {
            case .function("min", arity: _):
                if args.count < 2 { return nil }
                var lowest = args[0]
                for arg in args where arg < lowest {
                    lowest = arg
                }
                return lowest
            case .function("max", arity: _):
                if args.count < 2 { return nil }
                var highest = args[0]
                for arg in args where arg > highest {
                    highest = arg
                }
                return highest
            case .variable("num1"):
                return Double(self.num1)
            case .variable("num2"):
                return Double(self.num2)
            default:
                return nil
            }
        }
    }

    func textViewDidChange(_ textView: UITextView) {
        if let text = inputField.text, text != "" {
            do {
                let result = try customExpression(text).evaluate()
                addOutput(String(format: "= %g", result), color: .black)
            } catch {
                addOutput("\(error)", color: .red)
            }
        }
    }

Hopefully it should be obvious how you can add additional variables.

varun-naharia commented 6 years ago

Your code is working as expected but one thing that is missing is I want add variable at run time, there is another lib on github named DDMathParser. If I use DDMathParser lib and replace the code

func textViewDidChange(_ textView: UITextView) {
        if let text = inputField.text, text != "" {
            do {
                let result = try inputField.text!.evaluate(using: .default, Substitutions(dictionaryLiteral: ("num1", num1),("num2", num2)))
                addOutput(String(format: "= %g", result), color: .black)
            } catch {
                addOutput("\(error)", color: .red)
            }
        }
    }

then it's replacing string with variable, Can it be done with your lib ? but by passing Dictionary ([String:Any]) because this dictionary can created easily at run time.

nicklockwood commented 6 years ago

@varun-naharia yes, Expression can do this, but your example code didn’t include a dictionary of variables. The reason I asked for a sample was so I could provide a solution tailored to your use case.

Here is a version that works with a dictionary of [String: Double] called “variables”:

    var variables: [String: Double] // your variables

    func customExpression(_ string: String) -> Expression {
        return Expression(string) { symbol, args in
            switch symbol {
            case .function("min", arity: _):
                if args.count < 2 { return nil }
                var lowest = args[0]
                for arg in args where arg < lowest {
                    lowest = arg
                }
                return lowest
            case .function("max", arity: _):
                if args.count < 2 { return nil }
                var highest = args[0]
                for arg in args where arg > highest {
                    highest = arg
                }
                return highest
            case let .variable(name):
                return self.variables[name]
            default:
                return nil
            }
        }
    }
varun-naharia commented 6 years ago

Thanks problem solved. But it'll be better if you make the code for function more understandable so anyone can add new function to lib.

nicklockwood commented 6 years ago

@varun-naharia The reason this is so complex is your requirement of having a variadic min/max function. If it weren’t for that, the code would be much simpler (no need for customExpression):

let result = try Expression(text, constants: variables).evaluate()

I’ll consider if there is an easier way to support variadic functions, but flexibility is always at odds with simplicity, and I’ve tried to optimize for making the common use cases simple.

nicklockwood commented 6 years ago

@varun-naharia good news: If you update to Expression 0.10.0, it now has built-in variadic min/max functions, so you can remove the customExpression() function and just use:

let result = try Expression(text, constants: variables).evaluate()