Chinaunix

标题: 怎么解析一个数学表达式 [打印本页]

作者: fly3ds    时间: 2013-10-18 11:23
标题: 怎么解析一个数学表达式
char s[64],  s里存了用户输入的一行字符,  这一行字符期望是数学运算表达式, 比如 1 + 2,   2 * 3 ,  4 * (5 + 7), 7 / (2 + 5)等等,  但用户也可能输入些无效的表达式.

怎么编程实现这一套表达式解析呢?  要求输入无效的时候给出错误提示,  其次要能解析括号,  + - * / 优先级,  最好浮点数也能认出来并加以运算?  

该用什么样的结构和算法呢?    看似简单, 但是一时居然也想不到什么方法.
作者: hellioncu    时间: 2013-10-18 11:40
主要用栈  
作者: myworkstation    时间: 2013-10-18 11:46
本帖最后由 myworkstation 于 2013-10-18 11:49 编辑

回复 1# fly3ds


    二楼说的对,你可以参考这个库的实现:http://www.gnu.org/software/libmatheval/
    你可以去看看数据结构与算法中的“中缀表达式”,“后缀表达式”
作者: folklore    时间: 2013-10-18 11:58
LR (1). or LL(1)
作者: zhaohongjian000    时间: 2013-10-18 12:05
可以参考lua源代码中lparser.c中的subexpr函数的实现,这个函数用的方法在手写递归下降的编译器中挺常见,也不是很复杂。
作者: fenghw8088    时间: 2013-10-18 12:49
其实真的不简单
作者: egmkang    时间: 2013-10-18 13:11
LL
+1

lol
作者: w_anthony    时间: 2013-10-18 14:18
本帖最后由 w_anthony 于 2013-10-18 14:20 编辑

我怎么记得是前缀表达式可以比较轻松的解决这个问题,粗略的想了一下也应该是前缀表达式。
先有操作符,再取操作数,如果操作数仍然是一个操作符,那么递归进去,应该是比较简单的。
问题是只要解析生成这样的一个前缀表达式就OK了,记得可以通过生成关于表达式的一颗树,然后通过前序遍历就可以得到前缀表达式了。
作者: shan_ghost    时间: 2013-10-18 15:03
本帖最后由 shan_ghost 于 2013-10-18 15:05 编辑

偶前些天搞过一个……用来做动态调度的……

做法是通过简单解析找到其中的未知数,然后套个c的函数壳丢给gcc编译成动态链接库(太没节操了……);最后在载入模块做了一套简易版本管理逻辑去触发自动编译,以保证动态库总是最新的……

另外还搞了些乱七八糟的机制,以便在真正调用时识别参数个数和类型……
作者: w_anthony    时间: 2013-10-18 15:11
回复 9# shan_ghost


    我只能说能想到这么搞的你,不管实际效果如何,都是非常非常的牛B!
作者: fly3ds    时间: 2013-10-18 15:35
本帖最后由 fly3ds 于 2013-10-18 15:36 编辑

嗯   各位见识多广   方法不一   我也想出办法了   正在慢慢编   等完成了再贴程序
作者: folklore    时间: 2013-10-18 15:43
回复 9# shan_ghost


    小心把你当病毒杀了。
作者: cxytz01    时间: 2013-10-18 17:06
隐约记得大学课程<<编译原理>>有教怎么做,而且很easy。
作者: fly3ds    时间: 2013-10-18 17:07
回复 13# cxytz01


第一部是把输入分成 一个个的 token
作者: shan_ghost    时间: 2013-10-18 18:39
回复 12# folklore


    还好还好,用的linux,用不着傻毒软件……
作者: shan_ghost    时间: 2013-10-18 18:46
主要是考虑到自己写代码解释执行速度慢而且容易出错,能支持的功能范围还很窄。丢给gcc编译,速度快,可靠,错误提示信息详尽。

而且,甚至可以在套的c函数壳外面加上#include <math.h>之类东东,直接就能在表达式中支持sqrt、sin之类数学函数;或者#include <stdlib.h>,连枚举类字符串值都能检查……可谓随心所欲,无所不能。
作者: fly3ds    时间: 2013-10-18 20:11
本帖最后由 fly3ds 于 2013-10-18 21:37 编辑

https://github.com/fly3ds/linux_test

有兴趣看看这里面的expre.c,还是没有完成,不过照这个思路编下去,是没问题的。一个前提条件是输入要有空格隔开,只能认2 * 3 * 4,不能认2*3*4;

我这是野路子,只讲能完成这个任务, 不讲做的好不好完美完美, 漂亮不漂亮, 和学院派的LL LR什么的估计思路不大一样。
作者: sonicling    时间: 2013-10-19 00:18
真的是野路子。。。。

野路子的特点通常是仅能通过测试,一旦放开输入,各种莫名其妙的问题层出不穷。
作者: fly3ds    时间: 2013-10-19 00:19
回复 18# sonicling

还没编完呢 ,只处理了* ,  你别指望 + - * /都给你处理了. 有兴趣自己完成.

   
作者: cokeboL    时间: 2013-10-19 01:12
回复 19# fly3ds


    只处理了 * 。。。。
作者: fly3ds    时间: 2013-10-19 08:34
回复 20# cokeboL

* / + -的都加进去了 现在要搞搞优先级

   
作者: fly3ds    时间: 2013-10-20 11:41
回复 21# fly3ds


已经可以识别并计算浮点数!  

-bash-4.1$ ./expre
12 + 3 - 4 / 5 * 2.1 + 5
12 + 3 - 4 / 5 * 2.1 + 5

12 + 3 - 4 / 10.500000 + 5

12 + 3 - 0.380952 + 5

15 - 0.380952 + 5

15 - 5.380952
9.619048
-bash-4.1$

作者: 井蛙夏虫    时间: 2013-10-20 11:52
回复 8# w_anthony
我好像记得是后缀表达式,也就是逆波兰表示法。碰到操作数的时候压入堆栈,碰到操作符的时候处理。

   
作者: w_anthony    时间: 2013-10-21 09:16
回复 23# 井蛙夏虫


    你说的方法听起来挺靠谱的,我其实也记不清到底是哪种了,毕竟大学课本上的东西。
虽然前缀和后缀都可以解决这个问题,但是大学的时候应该不会深入讲递归,我想应该是你说的这个方法。
作者: lxyscls    时间: 2013-10-21 09:53
直接参考Louden的《编译原理与实践》
第四章有个现成的caculator,基本可以解决你的问题
用的是递归下降,就是不能处理浮点数
作者: 井蛙夏虫    时间: 2013-10-22 10:20
本帖最后由 井蛙夏虫 于 2013-10-22 10:21 编辑

回复 24# w_anthony
我也是在大学时学数据结构看到的这种方法。编译原理时就是说LL这些方法了。
如果会lex和yacc的话就简单的多,只需要写词法规则和语法规则,不需要自己解析了。


   
作者: w_anthony    时间: 2013-10-22 15:05
本帖最后由 w_anthony 于 2013-10-23 10:54 编辑

今天上班没什么事,无聊写了个,测试运行了下,效果不错。
方法是解析生成一颗与运算优先级相关的树,然后前缀递归计算结果。

代码BUG了,先编辑下。
现在修改过了,应该没问题了……

再优化一下,去掉了while,因为这个while的次数是可预见的。
  1. #include <stdio.h>

  2. enum ElemType
  3. {
  4.         Type_Number = 0,
  5.         Type_Root,
  6.         Type_Plus,
  7.         Type_Subtract,
  8.         Type_Prior,
  9.         Type_Multiply,
  10.         Type_Divide,
  11. };

  12. class CCalcElem
  13. {
  14. public:
  15.         CCalcElem() : m_iType(Type_Number) {}
  16.         ~CCalcElem()
  17.         {
  18.                 if (m_iType != Type_Number)
  19.                 {
  20.                         delete m_lpLeft;
  21.                         delete m_lpRight;
  22.                 }
  23.         }
  24. protected:
  25.         CCalcElem(const CCalcElem&) {}
  26.         const CCalcElem& operator=(const CCalcElem&) { return *this; }
  27. public:
  28.         int m_iType;
  29.         union {
  30.                 double m_dlNumber;
  31.                 struct {
  32.                         CCalcElem* m_lpLeft;
  33.                         CCalcElem* m_lpRight;
  34.                 };
  35.         };
  36.         CCalcElem* m_lpParent;
  37. };

  38. static bool IsBlank(char ch)
  39. {
  40.         return ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'));
  41. }

  42. int AnalyseNumber(const char*& lpExpress, CCalcElem* lpTree);

  43. static int AnalyseOperator(const char*& lpExpress, CCalcElem* lpTree)
  44. {
  45.         while (IsBlank(*lpExpress))
  46.                 ++lpExpress;
  47.         int iType;
  48.         switch (*lpExpress)
  49.         {
  50.         case '+':
  51.                 iType = Type_Plus;
  52.                 break;
  53.         case '-':
  54.                 iType = Type_Subtract;
  55.                 break;
  56.         case '*':
  57.                 iType = Type_Multiply;
  58.                 break;
  59.         case '/':
  60.                 iType = Type_Divide;
  61.                 break;
  62.         case ')':
  63.                 ++lpExpress;
  64.                 return 1;
  65.         case '\0':
  66.                 return 0;
  67.         default:
  68.                 return -1;
  69.         }
  70.         ++lpExpress;
  71.         CCalcElem* lpNode = lpTree->m_lpParent;
  72.         if (iType < Type_Prior)
  73.         {
  74.                 if (lpNode->m_iType != Type_Root)
  75.                 {
  76.                         lpNode = lpNode->m_lpParent;
  77.                         if (lpNode->m_iType != Type_Root)
  78.                                 lpNode = lpNode->m_lpParent;
  79.                 }
  80.         }
  81.         else
  82.         {
  83.                 if ((lpNode->m_iType != Type_Root) && (lpNode->m_iType > Type_Prior))
  84.                         lpNode = lpNode->m_lpParent;
  85.         }
  86.         CCalcElem* const lpElem = new CCalcElem;
  87.         lpElem->m_lpParent = lpNode;
  88.         CCalcElem* const lpNodeRight = lpNode->m_lpRight;
  89.         lpNode->m_lpRight = lpElem;
  90.         lpElem->m_iType = iType;
  91.         lpElem->m_lpLeft = lpNodeRight;
  92.         lpElem->m_lpRight = NULL;
  93.         return AnalyseNumber(lpExpress, lpElem);
  94. }

  95. int AnalyseNumber(const char*& lpExpress, CCalcElem* lpTree)
  96. {
  97.         while (IsBlank(*lpExpress))
  98.                 ++lpExpress;
  99.         if (*lpExpress == '(')
  100.         {
  101.                 ++lpExpress;
  102.                 const int iType = lpTree->m_iType;
  103.                 lpTree->m_iType = Type_Root;
  104.                 const int iResult = AnalyseNumber(lpExpress, lpTree);
  105.                 if (iResult > 0)
  106.                 {
  107.                         lpTree->m_iType = iType;
  108.                         return AnalyseOperator(lpExpress, lpTree->m_lpRight);
  109.                 }
  110.         }
  111.         else
  112.         {
  113.                 double dl;
  114.                 int count;
  115.                 if (sscanf(lpExpress, "%lf%n", &dl, &count) > 0)
  116.                 {
  117.                         lpTree->m_lpRight = new CCalcElem;
  118.                         lpTree->m_lpRight->m_lpParent = lpTree;
  119.                         lpTree->m_lpRight->m_dlNumber = dl;
  120.                         lpExpress += count;
  121.                         return AnalyseOperator(lpExpress, lpTree->m_lpRight);
  122.                 }
  123.         }
  124.         return -1;
  125. }

  126. static double CalcTree(CCalcElem* lpTree)
  127. {
  128.         if (lpTree->m_iType != Type_Number)
  129.         {
  130.                 const double dlLeft = CalcTree(lpTree->m_lpLeft);
  131.                 const double dlRight = CalcTree(lpTree->m_lpRight);
  132.                 const int iType = lpTree->m_iType;
  133.                 if (iType == Type_Plus)
  134.                         return (dlLeft + dlRight);
  135.                 else if (iType == Type_Subtract)
  136.                         return (dlLeft - dlRight);
  137.                 else if (iType == Type_Multiply)
  138.                         return (dlLeft * dlRight);
  139.                 else
  140.                         return (dlLeft / dlRight);
  141.         }
  142.         return lpTree->m_dlNumber;
  143. }

  144. static bool AnalyseExpress(const char* lpExpress, double& dlValue)
  145. {
  146.         CCalcElem nElem;
  147.         nElem.m_iType = Type_Root;
  148.         nElem.m_lpLeft = NULL;
  149.         nElem.m_lpRight = NULL;
  150.         if (AnalyseNumber(lpExpress, &nElem) == 0)
  151.         {
  152.                 dlValue = CalcTree(nElem.m_lpRight);
  153.                 return true;
  154.         }
  155.         return false;
  156. }

  157. int main(int, char**)
  158. {
  159.         printf("%s", "Input expression:");
  160.         char str[512];
  161.         gets(str);
  162.         double dlResult;
  163.         if (AnalyseExpress(str, dlResult))
  164.                 printf("%s = %lf\r\n", str, dlResult);
  165.         else
  166.                 printf("%s", "Invalid expression!\r\n");
  167.         return 0;
  168. }
复制代码

作者: yulihua49    时间: 2013-10-22 16:03
本帖最后由 yulihua49 于 2013-10-22 16:15 编辑
fly3ds 发表于 2013-10-18 20:11
https://github.com/fly3ds/linux_test

有兴趣看看这里面的expre.c,还是没有完成,不过照这个思路编下去 ...

用stptok去分割操作符:
rp=stptok(arg->cp,p,
                        sizeof(arg->argbuf)-(p-arg->argbuf),",+*-/%()#;");
这是我的计算器里的代码,这里不处理=,它在外部处理。
要处理:#注释,;结束。
函数执行完,判断*rp是哪个操作符即可。

stptok()可以在网上搜到,有源码。
作者: w_anthony    时间: 2013-10-22 23:19
fly3ds 发表于 2013-10-20 11:41
已经可以识别并计算浮点数!  

-bash-4.1$ ./expre
12 + 3 - 4 / 5 * 2.1 + 5
12 + 3 - 4 / 5 * 2.1 + 5

12 + 3 - 4 / 10.500000 + 5

12 + 3 - 0.380952 + 5

15 - 0.380952 + 5

15 - 5.380952
9.619048
-bash-4.1$

你不觉得第一步应该先算/,再算*么?

作者: fly3ds    时间: 2013-10-23 03:52
回复 29# w_anthony

这是常见的处理方式,不过我看不是必需的。
   
作者: fly3ds    时间: 2013-10-23 04:00
回复 27# w_anthony


你这程序不错,看起来很舒服,虽然我看的不太懂 -_-;不像我,只会用点数组指针低级的东西,搞了一天不不止也没搞出像样的东西。
作者: w_anthony    时间: 2013-10-23 09:58
回复 30# fly3ds


    不是,你没明白我的意思。我是说你这条算式应该先算前面的除号,如果先算后面的乘号,计算结果肯定是错了。
A / B * C显然和A / (B * C)不是同一个答案




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2