【问题标题】:String calculator in C with recursion带有递归的C中的字符串计算器
【发布时间】:2015-08-12 04:15:03
【问题描述】:

我正在用 C 语言制作一个字符串计算器,但我遇到了一些问题。

例如:2 * (123-321) * (2+(3-4)-(3*2*2)) / ((12-2)/(1+1+1+1+0+ 1))

应该打印2178,而是打印3058704。

所以我尝试计算 (2+(3-4)-(3*2*2)) 并发现它返回 77578153 而不是 -11。
而且2*(123-321)*(1-12)/2 返回1980 而不是2178。

#include <stdio.h>
#include <stdlib.h>
char *vnos;

int charToDigit(int i1, int i2) {
    int stevilo = 0;
    int i;
    //printf("%d  %d\n",i1,i2);
   // printf("%c %d\n",vnos[i1], vnos[i1]);
    for(i=i1; i<=i2; i++)
        //printf("%d  ", (vnos[i1]-48));
         stevilo = (stevilo + (vnos[i] - '0'))*10;
    stevilo = stevilo/10;
    return stevilo;
}

int rekurzija(int zacetek, int konec) {
     //printf("bumbum: %d %d \n", zacetek, konec);
    int i,oklepaj1,zaklepaj1,oklepaj,zaklepaj;
    int zacasniI=0;
    i=zacetek;

    if(vnos[zacetek] == '(' && vnos[konec] == ')'){
        oklepaj = 1;
        zaklepaj = 0;
        i++;
        while(!oklepaj == zaklepaj) {
            if (vnos[i] == '(')
                oklepaj++;
            if (vnos[i] == ')')
                zaklepaj++;
            i++;
        }
        i--;
        if(i==konec){
            return rekurzija(zacetek+1,konec-1);
        }
    }


    for(i=zacetek; i<=konec; i++){
           // printf("tralala: %d %d \n", zacetek, konec);
        switch(vnos[i]){
            case '+':
                return rekurzija(zacetek,(i-1))+rekurzija((i+1),konec);
            case '-':
                if(i>zacasniI)
                    zacasniI = i;
                break;
            case '*':
                if(zacasniI==0)
                    zacasniI = i;
                break;
            case '/':
                if(zacasniI==0 || vnos[zacasniI]=='/')
                    zacasniI = i;
                break;
            case '(':
                oklepaj1 = 1;
                zaklepaj1 = 0;
                i++;
                while(!oklepaj1 == zaklepaj1) {
                if (vnos[i] == '(')
                    oklepaj1++;
                if (vnos[i] == ')')
                    zaklepaj1++;
                i++;
                }
                i--;
                break;
        }

    }
    if(zacasniI>0){
        switch(vnos[zacasniI]) {
        case '-': return rekurzija(zacetek, zacasniI-1)-rekurzija(zacasniI+1, konec);
        case '*': return rekurzija(zacetek, zacasniI-1)*rekurzija(zacasniI+1, konec);
        case '/': return rekurzija(zacetek, zacasniI-1)/rekurzija(zacasniI+1, konec);
        }
    }

    return charToDigit(zacetek,konec);

}

int main(){
    vnos = malloc(sizeof(char) * 9000);
    char r;
    int z = 0;
    int l;
    scanf("%c", &r);
    while(r != '\n'){
        if(r != ' '){
            vnos[z] = r;
            z++;
        }
        scanf("%c", &r);
    }
    int result = rekurzija(0,z-1);
    printf("%d\n", result);
    return 0;
}

vnos = 输入
zacetek = 开始
konec = 结束
stevilo = 数字
oklepaj = 右括号
zaklepaj = 左 括号
zacasni = 临时

任何帮助将不胜感激。

【问题讨论】:

  • (3 2 2) 是什么意思?是否缺少一些运算符?
  • 是的,应该是 3*2*2
  • 你试过在函数中检查你的解决方案吗?
  • 这是一个学校项目,它说最大输入是 9000,所以我没有费心重新分配它,因为它会使用更多内存
  • @sadsad 那么你也应该在 9000 处强制停止阅读。永远不要相信用户

标签: c string recursion calculator


【解决方案1】:

当输入中有多层括号时,您的问题似乎出现了。

(2+(3-4)-(3*2*2)) --> fail

2+(3-4)-(3*2*2) --> -11  correct

这条线看起来很可疑:

            while(!oklepaj1 == zaklepaj1) {

! 的优先级高于==,所以您可能的意思是:

            while(!(oklepaj1 == zaklepaj1)) {

我试过了,输入有多层括号的时候还是不能正确计算,所以括号相关的bug比较多。

编辑:

我发现括号的剩余问题。当您遇到括号时,您将括号中的整个表达式发送到charToDigit。相反,您应该将括号内的内容发送到rekurzija。

此外,您正在使用整数除法,因此任何余数都将被删除。这就是为什么1/2*2 给出0。

【讨论】:

  • 谢谢。我会改正的。我同意最有可能的括号是问题所在。但是刚刚发现还有一个问题 1/2*2 给了我 0 而不是 1
  • 您可以通过打印出哪些值/子字符串传递给charToDigit来验证括号是否存在问题。
  • 好吧,我想我现在明白了。修复了 while(!oklepaj1 == zaklepaj1) int result---> long, int rekurzija----> double case' * ' ---> case ' * ': if(zacasniI==0|| vnos[zacasniI] =='/')
【解决方案2】:

正如 Klas Lindbäck 已经指出的那样,您的代码在解析括号中的结构时会出现问题。

原则上,你的算法应该是这样的:

  • 如果括号外有运算符,请找到优先级最低的运算符(即加号或减号,而不是乘号或 div),然后在此处拆分表达式,左右递归。
  • 否则,当第一个和最后一个字符是括号时,删除它们并使用内容递归。
  • 否则,结果必须是数字:解析它。

您可以在途中检查不平衡的括号或非数字。

将此算法应用于您的代码会得到:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *vnos = "2*(123-321)*(2+(3-4)-(3*2*2))/((12-2)/(1+1+1+1+0+1))";

int charToDigit(int i1, int i2)
{
    int stevilo = 0;
    int i;

    for(i = i1; i <= i2; i++) {
        int n = vnos[i] - '0';

        if (n < 0 || n > 9) return -1;
        stevilo = 10 * stevilo + n;
    }

    return stevilo;
}

int prec(int op)
{
    switch (op) {
    case '+':   return 1;
    case '-':   return 1;
    case '*':   return 2;
    case '/':   return 2;
    }

    return 0;
}

int calc(int op, int a, int b)
{
    switch (op) {
    case '+':   return a + b;
    case '-':   return a - b;
    case '*':   return a * b;
    case '/':   if (b == 0) {
                    printf("Division by zero\n");
                    exit(1);
                }
                return a / b;
    }

    return 0;
}

int rekurzija(int zacetek, int konec)
{
    int oklepaj = 0;
    int mid = -1;
    int i;

    if (zacetek > konec) return 0;

    for(i = zacetek; i <= konec; i++) {
        int c = vnos[i];

        if (c == '(') {
            oklepaj++;
        } else if (c == ')') {
            if (oklepaj == 0) {
                printf("Bad zaklepaj in %.*s\n", 
                    konec - zacetek + 1, vnos + zacetek);
                exit(1);
            }
            oklepaj--;
        } else if (oklepaj == 0) {            
            int n = prec(c);

            if (n && (mid < 0 || n < prec(vnos[mid]))) {
                mid = i;
            }
        }
    }

    if (oklepaj > 0) {
        printf("Bad uklepaj in %.*s\n", 
            konec - zacetek + 1, vnos + zacetek);
        exit(1);
    }

    if (mid >= 0) {
            int a = rekurzija(zacetek, mid - 1);
            int b = rekurzija(mid + 1, konec);   
            int res =  calc(vnos[mid], a, b);

            printf("%d %c %d == %d\n", a, vnos[mid], b, res);
            return res;
    } else {
        if (vnos[zacetek] == '(' && vnos[konec] == ')'){
            return rekurzija(zacetek + 1, konec - 1);
        }

        int res = charToDigit(zacetek, konec);

        if (res < 0) {
            printf("Bad stevilo in %.*s\n", 
                konec - zacetek + 1, vnos + zacetek);
            exit(1);
        } else {
            return res;
        }
    }

    return 0;
}

int main()
{
    int result = rekurzija(0, strlen(vnos) - 1);

    printf("%d\n", result);

    return 0;
}

这将产生 1980,而不是 2178,因为在将 -11 除以 2 的过程中,由于整数除法,结果是 -5 而不是 -5.5。

通过将空字符串视为零,此代码甚至会将一元减号和加号视为0 - x。 (当然,现在也有一元时代,这很荒谬。)

最后,如果您对其他方法持开放态度,Shunting-yard algorithm 从左到右一次计算表达式。

【讨论】:

    猜你喜欢
    • 2020-03-15
    • 2013-07-21
    • 2014-03-31
    • 1970-01-01
    • 2020-02-26
    • 2022-01-09
    • 2019-08-25
    • 2017-04-28
    • 2012-10-04
    相关资源
    最近更新 更多