程序员修仙之路- CXO让我做一个计算器!!


菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧

CEO,CTO,CFO于一身的CXO

X总,咱不会买一个吗?

菜菜

那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除运算就行,很简单

CEO,CTO,CFO于一身的CXO

(尼玛)那能不能给我涨点工资呀?

菜菜

公司现在很困难,你这个计算器关系到公司的存亡,你要注意呀!!

CEO,CTO,CFO于一身的CXO

(关于撇开话题佩服的五体投地)好吧X总,我尽快做

菜菜

给你一天时间,我这里着急要用

CEO,CTO,CFO于一身的CXO

.........

菜菜
CXO的需求果然还在继续,深呼吸,深呼吸 .......


有人说数据结构是为算法服务的,我还要在加一句:数据结构和算法都是为业务服务的!!

CXO的需求果然不同凡响,又让菜菜想到了新的数据结构:

◆◆
栈的特性
◆◆

定义


栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。


栈作为一种数据结构,其中有几个特性需要提起大家注意:


1.  操作受限:何为操作受限?在栈的操作中,一般语言中针对栈的操作只有两种:入栈和出栈。并且操作只发生在栈的顶部。 有的同学会问,我用其他数据结构也一样能实现栈的效果。不错,但是每种数据结构都有自己的使用场景,没有一种绝对无用的数据结构。

2.  栈在数据结构上属于一种线性表,满足后进先出的原则。这也是栈的最大特性,几乎大部分后进先出的场景都可以使用栈这个容器。比如一个函数的调用过程中,局部变量的存储就是栈原理。当执行一个函数结束的时候,局部变量其实最先释放的是最后的局部变量。

◆◆
实现
◆◆

在内存分布上栈是用是实现的呢?既然栈是一种线性结构,也就说可以用线性的内存分布数据结构来实现。


1. 数组实现栈(顺序栈):数组是在内存分布上连续的一种数据结构。经过以前的学习,我们知道数组的容量是不变的。如果业务上可以知道一个栈的元素的最大数量,我们完全可以用数组来实现。为什么这么说?因为数组的扩容在某些时候性能是比较低的。因为需要开辟新空间,并发生复制过程。

class MyStack

{

//数组容器

int[] container = new int[100];

//栈顶元素的索引

int TopIndex = -1;

//入栈操作

public void Push(int newValue)

{

if (TopIndex >= 99)

{

return ;

}

TopIndex++;

container[TopIndex] = newValue;

}

//出栈操作

public int Pop()

{

if (TopIndex < 0)

{

return 0;

}

var topValue = container[TopIndex];

TopIndex--;

return topValue;

}

}


2. 链表实现栈(链式栈):为了应对数组的扩容问题,我们可以用链表来实现栈。栈的顶部元素永远指向链表的头元素即可。具体代码有兴趣的同学可以实现一下。

由以上可以看出,栈其实是基于基础数据结构之上的一个具体业务形式的封装即:先进后出。

◆◆
性能
◆◆

基于数组的栈我们暂且只讨论未发生数组重建的场景下。无论是数组实现还是链表实现,我们发现栈的内部其实是有一个指向栈顶元素的指针,不会发生遍历数组或者链表的情形,所以栈的出栈操作时间复杂度为O(1)。

至于入栈,如果你看过我以前介绍数组和链表的文章,你可以知道,给一个数组下标元素赋值的操作时间复杂度为O(1),在链表头部添加一个元素的操作时间复杂度也是O(1)。所以无论是数组还是链表实现栈,入栈操作时间复杂度也是O(1)。并且栈只有入栈出栈两种操作,比其他数据结构有N个操作方法要简单很多,也不容易出错。

至于发生数组重建,copy全部数据的过程其实是一个顺序栈最坏的时间复杂度,因为和原数组的元素个数n有关,所以时间复杂度为O(n)

◆◆
设计要点
◆◆

那一个计算器怎么用栈来实现呢?其实很多编译器就是通过两个栈来实现的,其中一个栈保存操作的数,另一个栈保存运算符。

我们从左到右遍历表达式,当遇到数字,我们直接压入操作数栈;当遇到操作符的时候,当前操作符与操作符栈顶的元素比较优先级(先乘除后加减的原则)。如果当前运算符比栈顶运算符优先级高,那说明不需要执行栈顶运算符运算,我们直接将当前运算符也入栈;

如果当前运算符比栈顶运算符优先级低,那说明该执行栈顶运算符的运算了。然后出栈运算符栈顶元素,数据栈顶两个元素,然后进行相关运算,然后把运算结果再次压入数据栈。

◆◆
来一发吧
◆◆
 class Program    {        static void Main(string[] args)        {            List<string> lstAllData = new List<string>();            //读取输入的表达式,并整理            string inputStr = Console.ReadLine();            string tempData = "";            for (int i = 0; i < inputStr.Length; i++)            {                if (inputStr[i] == ‘+‘ || inputStr[i] == ‘-‘ || inputStr[i] == ‘*‘ || inputStr[i] == ‘/‘)                {                    lstAllData.Add(tempData);                    lstAllData.Add(inputStr[i].ToString());                    tempData = "";                }                else                {                    tempData += inputStr[i];                }                if(i== inputStr.Length - 1)                {                    lstAllData.Add(tempData);                }            }            foreach (var item in lstAllData)            {                Calculator.Cal(item.ToString());            }            var ret = Calculator.GetResult();            Console.WriteLine(ret);            Console.Read();        }

    }    //计算器    class Calculator    {        //存放计算数据的栈        static Stack<int> DataStack = new Stack<int>();        //存放操作符的栈        static Stack<string> OperatorStack = new Stack<string>();        public static int Cal(string dataOrOperator)        {            int data;            bool isData = int.TryParse(dataOrOperator, out data);            if (isData)            {                //如果是数据直接入数据栈                DataStack.Push(data);            }            else            {                //如果是操作符,和栈顶操作符比较优先级,如果大于栈顶,则直接入栈,否则栈顶元素出栈 进行操作                if (OperatorStack.Count <= 0)                {                    OperatorStack.Push(dataOrOperator);                }                else                {                    //当前运算符的优先级                    var currentOpePrecedence = OperatorPrecedence(dataOrOperator);                    //当前运算符栈顶元素的优先级                    var stackTopOpePrecedence = OperatorPrecedence(OperatorStack.Peek());                    if (currentOpePrecedence > stackTopOpePrecedence)                    {                        //如果当前运算符的优先级大于栈顶元素的优先级,则入栈                        OperatorStack.Push(dataOrOperator);                    }                    else                    {                        //运算符栈顶元素出栈,数据栈出栈两个元素,然后进行运算                        var stackOpe = OperatorStack.Pop();                        var data2 = DataStack.Pop();                        var data1 = DataStack.Pop();                        var ret = CalculateData(stackOpe, data1, data2);                        DataStack.Push(ret);                        OperatorStack.Push(dataOrOperator);                    }                }            }            return 0;        }        //获取表达式最后的计算结果        public static int GetResult()        {            var ret = 0;            while (OperatorStack.Count > 0)            {                var stackOpe = OperatorStack.Pop();                var data2 = DataStack.Pop();                var data1 = DataStack.Pop();                ret = CalculateData(stackOpe, data1, data2);                DataStack.Push(ret);            }            return ret;        }        //根据操作符进行运算,这里可以抽象出接口,请自行实现        static int CalculateData(string operatorString, int data1, int data2)        {            switch (operatorString)            {                case "+":                    return data1 + data2;                case "-":                    return data1 - data2;                case "*":                    return data1 * data2;                case "/":                    return data1 + data2;                default:                    return 0;            }        }        //获取运算符优先级        public static int OperatorPrecedence(string a)    //操作符优先级        {            int i = 0;            switch (a)            {                case "+": i = 1; break;                case "-": i = 1; break;                case "*": i = 2; break;                case "/": i = 2; break;            }            return i;

        }    }

运行结果:

10+20*3+10-10+20-20+60*2190
golang版本
package stack

import (    "errors"    "fmt")

type Stack struct {    Element []interface{} //Element}

func NewStack() *Stack {    return &Stack{}}

func (stack *Stack) Push(value ...interface{}) {    stack.Element = append(stack.Element, value...)}

//返回下一个元素func (stack *Stack) Top() (value interface{}) {    if stack.Size() > 0 {        return stack.Element[stack.Size()-1]    }    return nil //read empty stack}

//返回下一个元素,并从Stack移除元素func (stack *Stack) Pop() (value interface{}) {    if stack.Size() > 0 {        d := stack.Element[stack.Size()-1]        stack.Element = stack.Element[:stack.Size()-1]        return d    }    return nil}

//交换值func (stack *Stack) Swap(other *Stack) {    switch {    case stack.Size() == 0 && other.Size() == 0:        return    case other.Size() == 0:        other.Element = stack.Element[:stack.Size()]        stack.Element = nil    case stack.Size() == 0:        stack.Element = other.Element        other.Element = nil    default:        stack.Element, other.Element = other.Element, stack.Element    }    return}

//修改指定索引的元素func (stack *Stack) Set(idx int, value interface{}) (err error) {    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {        stack.Element[idx] = value        return nil    }    return errors.New("Set失败!")}

//返回指定索引的元素func (stack *Stack) Get(idx int) (value interface{}) {    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {        return stack.Element[idx]    }    return nil //read empty stack}

//Stack的sizefunc (stack *Stack) Size() int {    return len(stack.Element)}

//是否为空func (stack *Stack) Empty() bool {    if stack.Element == nil || stack.Size() == 0 {        return true    }    return false}

//打印func (stack *Stack) Print() {    for i := len(stack.Element) - 1; i >= 0; i-- {        fmt.Println(i, "=>", stack.Element[i])    }}

package calculator

import (    "calculator/stack"    "strconv")

type Calculator struct{}

var DataStack *stack.Stackvar OperatorStack *stack.Stack

func NewCalculator() *Calculator {    DataStack = stack.NewStack()    OperatorStack = stack.NewStack()    return &Calculator{}}

func (c *Calculator) Cal(dataOrOperator string) int {

    if data, ok := strconv.ParseInt(dataOrOperator, 10, 64); ok == nil {        //如果是数据直接入数据栈        // fmt.Println(dataOrOperator)        DataStack.Push(data)    } else {

        //如果是操作符,和栈顶操作符比较优先级,如果大于栈顶,则直接入栈,否则栈顶元素出栈 进行操作        if OperatorStack.Size() <= 0 {            OperatorStack.Push(dataOrOperator)        } else {            //当前运算符的优先级            currentOpePrecedence := operatorPrecedence(dataOrOperator)            //当前运算符栈顶元素的优先级            stackTopOpePrecedence := operatorPrecedence(OperatorStack.Top().(string))            if currentOpePrecedence > stackTopOpePrecedence {                //如果当前运算符的优先级大于栈顶元素的优先级,则入栈                OperatorStack.Push(dataOrOperator)            } else {                //运算符栈顶元素出栈,数据栈出栈两个元素,然后进行运算                stackOpe := OperatorStack.Pop()                data2 := DataStack.Pop()                data1 := DataStack.Pop()

                ret := calculateData(stackOpe.(string), data1.(int64), data2.(int64))                DataStack.Push(ret)                OperatorStack.Push(dataOrOperator)            }        }    }    return 0}

func (c *Calculator) GetResult() int64 {    var ret int64    for {

        if OperatorStack.Size() > 0 {            stackOpe := OperatorStack.Pop()            data2 := DataStack.Pop()            data1 := DataStack.Pop()

            ret = calculateData(stackOpe.(string), data1.(int64), data2.(int64))

            DataStack.Push(ret)        } else {            break        }    }

    return ret}

func calculateData(operatorString string, data1, data2 int64) int64 {    switch operatorString {    case "+":        return data1 + data2    case "-":        return data1 - data2    case "*":        return data1 * data2    case "/":        return data1 + data2    default:        return 0    }}

func operatorPrecedence(a string) int {    i := 0    switch a {    case "+":        i = 1    case "-":        i = 1    case "*":        i = 2    case "/":        i = 2    }    return i}

package main

import (    "calculator/calculator"    "flag"    "fmt")

var (    inputStr = flag.String("input", "", "请输入..."))

func main() {    flag.Parse()

    var lstAllData []string    var tempData string

    rs := []rune(*inputStr)    for i := 0; i < len(rs); i++ {        if string(rs[i]) == "+" || string(rs[i]) == "-" || string(rs[i]) == "*" || string(rs[i]) == "/" {            lstAllData = append(lstAllData, tempData)            lstAllData = append(lstAllData, string(rs[i]))            tempData = ""        } else {            tempData += string(rs[i])        }        if i == len(rs)-1 {            lstAllData = append(lstAllData, tempData)        }    }

    ca := calculator.NewCalculator()    for _, v := range lstAllData {        ca.Cal(v)    }    ret := ca.GetResult()    fmt.Println(ret)}
运算结果:go run program.go  -input=1+2-1*3结果:0

X总的个人空间需求并没有结束,菜菜仍然在持续优化中,欢迎大佬指正

菜菜出品
一个和大家一起成长的公众号

原文地址:https://www.cnblogs.com/zhanlang/p/10259343.html

时间: 2024-10-06 11:29:26

程序员修仙之路- CXO让我做一个计算器!!的相关文章

程序员修仙之路--优雅快速的统计千万级别uv

菜菜,咱们网站现在有多少PV和UV了? Y总,咱们没有统计pv和uv的系统,预估大约有一千万uv吧 写一个统计uv和pv的系统吧 网上有现成的,直接接入一个不行吗? 别人的不太放心,毕竟自己写的,自己拥有主动权.给你两天时间,系统性能不要太差呀 好吧~~~ 定义PV是page view的缩写,即页面浏览量,通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标.网页浏览数是评价网站流量最常用的指标之一,简称为PV UV是unique visitor的简写,是指通过互联网访问.浏览这个网页的自

程序员修神之路--做好分库分表其实很难之一(继续送书)

菜哥,领导让我开发新系统了 这么说领导对你还是挺信任的呀~ 必须的,为了设计好这个新系统,数据库设计我花了好多心思呢 做一个系统我觉得不应该从数据库入手,应该从设计业务模型开始,先不说这个,说说你的数据库设计的优势 为了高性能我首先设计了分库 分表策略,为以后打下基础 那你的数据量将来会很大吗?分库分表其实涉及到很多难题,你了解过吗? 我觉得分库分表很容易呀 是吗? 是否需要分 说到数据库分库分表,不能一味的追求,我们要明白为什么要进行分库分表才是最终目的.现在网上一些人鼓吹分库分表如何应对了多

程序员修神之路--redis做分布式锁可能不那么简单

菜哥,复联四上映了,要不要一起去看看? 又想骗我电影票,对不对? 呵呵,想去看了叫我呀 看来你工作不饱和呀 哪有,这两天我刚基于redis写了一个分布式锁,很简单 不管你基于什么做分布式锁,你觉得很简单吗?来来来 在计算机世界里,对于锁大家并不陌生,在现代所有的语言中几乎都提供了语言级别锁的实现,为什么我们的程序有时候会这么依赖锁呢?这个问题还是要从计算机的发展说起,随着计算机硬件的不断升级,多核cpu,多线程,多通道等技术把计算机的计算速度大幅度提升,原来同一时间只能执行一条cpu指令的时代已

程序员修神之路--为什么有了SOA,我们还用微服务?

菜菜哥,我最近需要做一个项目,老大让我用微服务的方式来做 那挺好呀,微服务现在的确很流行 我以前在别的公司都是以SOA的方式,SOA也是面向服务的方式呀 的确,微服务和SOA有相同之处 面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和契约联系起来.接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台.操作系统和编程语言.这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互.它是一种设计方法,其中包

程序员修神之路--高并发下为什么更喜欢进程内缓存

菜菜哥,告诉你一个好消息 YY妹子,什么好消息,你有男票了? 不是啦,我做的一个网站,以前经常由于访问量太大而崩溃,现在我加上了缓存,很稳定啦 加的什么缓存呢? 我用的redis,号称业界最快的缓存组件了 你觉得现在的缓存操作应该是最快的了吗? 是的,我觉得没有缓存能比这种模式更快了 你先停停,我给你先讲个故事 进程内缓存是指缓存和应用程序在相同地址空间.即同一个进程内.分布式缓存是指缓存和应用程序位于不同进程的缓存,通常部署在不同服务器上. 从前有个机构,机构的主人叫做 CPU,这个机构专门派

程序员修神之路--高并发优雅的做限流(有福利)

菜菜哥,有时间吗? YY妹,什么事? 我最近的任务是做个小的秒杀活动,我怕把后端接口压垮,X总说这可关系到公司的存亡 简单呀,你就做个限流呗 这个没做过呀,菜菜哥,帮妹子写一个呗,事成了,以后有什么要求随便说 那好呀,先把我工资涨一下 那算了,我找别人帮忙吧 跟你开玩笑呢,给哥2个小时时间 谢谢菜菜哥,以后你什么要求我都答应你 好嘞,年轻人就是豪爽 ◆◆ 技术分析 ◆◆ 如果你比较关注现在的技术形式,就会知道微服务现在火的一塌糊涂,当然,事物都有两面性,微服务也不是解决技术,架构等问题的万能钥匙

程序猿修仙之路--数据结构之设计高性能访客记录系统

菜菜呀,最近我有个想法呀! (心想:又尼玛有折磨人的想法了.) X总,您说~ 我想给咱们的用户做个个人空间,目前先有访客记录就可以,最近访问的人显示在最上边,由于用户量有十几亿,可能对性能要求比较高,三天后上线,你做一下吧! (心想:一万头羊驼飘过!!)  但是X总,个人空间访问量比较大,需要设计,测试等环节,三天不够呀!~ 这个关系到公司的生死存亡,你加加班就行了``` (心想:一亿头羊驼!!) 好吧,X总,我尽最大努力! 苦笑中....~ 需求要点 每个用户都有自己的个人空间,当有其他用户来

zz 游戏程序员的学习之路(中文版)

游戏程序员的学习之路(中文版) Milo Yip · 1 天前 感谢 @楚天阔(tkchu)编写脚本及整理中文译本数据,自动从英文版生成中文版,SVG / PDF 版本中的书籍图片现在链接至豆瓣页面. Github miloyip/game-programmer 检视/下载中文版 SVG / PDF 「真诚赞赏,手留余香」 赞赏 15 人赞赏 程序员游戏开发书籍推荐 分享 举报 977 文章被以下专栏收录 Milo的编程 进入专栏 97 条评论 写下你的评论 trycatch 这是劝退吧...

菜鸟程序员的成长之路(三)——2014,逝去的半年,奋斗的半年

从3月份到现在,仅仅半年的时间让我扮演了两个完全不同的角色,从在校生一下变成了毕业生,作为毕业生不能再像在校生一样自由自在,无所顾忌,想怎样就怎样,肆无忌惮的生活,浪费时间.如果你想从容的面临未来的生活,就需要彻头彻尾的改变.多一份稳重,多一份责任,多一份担当. 鉴于LZ不太擅长写非技术博文,那就以碎碎念的形式,来回顾一下我的奋斗历程: 技术 3月份开始备战软考,软考准备了两个多月的时间,从看视频做笔记,再到大家一起讲课,复习,做试题巩固,整个过程至今历历在目.软考虽然不难,但是对于基础差的同学