带函数的Shunting Yard算法

6

这是我在这里发布的第一篇帖子 :)

我发现有很多关于Shunting yard算法的问题,但我希望仍然有论坛成员愿意帮助我解决这个算法的另一个问题。

我搜索了其他帖子,看看是否已经回答了我的问题,并在其他论坛和互联网上进行了一些研究:

http://stackoverflow.com/questions/16877546/modifying-the-shunting-yard-algorithm-c
http://www.computerhope.com/forum/index.php?topic=146535.0
http://en.wikipedia.org/wiki/Shunting-yard_algorithm
http://www.autoitscript.com/forum/topic/164627-shunting-yard-with-functions/

我的代码是用vb-script编写的,因为我喜欢它的简单性,而且我不知道java或类似语言的c..

我的问题:

目前这个算法允许错误使用“(”和“)”,例如: function((10,20,)30),虽然这显然不是调用函数的正确方法...

我也不确定我的代码是否编写正确,维基百科上的伪代码是我的参考,但不是很清晰:(

我还计划扩展它,加入if-else语句和嵌套循环等内容,因为主要目标是用类似vb的语言编写一种解释器作为学习项目 :)

我的代码[编辑]:

SET PRECEDENCE = CREATEOBJECT("SCRIPTING.DICTIONARY")
WITH PRECEDENCE
    .ADD "^",3
    .ADD "*",2
    .ADD "/",2
    .ADD "%",2
    .ADD "+",1
    .ADD "-",1
    .ADD "FUNCTION",0
    .ADD "(",0
    .ADD ",",0
    .ADD ")",0
END WITH

'#############################################################################
tokenArray = split("FUNCTION ( ( A , B ) , C )")
msgbox SHUNTINGYARD(tokenArray)
'#############################################################################

FUNCTION SHUNTINGYARD(INPUT)

    TOKEN_QUEUE = ARRAY()
    TOKEN_STACK = ARRAY()

    FOR TOKEN_NUMBER = 0 TO UBOUND(INPUT)
        SELECT CASE INPUT(TOKEN_NUMBER)

            CASE "("
                CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)

            CASE ")"
                DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
                    CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching ""("".", TRUE)
                LOOP

                IF PEEK(TOKEN_STACK) = "FUNCTION" THEN
                    DISCARD = POP(TOKEN_STACK)
                    CALL PUSH("@", TOKEN_QUEUE)
                ELSE
                    DISCARD = POP(TOKEN_STACK)
                END IF

            CASE ","
                DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
                    CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching function ""("".", TRUE)
                LOOP

            CASE "+","-","*","/","^","%"
                TOKEN_A = INPUT(TOKEN_NUMBER)
                DO WHILE ISOPERATOR(PEEK(TOKEN_STACK))
                    TOKEN_B = PEEK(TOKEN_STACK)
                    IF (ASSOCIATIVITY(TOKEN_B) = "left" AND PRECEDENCE(TOKEN_A) = PRECEDENCE(TOKEN_B)) OR (PRECEDENCE(TOKEN_A) < PRECEDENCE(TOKEN_B)) THEN
                        CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    ELSE
                        EXIT DO
                    END IF
                LOOP
                CALL PUSH(TOKEN_A, TOKEN_STACK)

            CASE ELSE
                CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_QUEUE)

        END SELECT
    NEXT

    FOR ITEMCOUNT = 0 TO UBOUND(TOKEN_STACK)
        IF PEEK(TOKEN_STACK) = "(" THEN CALL ERRORS("Can't find a matching "")"".", TRUE)'(
        CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
    NEXT

    SHUNTINGYARD = JOIN(TOKEN_QUEUE,"|")

END FUNCTION

'#############################################################################

FUNCTION ASSOCIATIVITY(ASSOC)
    SELECT CASE LCASE(ASSOC)
        CASE "^","\"
            ASSOCIATIVITY = "right"
        CASE ELSE
            ASSOCIATIVITY = "left"
    END SELECT
END FUNCTION

FUNCTION ISOPERATOR(ITEM)
    ISOPERATOR = LEN(ITEM) = 1 AND INSTR("+-*/%^",ITEM)
END FUNCTION

SUB PUSH(ITEM,BYREF STACK)
    IF UBOUND(STACK) > -1 THEN
        REDIM PRESERVE STACK(UBOUND(STACK) + 1)
        STACK(UBOUND(STACK)) = ITEM
    ELSE
        STACK = ARRAY(ITEM)
    END IF
END SUB

FUNCTION POP(BYREF STACK)
    IF UBOUND(STACK) > -1 THEN
        POP = STACK(UBOUND(STACK))
        REDIM PRESERVE STACK(UBOUND(STACK) - 1)
    END IF
END FUNCTION

FUNCTION STACKISEMPTY(STACK)
    IF UBOUND(STACK) > -1 THEN
        STACKISEMPTY = FALSE
    ELSE
        STACKISEMPTY = TRUE
    END IF
END FUNCTION

FUNCTION PEEK(STACK)
    IF UBOUND(STACK) > -1 THEN
        PEEK = STACK(UBOUND(STACK))
    END IF
END FUNCTION

对于关注此问题的人:https://stackoverflow.com/questions/28593987/shunting-yard-algorithm-with-functions-debugging - Tom
1个回答

7
您可以像对待其他运算符一样处理“FUN”、“(”、“)”和“,”,并将它们推入TOKEN_STACK。为了简洁起见,我将“FUNCTION”缩写为“FUN”。 “FUN”、“(”、“)”和“,”的优先级低于“+”,因此我们的优先级表如下:
^                    4
*  /  %              3
+  -                 2
( ) ,   FUN          1

考虑当解析1+FUN(2*3)时会发生什么。
Remaining Input     Stack             Output
1+FUN(2*3)                          
+FUN(2*3)                           1
FUN(2*3)          +                 1
(2*3)             +  FUN            1
2*3)              +  FUN  (         1
*3)               +  FUN  (         1  2
3)                +  FUN  (  *      1  2
)                 +  FUN  (  *      1  2  3
                  +  FUN  (  *  )   1  2  3          Note 1
                  +  FUN  ( )       1  2  3  *       Note 2
                  +                 1  2  3  * FUN() 
                                    1  2  3  * FUN() +

输出标记“FUN()”表示函数求值。
注意1:当我们试图将“)”推入栈中时,所有优先级高于它的运算符都将从栈中取出并移到输出中。
注意2:当堆栈末尾的项是匹配的“(”时,它将从堆栈中移除。有两种情况需要处理:像1 +(2 * 3)一样简单地匹配括号,或者在堆栈上的“(”之前有一个函数标记。在这种情况下,从堆栈中弹出FUN并添加一个函数求值标记到输出中。
逗号“,”会以类似于“)”的方式进行处理,导致优先级较高的运算符被弹出。但它不会导致函数求值输出。您需要一些方法记录函数具有多少个参数。
在我的代码中,我使用了基于递归的算法。解析算法可以被递归调用,并给出一个停止标记。当遇到停止标记时,它会退出递归。当遇到函数时,它调用递归,等待“,”或“)”。
我设法使用cscript命令行程序使其运行。我还添加了一些带有Wscript.Echo的调试代码。
查看TOKEN_STACK,您从未将FUNCTION Token添加到堆栈中,因此在搜索时不存在。添加
CASE "FUNCTION" CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)
似乎可以得到正确的结果。尽管我不确定当您匹配第一个关闭括号时会发生什么。对于像“E + FUNCTION((A,B),C)+ F”这样的输入,它也出现了严重错误。
SET PRECEDENCE = CREATEOBJECT("SCRIPTING.DICTIONARY")
WITH PRECEDENCE
    .ADD "^",3
    .ADD "*",2
    .ADD "/",2
    .ADD "%",2
    .ADD "+",1
    .ADD "-",1
    .ADD "FUNCTION",0
    .ADD "(",0
    .ADD ",",0
    .ADD ")",0
END WITH

Wscript.Echo "Start"

'#############################################################################
tokenArray = split("FUNCTION ( ( A , B ) , C )")
'#tokenArray = split("A + B * C")


Wscript.Echo "Result " + SHUNTINGYARD(tokenArray)
'#############################################################################

FUNCTION SHUNTINGYARD(INPUT)

    TOKEN_QUEUE = ARRAY()
    TOKEN_STACK = ARRAY()

    FOR TOKEN_NUMBER = 0 TO UBOUND(INPUT)
    Wscript.Echo "Token " + INPUT(TOKEN_NUMBER)
    Wscript.Echo "Stack " + JOIN(TOKEN_STACK,"|")

        SELECT CASE INPUT(TOKEN_NUMBER)

            CASE "("
                CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)

            CASE ")"

                DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )

                    CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching ""("".", TRUE)
                LOOP

                IF PEEK(TOKEN_STACK) = "FUNCTION" THEN
                    DISCARD = POP(TOKEN_STACK)
                    CALL PUSH("@", TOKEN_QUEUE)
                ELSE
                    DISCARD = POP(TOKEN_STACK)
                END IF

            CASE ","
                DO WHILE NOT( PRECEDENCE( PEEK(TOKEN_STACK) ) = 0 )
                    CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    IF STACKISEMPTY(TOKEN_STACK) THEN CALL ERRORS("Can't find a matching function ""("".", TRUE)
                LOOP

            CASE "+","-","*","/","^","%"
                TOKEN_A = INPUT(TOKEN_NUMBER)
                DO WHILE ISOPERATOR(PEEK(TOKEN_STACK))
                    TOKEN_B = PEEK(TOKEN_STACK)
                    IF (ASSOCIATIVITY(TOKEN_B) = "left" AND PRECEDENCE(TOKEN_A) = PRECEDENCE(TOKEN_B)) OR (PRECEDENCE(TOKEN_A) < PRECEDENCE(TOKEN_B)) THEN
                        CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
                    ELSE
                        EXIT DO
                    END IF
                LOOP
                CALL PUSH(TOKEN_A, TOKEN_STACK)

            CASE "FUNCTION"
                CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_STACK)

            CASE ELSE
                CALL PUSH(INPUT(TOKEN_NUMBER), TOKEN_QUEUE)

        END SELECT
    NEXT

    FOR ITEMCOUNT = 0 TO UBOUND(TOKEN_STACK)
        IF PEEK(TOKEN_STACK) = "(" THEN CALL ERRORS("Can't find a matching "")"".", TRUE)'(
        CALL PUSH(POP(TOKEN_STACK), TOKEN_QUEUE)
    NEXT

    SHUNTINGYARD = JOIN(TOKEN_QUEUE,"|")

END FUNCTION

'#############################################################################

FUNCTION ASSOCIATIVITY(ASSOC)
    SELECT CASE LCASE(ASSOC)
        CASE "^","\"
            ASSOCIATIVITY = "right"
        CASE ELSE
            ASSOCIATIVITY = "left"
    END SELECT
END FUNCTION

FUNCTION ISOPERATOR(ITEM)
    ISOPERATOR = LEN(ITEM) = 1 AND INSTR("+-*/%^",ITEM)
END FUNCTION

SUB PUSH(ITEM,BYREF STACK)
    IF UBOUND(STACK) > -1 THEN
        REDIM PRESERVE STACK(UBOUND(STACK) + 1)
        STACK(UBOUND(STACK)) = ITEM
    ELSE
        STACK = ARRAY(ITEM)
    END IF
END SUB

FUNCTION POP(BYREF STACK)
    IF UBOUND(STACK) > -1 THEN
        POP = STACK(UBOUND(STACK))
        REDIM PRESERVE STACK(UBOUND(STACK) - 1)
    END IF
END FUNCTION

FUNCTION STACKISEMPTY(STACK)
    IF UBOUND(STACK) > -1 THEN
        STACKISEMPTY = FALSE
    ELSE
        STACKISEMPTY = TRUE
    END IF
END FUNCTION

FUNCTION PEEK(STACK)
    IF UBOUND(STACK) > -1 THEN
        PEEK = STACK(UBOUND(STACK))
    END IF
END FUNCTION

感谢Salix alba提供的有用回复!如果我有时间,我会修改我的代码并在这里发布 :) - Tom
好的,找到了一些时间^^。已经有一段时间了(抱歉)。我仍然在代码方面遇到一些麻烦,函数token没有被函数评估token替换,我不知道为什么,而且我也不确定代码是否编写正确:(试图弄清楚如何重新发布我的代码s:) - Tom
是的,我会尝试帮助您解决这个问题。但是VBscript不是我熟悉的语言。您能否提供一个完整的工作示例,并指导我如何运行它。我有IE8和VB Express Edition 2008。 - Salix alba
当然,谢谢!如果你使用的是Windows系统,只需创建一个扩展名为.vbs的文件,并将我的脚本内容复制进去即可。另外,AutoIt版本可以在AutoIt论坛上找到。如果你想测试VBScript,只需双击它即可 :) 如果你使用的是Mac或Linux系统,则需要一些Windows模拟器。感谢你的帮助!如果用VBScript太难了,那么VB2008或Java也可以。 - Tom
我使用cscript命令行程序成功运行了它。我已经更新了我的答案,其中突出显示了您代码中的一个问题- FUNCTION标记未被推入堆栈。 - Salix alba
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接