2

Added another example.

I have a mathematical expression such as cos(pi*cos(pi * sin(pi*y))) and I want to solve it.

I think the best way to parse it is starting by the end of the string.

So, in the expression above:

  1. i = sin(pi*y)
  2. i = cos(pi*i)
  3. i = cos(pi*i)

I'm going to add another expression as example: cos(pi*(avg(x,x)*y))

This should be evaluated like this:

  1. i = avg(x,x)
  2. i = i*y
  3. i = cos(pi*i)

What do you think about it? Could you help me to implement a the code?

Thanks in advance

Elena
  • 39
  • 4
  • What do you mean by "solve it"? Numerically, symbolically? – CroCo Dec 17 '15 at 12:30
  • 2
    I think you should use recursion to parse such expression – Olga Akhmetova Dec 17 '15 at 12:32
  • With "solve it" I mean to get an integer that it's the result of the evaluation, I should give to variables x and y a value, I know. – Elena Dec 17 '15 at 12:32
  • Why not parse the string from start to end to make some list of required calculations and then work on the list from the end to front (or just make a stack instead of a list)? – shapiro yaacov Dec 17 '15 at 12:32
  • @OlgaAkhmetova yeah, I think the as you, but I don't know how to start parsing by the end of the string. – Elena Dec 17 '15 at 12:32
  • What I think @OlgaAkhmetova means is to have a recursive function that parses the required action, gives as input the rest of the string and gets back the `int` result. Then perform your action and return – shapiro yaacov Dec 17 '15 at 12:34
  • @Elena In case of doubt, reverse the string in memory (there are simple algorithms to do that) and start to parse from the beginning of the reversed string. But I’m not sure whether the direction is your actual problem. – Jonas Schäfer Dec 17 '15 at 12:35
  • @Elena why want you start by the end? Is it some sort of requirement? – Olga Akhmetova Dec 17 '15 at 12:35
  • @JonasWielicki What do you think is the problem? – Elena Dec 17 '15 at 12:36
  • @OlgaAkhmetova no, it's not a requirement. I thought it would be easier. – Elena Dec 17 '15 at 12:37
  • @Elena, do you have rules regarding the strings? This is not trivial work though. – CroCo Dec 17 '15 at 12:38
  • @CroCo I do not have any rules. – Elena Dec 17 '15 at 12:39
  • @Elena I’m not sure what you want to achieve by effectively reversing the string. For expressions like ``cos(pi*atan(y)) + sin(pi*y)``, I cannot see an advantage by reversing the string. (Although with your original example, there admittedly is an advantage, because in the reversed string, the operations happen to be in the order they are needed and by executing them in that order you only need to have a single temproary variable.) – Jonas Schäfer Dec 17 '15 at 12:42
  • 1
    @Elena, if you expect the user to enter random strings, then you are in a big trouble since `cos(pi)` can be written as cos[pi] or cos(Pi) or cos(PI) or cos(3.14). My point is to at least start with some rules. – CroCo Dec 17 '15 at 12:42
  • @CroCo the expression comes from a random generator so that's why there is only one possible notation. – Elena Dec 17 '15 at 15:18

3 Answers3

3

I have a mathematical expression such as cos(pi*cos(pi * sin(pi*y))) and I want to solve it.

No, you want to evaluate it. Solving tells you the conditions under which something is true. Evaluating it just gives you a resulting value.

I think the best way to parse it is starting by the end of the string.

The traditional way of parsing expressions like this is using recursive descent. It's more general and much easier to implement. The control flow looks something like this:

  • cos ( A ...

    • where A = pi * cos ( B ...

      • where B = pi * sin ( C ...

        • where C = pi * y

          now you can evaluate pi * y, and return the value of C

        ... and now you have C, you can evaluate pi * sin(C) and return the value of B

      ... and now you have the value of B, you can evaluate pi * cos(B), returning the value as A

    ... and now you have the value of A, you can evaluate cos(A), and you're done.

This is exactly the way the C expression cos(M_PI * cos(M_PI * sin(M_PI * y))) works (assuming the common but non-standard constant for π).

It is evaluated roughly right-to-left (actually inner-to-outer), but still read left-to-right. We've just labelled the temporary values for clarity.

This control flow is often simply turned into a tree like

[cos of _]
        |
    [pi * _]
          |
     [cos of _]
             |
         [pi * _]
               |
          [sin of _]
                  |
              [pi * y]

But obviously you can just evaluate the result once unless you need to save the tree for later. (Note this lopsided tree is still a tree, it's just degenerate because of the way your expression is nested).

...What do you think about it?

The problem with your solution is that it breaks for different nesting structures, eg.

cos( sin((pi * x) + y) + sin(y + (pi * x)) )

can't simply be evaluated right-to-left.

Could you help me to implement the code?

Separate the string processing (tokenization) from the parsing and evaluation. It's much easier to reason independently about your string processing and your maths.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • Since the OP seems to have very limited knowledge of parsers in general, it would be interesting to point to available parser generators. I used antlr for Java and it was ok. I tried Boost.Spirit once (years ago), but the error messages made me stop using it. Do you have any suggestions for C++? – Jens Dec 17 '15 at 13:39
  • Honestly, for expressions this simple, it seems like implementing it directly is the way to go - at least as a learning exercise. The lexer/parser systems I've used previously probably have more of a learning curve than the C++ required to do this, and the errors & documentation are probably harder to understand if you don't know the basics in the first place. If anyone has a beginner-friendly suggestion though, it would be well-received. – Useless Dec 17 '15 at 13:43
  • @Useless thanks for your answer but I'm still having some doubts about it. Could you help me, at least a little bit, to implement the code? Thank you very much. – Elena Dec 17 '15 at 15:19
  • Start by writing the tokenizer: your string should be converted into a sequence like `["cos", "(", "pi", "*", "sin", ...]`. Ask a dedicated question if you have a problem with this, it's a big enough step in itself. Then write the parser/evaluator operating on this sequence. It has to recognize `cos` as a function of one argument, `*` as the infix multiplication operator, `(` as the beginning of a nested sub-expression etc. etc. The key, as always, is to break the problem down into smaller problems and solve them individually. – Useless Dec 17 '15 at 17:07
  • @Useless now I have the tokenize, but I don't know how to continue. – Elena Dec 18 '15 at 10:18
  • Good work! Now you only have a few possibilities to deal with: eg. "cos" means evaluate the next sub-expression and apply `cos` to the result, "(" means recursively evaluate a nested sub-expression, etc. It might be a better fit for a fresh question though, showing the input (which is the output you got from your tokenizer) and desired result. – Useless Dec 18 '15 at 10:24
0

EDITED: Base case improvement.

This is just some pseudo code, but the idea should work:

int ParseAndCalculate(string input)
{
    if (input.DoesConvertToSomeIntegerWork())
        return input.ConvertToSomeInteger();

    string actionRequired = GetActionFromString(input, "(");
    int tempIndex = input.LocationOfFirst("(");
    int tempResult = ParseAndCalculate(input, tempIndex, input.Length -1);
    tempResult = PerformRequiredAction(tempResult, actionRequired);

    return tempResult;
}

Not sure what you should return at the base case. Maybe work out what the actual value is in there?

shapiro yaacov
  • 2,308
  • 2
  • 26
  • 39
  • Sorry to say it but I don't see how this pseudo code will guide the OP. Why the function is returning `int`? With no rules, it is extremely difficult to come up with a good pseudo. – CroCo Dec 17 '15 at 13:07
  • Well, @CroCo, it gives the basic idea of some way to go. Yes, it might need to return `long`, `double` or even a `string`. Yes, the syntax is similar to `C#`, and yes, I assumed delimiters are `(` and `)`. But since most of these issue can be done in other sub functions, the idea stays the same idea. Hence *Pseudo code*... – shapiro yaacov Dec 17 '15 at 13:15
0

If you want to write a C/C++ program that can parse mathematical expressions like the above, you can write a top-down parser in C/C++ yourself, or you might want to have a look at GNU Bison.

Bison is a parser generator, and it happens to come with an example that's quite close to your problem.

The benefits of using it would be that you don't need to care about the actual parsing routines yourself, but can focus on the grammar specification and the mathematical function callbacks. I have to admit that getting into Bison and integrating it into the build may be quite an effort. I can't tell if it's really useful in your situation, but at least it's an established and robust tool used when solving such tasks.

It can also be used for evaluating expressions in the "correct" order, because it supports operator precedence definitions.

stj
  • 9,037
  • 19
  • 33