【问题标题】:Nightmare Expression Tree with over-constrained class具有过度约束的类的噩梦表达式树
【发布时间】:2011-04-04 15:55:43
【问题描述】:

我无意中让我的学生过度限制了用于解决以下问题的共享课程。我意识到这可能是该网站的居民可能会喜欢的问题。

第一个团队/函数 getNodes 使用带符号整数和四个操作 +、-、* 和 / 获取一个表示前缀表达式的字符串,并使用类 Node 生成相应的以空结尾的令牌链表,其中通过“右”指针链接的标记。

第二个团队/函数 getTree 采用类似的字符串,将其传递给 getNodes,并将结果节点重新链接为表达式树。

第三个团队/函数,evaluate,接受一个类似的字符串,将其传递给 getTree,并评估结果表达式树以形成答案。

紧随其后的是过度约束的 exptree.h。这个问题必须通过只写上面定义的三个函数来解决,没有额外的函数。

#ifndef EXPTREE_H_
#define EXPTREE_H_

using namespace std;

enum Ops{ADD, SUB, MUL, DIV, NUM};

class Node {
   private:
        int num;
        Ops op;
        Node *left, *right;

    public:
        friend Node *getNodes(string d);
        friend Node *getTree(string d);
        friend int evaluate (string);
    };

int evaluate(string d);
Node *getNodes(string d);
Node *getTree(string d);
#endif

可以使用的库只有这些

#include <iostream>
#include <vector>
#include <string>
#include "exptree.h" 

对于那些担心我的学生的人,我今天要指出的是,只需几个更合适的函数就可以轻松解决这个问题。我知道表达式树可以编码有理数而不仅仅是整数。我今天也会指出这一点。

这是我根据他们的规格给他们的驱动程序。

#include <iostream>
#include <string>
#include "exptree.h"
using namespace std;
void test(string s, int target) {
    int result = evaluate(s);
    if (result == target)
        cout << s << " correctly evaluates to " << target << endl;
    else
        cout << s << "(" << result 
             << ") incorrectly evaluates to " << target << endl;
}
int main() {
    test("42", 42);
    test("* - / 4 2 1 42", 42);
    test("* - / -4 +2 -1 2", -2);
    test("* - / -4 +2 -1 2            ", -2);
    test("* 9 6", 54);
    return 0;
}

您能否以尽可能优雅的方式编写这三个函数来解决这个噩梦般的问题?

【问题讨论】:

  • 大约一年前,我在我的一个课程中解决了同样的问题(我敢打赌每个人都有)。我们通过使评估多态来解决它。乘节点将它们的左右相乘,值节点将简单地返回它们的值,依此类推。使程序自行编写:) 我意识到这违反了您的“只写三个函数”的约束,但将它们介绍给它们可能会很有趣。
  • 令牌是否保证像您的示例中那样以空格分隔?
  • “唯一可以使用的库是这些”&lt;string&gt;?否则很难定义函数;-)
  • -1:答案必须编译吗?驱动程序不会。当exptree.h 包含在驱动程序中时,exptree.h 中的string 已不在全局命名空间中。
  • @rob 哇,唯一的问题是包括&lt;string&gt;using namespace std 或前缀strings。有什么大不了的?

标签: c++ expression-trees friend


【解决方案1】:

getNodesgetTree 函数在这些约束下编写起来非常简单,所以我直接跳到有趣的部分。您自然会递归地评估表达式树,但这不是一个选项,因为 eval 函数只接受一个字符串。当然,您可以将剩余的树重新字符串化为前缀表达式并在其上递归调用 eval,但这太愚蠢了。

首先,我将表达式树转换为后缀表达式,使用显式堆栈作为穷人的递归。然后我使用标准操作数堆栈对其进行评估。

#include <iostream>
#include <vector>
#include <string>
using namespace std;
#include "exptree.h" 

int evaluate(string d){
    Node* tree = getTree(d);
   //convert tree to postfix for simpler evaluation
    vector<Node*> node_stack;
    node_stack.push_back(tree);
    Node postfix_head;
    Node* postfix_tail = &postfix_head;
    while(node_stack.size() > 0){
        Node* place = node_stack.back();
        if(place->left == 0){
             if(place->right == 0){
                 postfix_tail->right = place;
                 node_stack.pop_back();
             } else {
                 node_stack.push_back(place->right);
                 place->right = 0;
             }
        } else {
            node_stack.push_back(place->left);
            place->left = 0;
        }
    }
   //evaluate postfix
    Node* place = postfix_head.right;
    vector<int> stack;
    while(place != 0){
        if(place->op != NUM){
            int operand_a, operand_b;
            operand_b = stack.back();
            stack.pop_back();
            operand_a = stack.back();
            stack.pop_back();
            switch(place->op){
                case ADD:
                    stack.push_back(operand_a + operand_b);
                    break;
                case SUB:
                    stack.push_back(operand_a - operand_b);
                    break;
                case MUL:
                    stack.push_back(operand_a * operand_b);
                    break;
                case DIV:
                    stack.push_back(operand_a / operand_b);
                    break;
            }
        } else {
            stack.push_back(place->num);
        }
        place = place->right;
    }
    return stack.back();
}

【讨论】:

  • 这是一个很好的解决方案的开始,非常正确。当我在“operand_b = stack.back();”行上运行它时,它出现了段错误。看起来你的堆栈是空的。 getnodes 很容易编写。 getTree 需要一点技巧。
【解决方案2】:

我认为“没有附加功能”是一个太苛刻的要求。最简单的实现方法,例如getTree 可能是递归的,它需要定义一个额外的函数。

Node* relink(Node* start) // builds a tree; returns the following node
{
    if (start->op == NUM)
    {
        Node* result = start->right;
        start->left = start->right = NULL;
        return result;
    }
    else
    {
        start->left = start->right;
        start->right = relink(start->left);
        return relink(start->right);
    }
}

Node* getTree(string d)
{
    Node* head = getNodes(d);
    relink(head);
    return head;
}

我可以通过使用显式堆栈(由std::vector 实现)来实现递归,但这很丑陋且晦涩难懂(除非您希望您的学生准确地练习)。

【讨论】:

  • 使用额外的两个允许递归的函数很容易编写。当然,我不希望我的学生必须做一个明确的堆栈。当我解决它时,我很惊讶这个解决方案是多么漂亮。我也期望使用显式堆栈会很丑陋。我错了。我今天带领我的学生完成了我的解决方案。它实际上非常有用。我对我的学生练习不太感兴趣;我确实希望他们解决合理的问题;这对他们来说是不合理的,但我认为 stackoverflow 的人可能会喜欢它。
【解决方案3】:

对于它的价值,这是我在发布问题之前编写的解决方案

#include <iostream>
#include <vector>
#include "exptree.h"
using namespace std;

Node *getNodes(string s) {
    const int MAXINT =(int)(((unsigned int)-1) >> 1), MININT = -MAXINT -1;
    Node *list;
    int sign, num;

    s += " ";                                                   // this simplifies a lot of logic, allows trailing white space to always close off an integer
    list = (Node *) (num = sign = 0);
    for (int i=0; i<s.size(); ++i) {
        char c = s[i];                                          // more efficient and cleaner reference to the current character under scrutiny
        if (isdigit(c)) {
            if (sign == 0) sign = 1;                            // if sign not set, then set it. A blank with a sign==0 now signifies a blank that can be skipped
            num = 10*num + c - '0';
        } else if (((c=='+') || (c=='-')) && isdigit(s[i+1])) { // another advantage of adding blank to string above so don't need a special case
            sign = (c=='+') ? 1 : -1;
        } else if ( !isspace(c) &&  (c != '+')  && (c != '-')  && (c != '*')  && (c != '/')) {
            cout << "unexpected character " << c << endl;
            exit(1);
        } else if  (!isspace(c) || (sign != 0)) {                                                       // have enough info to create next Node
            list->left = (list == 0) ? (list = new Node) : (list->left->right = new Node);              // make sure left pointer of first Node points to last Node
            list->left->right = 0;                                                                      // make sure list is still null terminated
            list->left->op = (c=='+' ? ADD : (c=='-' ? SUB : (c=='*' ? MUL : (c=='/' ? DIV : NUM))));   // choose right enumerated type
            list->left->num = (list->left->op==NUM) ? sign*num : MININT;                                // if interior node mark number for evaluate function
            num = sign = 0;                                                                             // prepare for next Node
        }
    }
    return list;
}

Node *getTree(string s) {
    Node *nodes = getNodes(s), *tree=0, *root, *node;
    vector<Node *> stack;

    if (nodes == 0) return tree;
    root = tree = nodes;
    nodes = nodes->right;
    for (node=nodes; node != 0; node=nodes) {
        nodes = nodes->right;
        if (root->op != NUM) {              // push interior operator Node on stack til time to point to its right tree
            stack.push_back(root);
            root = (root->left = node);     // set interior operator Node's left tree and prepare to process that left tree
        } else {                            
            root->left = root->right = 0;   // got a leaf number Node so finish it off
            if (stack.size() == 0) break;
            root = stack.back();            // now pop operator Node off the stack
            stack.pop_back();
            root = (root->right = node);    // set its left tree and prepare to process that left tree
        }
    }
    if ((stack.size() != 0) || (nodes != 0)) {
        cout << "prefix expression has missing or extra terms" << endl;
        exit(1);
    }
    return tree;
}

int evaluate(string s) {
    // MININT is reserved value signifying operator waiting for a left side value, low inpact since at edge of representable integers
    const int MAXINT =(int)(((unsigned int)-1) >> 1), MININT = -MAXINT -1;
    Node *tree = getTree(s);
    vector<Node *> stack;
    int v = 0;                              // this is value of a leaf node (a number) or the result of evaluating an interior node
    if (tree == 0) return v;
    do {
        v = tree->num;
        if (tree->op != NUM) {
            stack.push_back(tree);
            tree = tree->left;              // prepare to process the left subtree      
        } else while (stack.size() != 0) {  // this while loop zooms us up the right side as far as we can go (till we come up left side or are done)
            delete tree;                    // done with leaf node or an interior node we just finished evaluating
            tree = stack.back();            // get last interior node from stack
            if (tree->num == MININT) {      // means returning up left side of node, so save result for later
                tree->num = v;
                tree = tree->right;         // prepare to evaluate the right subtree
                break;                      // leave the "else while" for the outer "do while" which handles evaluating an expression tree
            } else {                        // coming up right side of an interior node (time to calculate)
                stack.pop_back();           // all done with interior node
                v = tree->op==ADD ? tree->num+v : (tree->op==SUB ? tree->num-v : (tree->op==MUL ? tree->num*v : tree->num/v)) ;
            }
        }
    } while (stack.size() != 0);
    return v;
}

【讨论】:

  • list = (Node *) (num = sign = 0); 这绝对不漂亮!
  • 我猜美丽在情人眼中。这是一种将三件事初始化为零的紧凑方式。把相关代码放到屏幕上也是一件很漂亮的事情。
  • 美丽也在编译器的眼中;)。单行
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-20
  • 1970-01-01
  • 2017-03-21
  • 1970-01-01
  • 2010-12-21
相关资源
最近更新 更多