1

Given that I have 3 doubles:

double a = 1.0;
double b = 2.0;
double c = 3.0;

How do I generate all possible math equations using following operations:

  • +
  • *
  • ^2
  • sqrt()
  • parenthesis ()

The examples are:

  • 1 + 2 + 3;
  • 1 + 2 * 3;
  • (1 + 2) * 3;
  • 1^2 * 2 * 3;
  • 1 * (2^2 + sqrt(3))
  • ...

Overall, my approach will be following:

  • generate a string of each equation

  • execute the string either using the native roslyn compiler, or create my own implementation for logic execution, or use a solution such as ncalc

First option is the greatest, but it is not the fastest. So my question would be - how do I generate strings as in example section or if there is a better approach to this problem - I would like to hear out the ideas.

Alex
  • 4,607
  • 9
  • 61
  • 99
  • I wouldn't generate strings at all. The only thing any "engine" can do with a string is parse it back to values and operators. – C.Evenhuis May 01 '17 at 09:35
  • What is the end goal? – Evk May 01 '17 at 09:53
  • @C.Evenhuis I would prefer to execute native formulas, rather than parsable strings, but if I don't know the formula at the compile time, then I can not do it the native way. Well of course `roslyn` can help with that but this will make code execution a thousand times slower if I create a separate compiler for each equation. – Alex May 01 '17 at 09:57
  • @Evk I want a solution which will create formulas for me. I know the answer. I know the constants. Now I want the formula. A solution will try all different combinations and present a correct formula to the user. – Alex May 01 '17 at 09:58
  • 2
    [Expression trees](https://msdn.microsoft.com/en-us/library/mt654263.aspx) would be my idea. – Jeroen van Langen May 01 '17 at 10:13

1 Answers1

1

I'd think about this in terms of expression trees and context free grammars. You could say something like

EXPRESSION ::=
    1 | 2 | 3
  | ( EXPRESSION )+( EXPRESSION )
  | ( EXPRESSION )*( EXPRESSION )
  | ( EXPRESSION )^2
  | sqrt( EXPRESSION )
  ;

This is a bit over-eager on the parentheses, so if you care about beautiful strings you may want to clean up superfluous parens in a postprocessing step, or use some more elaborate grammar with multiple non-terminals to handle those correctly.

You can start with expression trees for the three terminal rules, i.e. your three constants. Then you can consider each of the recursive rules and plug the constants you have in place of the non-terminals. So you'd generate 1+1, 1+2, 1+3, 2+1, 2+2, 2+3, 3+1, 3+2, 3+3, (1)*(1), …. Something like

for op in [plus, times]:
  for lhs in expressions:
    for rhs in expressions:
      new_expressions.append(binaryop(lhs, op, rhs))
for op in [square, sqrt]:
  for arg in expressions:
    new_expressions.append(unaryop(op, arg))

Then after each such cycle, you would have to extend the set of expressions using the newly found ones. For the next round, you would try to ensure that at least one operand was generated in the last round. For binary operations, the other may have been older.

Perhaps things will become more feasible if you only use a single formula per possible value. So that once you have found that 1+3 can be used as an expression for 4, you don't do 2+2 and 2*2 and 2^2 and so on, as one way of expressing 4 is enough. Depends on the application.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • 1
    Good answer; I would have suggested the same thing. I did a series of blog articles on some techniques for enumerating the strings matched by a simple grammar, starting here: https://blogs.msdn.microsoft.com/ericlippert/2010/04/26/every-program-there-is-part-one/ – Eric Lippert May 01 '17 at 23:37