【问题标题】:other ideas for calculating a string that contains e.g. "5 + 3 / 2"计算包含例如字符串的其他想法“5 + 3 / 2”
【发布时间】:2017-10-12 00:30:30
【问题描述】:

我编写了一个程序,您可以在其中提供一个字符串作为输入。这个字符串应该是一个正常的术语,例如"5 + 3 / 2" 并且所有数字和运算符都必须通过空格分隔。您可以输入的术语应该与您想要的一样长,例如“1 * 2 * 5 - 1 * 4 + 1 + 5 + 3 + 3 + 3”也应该可以工作。 +、-、* 和 / 是唯一允许使用的运算符。

我已经有了一个工作代码。但它忽略了 * 和 / 在 + 和 - 之前的事实。但它完美地完成了其他所有事情。 这个想法是它创建了两个数组,一个将运算符保存在一个字符数组中(称为字符运算符[]),另一个数组将整数保存在一个浮点数组中(称为浮点值[])。那么我有这个计算方法:

    void calc(float values[], char operators[]) {
    float res_final;
    float res_array[10];
    int counter = (sizeof(values) / sizeof(*values));

    for (int i = 0; i < getBorder(values); i++) {
        if (i == 0) {
            res_array[i] = switchFunction(values[i], values[i + 1], operators[i]);
        }

        res_final = switchFunction(res_array[i], values[i + 2], operators[i + 1]);
        res_array[i+1] = res_final;
        if (i == getBorder(values)) {
            break;
        }
    }
    std::cout << "evaluation of expression is: " << res_final << std::endl;
    }

    float switchFunction(float val_1, float val_2, char op) {
    switch (op) {
    case '+': return val_1 + val_2;
        break;
    case '-': return val_1 - val_2;
        break;
    case '*': return val_1 * val_2;
        break;
    case '/': return val_1 / val_2;
        break;
    }
    return 0;
    }

好吧,代码不是很漂亮,但我想不出更有用的东西。我有很多想法,但在运营商方面都失败了。我想在“+”中定义正常的+,其余的也一样,但这不起作用。

因此,如果您对如何在行前包含点有任何建议,或者您对我的方法有完全不同的方法,我会很高兴听到:)

【问题讨论】:

  • 您有什么具体问题吗?如果不是 - 您的问题与 SO 无关。你在找code review吗?
  • sizeof(values) / sizeof(*values) 在函数内部不起作用。并且要计算一个中缀表达式,您需要转换为reverse Polish notation
  • 但它忽略了 * 和 / 在 + 和 - 之前的事实。但它完美地完成了其他所有事情。 -- 愚人金。要真正让它正常工作,你基本上必须放弃你所做的一切,并正确实现递归下降解析器,或某种类型的类来获取该表达式并将其转换为反向波兰符号。不要试图将代码硬塞进你当前的实现中来让它工作——你最终会得到你无法维护、理解或在必要时扩展的代码(例如,如果你想添加括号)。

标签: c++ string


【解决方案1】:

从长远来看,您希望创建一个表示公式的对象。

一个好的结构应该是一棵树。这种树的内部节点是一个运算符,而叶子是一个数字。

然后你编写一个解析器,将一个字符串解析成一棵树。我会这样递归:

FormulaNode parse(input){
    string left, right;
    if(split_string(input, * or /, left, right){
        return FormulaNode(* or /, parse(left), parse(right))
    if(split_string(input, + or -, left, right){
        ...
    }
    return FormulaNode(number, to_value(string))
}

split_string 是一种尝试通过某个符号拆分字符串的方法,如果可能,则返回一个布尔值并将其拆分为左右引用,

FormulaNode(symbol, left child, right child) 是创建内部节点的构造函数,

FormulaNode(number, value) 是一个创建叶子的构造函数。

当然,这一切都是伪代码,不想给你强加一种风格,只是为了说明原理。第二个构造函数可能只有签名FormulaNode(const double)。至于符号,我建议创建类似enumerate OperatorType {addition,...} 的东西。

编辑:

这是一个更大的架构,但设计略有不同:

class FormulaTree{

    private:

    class FormulaNode{

        private:

            bool is_number;

            //used members if is number
            double value;

            //used members if not is number / is operator
            OperatorType type;
            unique_ptr<FormulaNode> left_child, right_child;

        public:

            FormulaNode(string input);
            double evaluate() const;
    };

        unique_ptr<FormulaNode> root;

    public:

        Formula(string input);
        double evaluate() const;
}

with(伪代码)

FormulaTree::FormulaNode::FormulaNode(string input){

    if(input contains * or /){

        char symbol = first occurence(input, * or /);
        vector<string> split_input= split at first occurence(input, symbol);
        type = OperatorType(symbol);

        is_number = false;
        left_child = make_unique(new FormulaNode(split_input[0]));
        right_child = make_unique(new FormulaNode(split_input[1]));
        return;
    }

    if(input contains + or -){

        ...
    }

    is_number = true;
    value = parse to int(input);
}

(从长远来看,您可能还想添加一些检查输入是否合法的内容,例如“字符串在运算符的一侧不为空”、“解析为 int 有效,不包含非法字符”等等)

(另外,如果你继续扩展它,你需要一些解析器先用括号将它分割)

如果您需要我解释有关此结构的任何内容,请直接询问,我会进行编辑。

编辑:

Slava 评论说,为不同类型派生 FormulaNode 会更好。没错,我最初编辑它是为了展示这样的设计,但我再次删除了它,因为它可能很容易让初学者感到困惑。

特别是因为这样的模式需要一些不同的布局 - 我们希望让树本身进行解析,因为派生类不应该相互认识。从长远来看,你想学习这些东西。我建议您尝试我介绍的模式,添加您自己的样式,添加更多功能(例如电源符号或使用减号表示负数的选项),然后将其放在 CodeReview 上。我的理由是无论如何这都是你想做的,当你这样做时,你的代码无论如何都会受到攻击,直到它“完美”。

【讨论】:

  • FormulaNode 应该是一个基类,可以扩展为值或表达式,而不是保留所有可能的值和标志。
  • @Slava Right,已编辑。不过,我不确定我是否应该这样做,但不想让初学者感到困惑。
  • @Slava 再次将其删除。发现它对初学者来说太混乱了。但话又说回来……嗯。不知道如何呈现这个。我认为最好的方法是如果 N30 尝试根据自己的喜好使用我的模式,然后让它在 CodeReview 上进行评估。对我或你来说,过于深入细节而不是让他尝试一下没有太大意义。可能现在甚至 unique_ptr 都是矫枉过正的。 N30:如果您这样做(即在 CodeReview 上发帖),请在此处提供链接并标记我。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
  • 2022-06-13
相关资源
最近更新 更多