描述
LISP语言唯一的语法就是括号要配对。
形如 (OP P1 P2 …),括号内元素由单个空格分割。
其中第一个元素OP为操作符,后续元素均为其参数,参数个数取决于操作符类型
注意:参数 P1, P2 也有可能是另外一个嵌套的 (OP P1 P2 …)
当前OP类型为add/sub/mul/div(全小写),分别代表整数的加减乘除法。简单起见,所以OP参数个数为2
举例:
- 输入:(mul 3 -7)输出:-21
- 输入:(add 1 2) 输出:3
- 输入:(sub (mul 2 4) (div 9 3)) 输出 :5
- 输入:(div 1 0) 输出:error
解决方案
众所周知,应用程序中运行的时候会有各种各样的函数的嵌套调用,甚至是递归,整个函数调用的路径(调用链)是以栈(称之为调用栈)的形式保存,异常的时候打印(或者coredump的时候来记录的吐核)都会记录逐层的调用栈信息。为什么函数逐层深入调用完毕之后能回到原来的调用点呢,就是一个配对的问题。配对也就是有明确的固定的格式来区别起始位置和结束位置,比如函数的调用入口就是起始位置,函数返回就是结束位置。针对配对的问题,可以用栈(stack)这种数据结构解决,具体的解决思路:
- 开始调用symbol-1,起始位置symbol-1-starting压栈
- symbol-1中调用symbol-2,起始位置symbol-2-starting压栈
- symbol-2调用完毕,结束位置symbol-2-ending,栈顶是symbol-2-starting,弹栈进行处理
- symbol-1调用完毕,结束位置是symbol-1-ending,栈顶是symbol-1-starting,弹栈进行处理
- 调用整个过程也完毕,调用栈也是空栈
举个栗子
举个很常见的例子,斐波那契数列:
// 数列前两项是1,后面的每一项是前面两项的和
Fib(n) = {1, 1, 2, 3, 5, 8, 13, 21, ...}
用代码程序的话,描述(描述语言:C/C++)如下:
// 计算第count个斐波那契数列的元素
int Fib(const int& count)
{
if(count < 1)
return -1; // 输入有误
else if(count < 3)
return 1; // 最开始的两项
else
return Fib(count-1) + Fib(count-2); // 后面的元素进行递归运算
}
比如要获取斐波那契数列的第5
个元素,也就是调用Fib(5)
,调用栈的轨迹如下:
- 进入
Fib(5)
,压栈,栈顶记为Fib-5-starting - 进入
Fib(4)
,压栈,栈顶记为Fib-4-starting - 进入
Fib(3)
,压栈,栈顶记为Fib-3-starting - 进入
Fib(2)
,直接返回1 - 进入
Fib(1)
,直接返回1 - 结束
Fib(3)
,弹栈Fib-3-starting,结果是Fib(2)+Fib(1) = 2
,返回2 - 结束
Fib(4)
,弹栈Fib-4-starting,结果是Fib(3)+Fib(2) = 3
,返回3 - 结束
Fib(5)
,结果是Fib(4)+Fib(3)
,上述Fib(4)
已经返回3,需要继续算Fib(3)
,压栈,栈顶记为Fib-3-starting - 进入
Fib(2)
,直接返回1 - 进入
Fib(1)
,直接返回1 - 结束
Fib(3)
,弹栈Fib-3-starting,结果是Fib(2)+Fib(1) = 2
,返回2 - 继续结束
Fib(5)
,弹栈栈顶记为Fib-5-starting,结果是Fib(4)+Fib(3) = 5
,返回5 - 调用结束,栈也是空栈,返回结果:
5
描述的问题解决
直接上代码,代码是C++,其他的同理:
#include <iostream>
#include <string>
#include <stack>
#include <sstream>
// 判断字符串是不是约定的操作符
// add:加法
// sub:减法
// mul:乘法
// div:除法
bool is_oper(const std::string& src)
{
if(src.size() != 3)
return false;
if(src != "add" && src != "sub" && src != "mul" && src != "div")
return false;
return true;
}
// 整型数字转C++字符串
std::string altos(const int& i)
{
std::stringstream ss;
ss << i;
return ss.str();
}
// 运算符和因子进行运算并且返回结果
std::string calculate(const std::string& oper, const std::string& operand1, const std::string& operand2)
{
if(oper == "add")
return altos(atoi(operand1.c_str()) + atoi(operand2.c_str()));
else if(oper == "sub")
return altos(atoi(operand1.c_str()) - atoi(operand2.c_str()));
else if(oper == "mul")
return altos(atoi(operand1.c_str()) * atoi(operand2.c_str()));
else if(oper == "div")
{
int operand_int2 = atoi(operand2.c_str());
if(operand_int2 == 0)
return "error";
else
return altos(atoi(operand1.c_str()) / operand_int2);
}
else
return "error";
}
// 进行输入字符串的判断以及运算处理
std::string lisp_like(const std::string& src)
{
std::stack<std::string> oper;
std::stack<std::string> operands;
for(int pos = 0; pos < src.size(); )
{
if(src[pos] == '(')
{
// 判断是运算符
std::string soper = src.substr(pos+1, 3);
if(is_oper(soper))
oper.push(soper);
pos += 4;
}
else if((src[pos] >= '0' && src[pos] <= '9') || src[pos] == '-')
{
// 判断是操作数
int digit_pos = pos + 1;
while(src[digit_pos] <= '9' && src[digit_pos] >= '0')
++digit_pos;
std::string operand = src.substr(pos, digit_pos - pos);
operands.push(operand);
pos = digit_pos;
}
else if(src[pos] == ')')
{
// 计算
std::string operand1 = operands.top();
operands.pop();
std::string operand2 = operands.top();
operands.pop();
std::string soper = oper.top();
oper.pop();
// 取出来早的是第二个操作数,栈的特性:后进先出
std::string result = calculate(soper, operand2, operand1);
operands.push(result); // 计算结果要入库
if(result == "error")
break;
++pos;
}
else
++pos;
}
std::string result = operands.top();
return result;
}
// 测试程序主入口
int main(const int argc, const char* argv[])
{
std::string input;
while(std::getline(std::cin, input))
{
if(input == "bye")
break;
std::cout << lisp_like(input) << std::endl;
}
return 0;
}