简体   繁体   中英

Where is the flaw in my algorithm to evaluate basic parenthetical expressions?

I'm trying to implement a program that works like

Input:

~1^(2~&3)|1 69 11111 -12 

Output:

[whatever NOT 69 XOR (11111 NAND -12) OR 69 equals]

with all the bitwise operators having equal precedence, evaluated left to right. The algorithm I'm constructing is basically

Get number n1 (or evaluation of expression in parenthesis)
Set result = n1
Get operator o1
Get number n2 (or evaluation of expression in parenthesis)
Set result = result op n2
Get operator o2
Get number n3 (or evaluation of expression in paranthesis)
Set result = result o2 n3
Etcetera

I'm not totally finished, but what I have should at least be able to evaluate

((1)) 69

which would result in

69

I've tested that

(1) 69

results in

69

which means I just have to figure out what's going wrong with the nested parenthesis.

The relevant part of my code, nicely commented, is ... bear with me here ...

    private static long? EvalInner ( string eqtn, Tuple<int,int> eqtnBnds, Dictionary<int,long> eqtnArgs)
    {
        //     eqtn: Equation to be parsed 
        // eqtnBnds: Bounds that define sub-equation being evaluated.
        // eqtnargs: Equation arguments

        // Parses and returns the evaluation of the equation eqtn in the bounds [eqtnBands.Item1, eqtnBnds.Item2)

        // Handle case of empty sub-equation:
        if (eqtnBnds.Item1 == eqtnBnds.Item2) throw new Exception(String.Format("Encountered empty equation at index {0}", eqtnBnds.Item1));


        long? result = null; 
        char? lastop = null; // last operator found 
        bool negateNextNum = false;
        PARSEMODE CURMODE = PARSEMODE.NUM; // beginning of equation should be a number since the form of the equation is
                                           // <NUM><OPER><NUM><OPER>...<OPER><NUM>
        int curidx = eqtnBnds.Item1, offend = eqtnBnds.Item2;
        while ( curidx < offend )
        {
            switch ( CURMODE )
            {
                case PARSEMODE.NUM: // Expecting character at current index to be the beginning of a 
                {
                    if ( eqtn[curidx] >= '0' && eqtn[curidx] <= '9' ) // beginning of the int corresponding to the argument index
                    {
                        int begidx = curidx;
                        // Increment curidx to one after the last digit or the end of the subequation
                        while ( ++curidx < offend && eqtn[curidx] >= '0' && eqtn[curidx] <= '9' );
                        // Try to get an integer representation of the argument. If an error is encountered in that parsing,
                        // throw an error. If the argument is one within the range of arguments given in the command line, 
                        // get its value and update result accordingly. If not, throw an error.
                        int argnum; 
                        if ( Int32.TryParse(eqtn.Substring(begidx, curidx - begidx), out argnum) )
                        {
                            if (eqtnArgs.ContainsKey(argnum))
                            {
                                result = (result != null ? BinOpEval(result, lastop, eqtnArgs[argnum]) : eqtnArgs[argnum]);
                                if (negateNextNum)
                                {
                                    result = ~result;
                                    negateNextNum = false;
                                }
                            }
                            else
                                throw new Exception(String.Format("Argument {0} found in the equation beginning at index {1} is out-of-bounds",
                                                                   argnum,
                                                                   begidx)
                                                    );
                        }
                        else
                        {
                            throw new Exception(String.Format("Trouble parsing argument number that begins at index {0}", 
                                                               begidx)
                                                );
                        }
                        CURMODE = PARSEMODE.OPER;
                    }
                    else if ( eqtn[curidx] == Calculator.OPENPAREN ) // beginning of subequation in paranthesis
                    {
                        int begidx = curidx, netparens = 1;
                        while ( ++curidx < offend && netparens != 0 )
                        {
                            if      ( eqtn[curidx] == Calculator.OPENPAREN  )  ++netparens;
                            else if ( eqtn[curidx] == Calculator.CLOSEPAREN )  --netparens;
                        }
                        if ( netparens != 0 ) // didn't find closing parenthesis
                            throw new Exception(String.Format("Couldn't find closing paranthesis for opening paranthesis at index {0}",
                                                               begidx)
                                                );
                        long? presult = null; // to be the result of the evaluated subequation between the set of parenthesis
                        try { presult = EvalInner(eqtn,new Tuple<int, int>(++begidx, curidx - begidx),eqtnArgs); }
                        catch ( Exception e ) { throw e; }
                        result = (result != null ? BinOpEval(result,lastop,presult) : presult);
                        if (negateNextNum)
                        {
                            result = ~result;
                            negateNextNum = false;
                        }
                        // Upon leaving this else-if block, curidx should be 1 after the closing paranthesis
                        // Expect operate to following closing paranthesis
                        CURMODE = PARSEMODE.OPER; // expecting operator after parens
                    }
                    else if ( eqtn[curidx] == Calculator.NOT ) // NOT symbol preceding number
                    {
                        negateNextNum = !negateNextNum;
                        ++curidx;
                        // CURMODE stays as num
                    }
                    else // unexpected character where beginning of number expected
                    {
                        throw new Exception(String.Format("Expected beginning of number at index {0}, instead got {1}",
                                                            curidx,
                                                            eqtn[curidx])
                                            );
                    }
                    break;
                }

                case PARSEMODE.OPER:
                {
                     // ... 

and I'm trying to figure out the logical reason why

((1)) 69

is printing the error

Error: Encountered empty equation at index 2

Let me go through my logic:

At first curidx = 0 , eqtn[curidx] = '(' and CURMODE = PARSEMODE.NUM . That causes us to enter the block

                    else if ( eqtn[curidx] == Calculator.OPENPAREN ) // beginning of subequation in paranthesis
                    {
                          // ... 
                    }

At the end of the portion of the block

                        int begidx = curidx, netparens = 1;
                        while ( ++curidx < offend && netparens != 0 )
                        {
                            if      ( eqtn[curidx] == Calculator.OPENPAREN  )  ++netparens;
                            else if ( eqtn[curidx] == Calculator.CLOSEPAREN )  --netparens;
                        }

after which begidx is 0 and curidx is 5 (the index one after the closing parenthesis). Therefore, ++begidx is 1 and curidx - begidx (evaluated after ++begidx ) is 4 . Therefore, after calling

EvalInner(eqtn,new Tuple<int, int>(++begidx, curidx - begidx),eqtnArgs); }

we end up back in the block

                    else if ( eqtn[curidx] == Calculator.OPENPAREN ) // beginning of subequation in paranthesis
                    {
                        int begidx = curidx, netparens = 1;
                        while ( ++curidx < offend && netparens != 0 )
                        {
                            if      ( eqtn[curidx] == Calculator.OPENPAREN  )  ++netparens;
                            else if ( eqtn[curidx] == Calculator.CLOSEPAREN )  --netparens;
                        }

and after the above begidx = 1 and curidx = 4 (the index one after the closing parenthesis). Then ++begidx = 2 and henceforth curidx - begidx = 2 and when we re-enter EvalInner we have eqt n[curidx] = '1' , causing us to enter the block

                    if ( eqtn[curidx] >= '0' && eqtn[curidx] <= '9' ) // beginning of the int corresponding to the argument index
                    {
                       // ... 
                    }

after which, clearly, we have oarsed the 1 , associated it with the argument 69 , and passed back through the call stack to the output.

Where did I go wrong there?

As far as your code does show a relevant effort and fixing it might not be straightforward, I have preferred to write a recursive algorithm delivering the functionality you want.

The basic ideas of the code below:

  • removeAllParen starts the parsing process. It performs a basic validity check (ie, same number of opening and closing parenthesis) to decide whether a new call to the function performing the actual analysis is required or not. All these calls are recursively building the final output.
  • removeOneParen takes care of just one set of parenthesis, the most external ones (eg, from "(((1)))" the output is "((1))"). It looks for the first opening parenthesis and the last closing one.

Bear in mind that this is just a sample code to help you understand how to solve this situation recursively. This is not a comprehensive parenthesis-parsing approach, but one adapted to your specific conditions. For example, the second function would fail in situations like "(1)(1)" (although adapting it is not too difficult; you would have to rely on an opening-/closing-parenthesis counting approach similar to what you are doing in your code).

private string removeAllParen(string input)
{
    string output = input;

    int openingCount = 0;
    int closingCount = 0;
    do
    {
        openingCount = output.Split('(').Length - 1;
        closingCount = output.Split(')').Length - 1;
        if (openingCount != closingCount || openingCount < 1 || closingCount < 1)
        {
            if (openingCount != closingCount) output = "ERROR";
            break;
        }
        output = removeOneParen(output);
    } while (true);


    return output;
}

private string removeOneParen(string input)
{
    string output = input;
    int count = 0;

    bool error = false;
    int iIni = 0;
    int iEnd = output.Length - 1;
    while (count < 2)
    {
        count = count + 1;
        bool ended = false;
        if (count == 2)
        {
            iIni = output.Length - 1;
            iEnd = 0;
        }
        int i = iIni;
        while (!ended)
        {
            string curBit = output.Substring(i, 1);
            if (curBit == "(" || curBit == ")")
            {
                if (count == 1 && curBit == "(" && i < output.Length - 2) output = output.Substring(i + 1);
                else if (count == 2 && curBit == ")") output = output.Substring(0, i);
                else error = true;
                break;
            }
        }
        if (error) break;
    }

    if (error) output = "ERROR";

    return output;
}

UPDATE

Just for the sake of completeness, here you have another version of removeOneParen delivering a more comprehensive answer based on the aforementioned opening/closing parenthesis count.

private string removeOneParen(string input)
{
    string output = "ERROR";

    int openingCount = 0;
    int closingCount = 0;
    bool firstOpening = true;
    int startI = -1;
    for (int i = 0; i < input.Length; i++)
    {
        string curBit = input.Substring(i, 1);
        if (curBit == "(")
        {
            openingCount = openingCount + 1;
            if (firstOpening)
            {
                startI = i + 1;
                firstOpening = false;
            }
        }
        else if (curBit == ")")
        {
            closingCount = closingCount + 1;
            if (openingCount == closingCount)
            {
                if(startI > 0) output = input.Substring(0, startI - 1);
                output = output + input.Substring(startI, i - startI);
                if(i < input.Length - 1) output = output + input.Substring(i + 1);
                break;
            }
        }
    }

    return output;
}

Is the second element of the tuple here meant to be the length of the subequation or the upper bound of it?

presult = EvalInner(eqtn,new Tuple<int, int>(++begidx, curidx - begidx),eqtnArgs);

It would have probably been more helpful to simply have two named arguments that were clear, but based on your usage earlier than the recursive call, it looks as though it is meant to be the upper bound, but you are passing in a length.

Maybe this is the only problem? Not sure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM