栈的应用 - 仿LISP字符串运算

描述

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;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值