目录
1 栈概览
- 栈是线性集合,遵从后进先出原则( Last - in first - out , LIFO )原则
- 栈常用的操作包括压入( push ) 和弹出( pop )
- 栈的应用
- 将中缀表达式转换为后缀表达式,并且计算后缀表达式的值
- 回溯算法
- 管理计算机内存以支持函数和方法调用
- 支持应用程序中的撤消功能
- 维护Web浏览器访问链接的历史记录
2 使用栈
2.1 栈接口
- 栈不是Python的内建类型,可以用列表代替,但是列表可以在任何位置插入,删除,替换元素,这些违背了栈作为一种抽象数据类型的本意,因此需要为栈定义一种更为严格的接口
栈方法 | 作用 |
s.isEmpty() | 如果为空返回True,否则返回False |
__len__(s) | 和len(s)相同,返回s中的项的数目 |
__str__(s) | 和str(s)相同,返回s的字符串表示 |
s.__iter__() | 和iter(s)或for item in s:相同;从底部像顶部,访问s中的每一项 |
s.__contains__(item) | 和item in s相同,如果item在s中,返回True,否则返回False |
s1.__add__(s2) | 和s1+s2相同,返回一个新的栈,其中包含了s1和s2中的项 |
s.__eq__(anyObject) | 和s==anyObject相同,如果s等于anyObject,返回True,否则返回False。如果在对应位置的项都是相同的,这两个栈就是相等的 |
s.clear() | 将s清空 |
s.peek() | 返回s顶部的项。先验条件:s不能为空,如果栈为空的话,将会抛出一个KeyError |
s.push(item) | 在s的顶部添加一项 |
s.pop() | 在s的顶部删除一项并返回该项。先验条件:s不能为空。如果栈为空的话,将会抛出一个KeyError |
- pop和peek方法有一个重要的先验条件,如果不满足这个先验条件的话,将会导致一个异常。
- 这个接口的优点是,用户将知道使用哪一个方法,以及这些方法接收什么参数,而不必管选择了哪一种栈实现
操作 | 该操作之后栈的状态 | 返回值 | 说明 |
s = <Stack Type>() | 初始化,栈为空 | ||
s.push(a) | a | 栈只包含一个项a | |
s.push(b) | a b | b是栈顶 | |
s.push(c) | a b c | c是栈顶 | |
s.isEmpty() | a b c | False | 这个栈不为空 |
len(s) | a b c | 3 | 栈中包含3个项 |
s.peek() | a b c | c | 返回栈顶的值,但并不删除它 |
s.pop() | a b | c | 删除并返回栈顶的值,b现在成为栈顶 |
s.pop() | a | b | 删除并返回栈顶的值,a现在成为栈顶 |
s.pop() | a | 删除并返回栈顶的值 | |
s.isEmpty() | True | 栈现在为空 | |
s.peek() | KeyError | 查看一个空的栈会导致一个异常 | |
s.pop() | KeyError | 弹出一个空的栈会导致一个异常 | |
s.push(d) | d | d是栈顶 |
2.2 初始化一个栈
s1 = ArrayStack()
s2 = LinkedStack([20, 40, 60])
2.3 示例应用程序:匹配括号
示例表达式 | 状态 | 原因 |
(...)...(...) | 匹配 | |
(...)...(... | 不匹配 | 末尾漏掉了一个) |
)...(...(...) | 不匹配 | 最开头的结束),没有一个匹配的开始(; 第2个开始的(,没有对应的结束圆括号 |
[...(...)...] | 匹配 | |
[...(...]...) | 不匹配 | 方括号的部分没有正确地嵌套 |
-
用栈检查一个表达式的步骤如下
- 扫描表达式,将开始的括号压入栈中
- 当遇到一个结束的括号时,如果栈为空,或者如果栈项的项不是相同的类型开始括号,括号不匹配
- 如果是正确类型的括号,从栈顶弹出一项,继续扫描表达式
- 当到达表达式未尾时,栈应为空,否则括号不匹配
- Python代码:
- index 方法返回列表中项的位置
- 可以自定义括号的类型
# -*- encoding: utf-8 -*-
# Author: ZFT
"""
Check expressions for matching brackets
"""
from Data_structure.Chapter7.linkedstack import LinkedStack
def bracketBalance1(exp):
"""exp is a string that represents the expression."""
stk = LinkedStack() # Create a new stack
for ch in exp: # Scan across the expression
if ch in ['[', '(']: # Push an opening bracket
stk.push(ch)
elif ch in [']', ')']: # Process a closing bracket
if stk.isEmpty(): # Not balanced
return False
chFromStack = stk.pop()
# Brackets must be of same type and match up
if ch == ']' and chFromStack != '[' or \
ch == ')' and chFromStack != '(':
return False
return stk.isEmpty() # They all matched up
def bracketBalance2(exp, startBracketList = ['(', '['],endBracketList = [')',']'] ):
"""exp is a string that represents the expression.
startBracketList is the list of start bracket list.
endBracketList is the list of end bracket list.
precondition:startBracketList must have the same length of endBracketList.
raise: Exception if startBracketList don't have the same length of endBracketList.
"""
if len(startBracketList) != len(endBracketList):
raise Exception("The startBracketList must have the same length with the endBracketList.")
stk = LinkedStack()
for ch in exp:
if ch in startBracketList:
stk.push(ch)
elif ch in endBracketList:
if stk.isEmpty():
return False
chFromStack = stk.pop()
if chFromStack != startBracketList[endBracketList.index(ch)]:
return False
return stk.isEmpty()
def main():
exp = input("Enter a bracketed expression:")
if bracketBalance1(exp):
print ("OK")
else:
print("Not OK")
if __name__ == "__main__":
main()
3 栈的3种应用
3.1 后缀表达式
3.1.1 计算算数表达式
- 首先,将一个表达式从我们熟悉的中缀形式转换为后缀形式,然后,计算这种后缀形式
- 在中缀形式中,每个运算符位于两个运算数之间
- 在后缀形式中,运算符紧跟在其运算数之后
中缀形式 | 后缀形式 | 值 |
34 | 34 | 34 |
34 + 22 | 34 22 + | 56 |
34 + 22 * 2 | 34 22 2 * + | 78 |
34 * 22 + 2 | 34 22 * 2 + | 750 |
(34 + 22)* 2 | 34 22 + 2 * | 112 |
- 在两种形式之中,运算数都按照相同的顺序出现。然而,运算符则不是按照相同的顺序出现的。中缀形式有时候需要圆括号,而后缀形式不需要。中缀形式的计算涉及优先级的规则,而后缀形式则只要一遇到运算符就应用它
- 在中缀表达式中,圆括号和运算符优先级的使用是为了方便人们读写和书写表达式。通过删除这些圆括号,等价的后缀式一以一种更加容易和高效计算的格式展示给计算机
3.1.2 计算后缀表达式
- 计算一个后缀表达式涉及3个步骤:
- 从左到右的遍历表达式,遇到运算数,则将其压入数字栈中
- 碰到一个运算符时,从数字栈中弹出两个运算数,对其应用运算符,并将所得的结果压入数字栈
- 继续遍历,直到到达表达式的未尾,此时,数字栈中只剩表达式的值
# token 指的是一个运算数或一个运算符
Create a new stack
While there are more tokens in the expression
Get the next token
If the token is an operand
Push the operand onto the stack
Else if the token is an operator
Pop the top two operands from the stack
Apply the operator to the two operands just popped
Push the resulting value onto the stack
Return the value at the top of the stack
计算一个后缀表达式的过程
后缀表达式: 4 5 6 * + 3 - 结果值:31
后缀表达式目前扫描到的部分 运算数栈 说明 还没有扫描到标记。栈为空 4 4 将运算数4压入栈 4 5 4 5 将运算数5压入栈 4 5 6 4 5 6 将运算数6压入栈 4 5 6 * 4 30 用栈顶的两个运算数的乘积来替换 4 5 6 * + 34 用栈顶的两个运算数的和来替换 4 5 6 * + 3 34 3 将运算数3压入栈 4 5 6 * + 3 - 31 用栈顶的两个运算数的差来替换 弹出最终的结果3.1.3 将中缀表达式转换为后缀表达式
- 将中缀表达式转换为后缀表达式的步骤:
- 开始的时候,有一个空的后缀表达式和一个空的栈,栈用来保存运算符和左圆括号
- 从左向右扫描中缀表达式
- 遇到一个运算数的时候,将其添加到后缀表达式的后面
- 遇到一个左圆括号的时候,将其压入到栈中
- 遇到一个运算符,从栈中弹出和它具有相等的或更高优先级的所有运算符,将它们添加到后缀表达式的末尾,然后,将扫描到的运算符压入栈中
- 遇到一个右圆括号的时候,将运算符从栈中移动到后缀表达式中,直到遇到了与之匹配的左圆括号,并将其丢弃
- 遇到中缀表达式结束的时候,将栈中剩下的运算符都转移到后缀表达式之中
一个中缀表达式转换为后缀表达式的过程
中缀表达式:4 + 5 * 6 - 3 后缀表达式:4 5 6 * + 3
当前扫描到的中缀表达式
的位置
运算符栈 后缀表达式 说明 还没有看到标记,堆栈和后缀表达式都是空的 4 4 将4添加到后缀表达式 4 + + 4 将+压入运算符栈 4 + 5 + 4 5 将5添加到后缀表达式 4 + 5 * + * 4 5 将*压入运算符栈 4 + 5 * 6 + * 4 5 6 将6添加到后缀表达式 4 + 5 * 6 - - 4 5 6 * + 将*和+弹出栈,并将它们添加到后缀表达式,并将-压入运算符栈 4 + 5 * 6 - 3 - 4 5 6 * + 3 将3添加到后缀表达式 4 + 5 * 6 - 3 4 5 6 * + 3 - 将剩下的运算符从栈中弹出,并将其添加到后缀表达式从中缀表达式到后缀表达式的转换过程(带括号)
中缀表达式:(4 + 5) *(6 - 3) 后缀表达式:4 5 + 6 3 - *
当前扫描到的中缀表达式
的位置
运算符栈 后缀表达式 说明 还没有看到标记,堆栈和后缀表达式都是空的 ( ( 将(压入运算符栈 (4 ( 4 将4添加到后缀表达式 (4 + ( + 4 将+压入运算符栈 (4 + 5 ( + 4 5 将5添加到后缀表达式 (4 + 5) 4 5 + 弹出栈直到遇到(,并且将运算符添加到后缀表达式 (4 + 5)* * 4 5 + 将*压入运算符栈 (4 + 5) *( *( 4 5 + 将(压入运算符栈 (4 + 5) *(6 *( 4 5 + 6 将6添加到后缀表达式 (4 + 5) *(6 - *( - 4 5 + 6 将-压入运算符栈 (4 + 5) *(6 - 3 *( - 4 5 + 6 3 将3添加到后缀表达式 (4 + 5) *(6 - 3) * 4 5 + 6 3 - 弹出栈直到遇到(,并且将运算符添加到后缀表达式 (4 + 5) *(6 - 3) 4 5 + 6 3 - * 将剩下的运算符从栈中弹出,并将其添加到后缀表达式3.2 回溯算法
- 回溯算法从一个预定义的起始状态开始,随后从一个状态移动到另一个状态,以搜索想要的最终状态
- 在任何状态下,如果有多个选择,则会随机选取一种状态,然后继续
- 如果算法到达了不希望结果的一个状态,它会回到上一个拥有一个末探索的、可替代选项的位置,并尝试这个可替代选项
- 最后,要么算法到达了想要的结果,要么穷尽了对所有算法的搜索
- 使用栈实现回溯算法的伪代码:
Create an empty stack
Push the starting state onto the stack
While the stack is not empty
Pop the stack and examine the state
If the state represents an ending state
Return SUCCESSFUL CONCLUSION
Else if the state has not been visited previously
Mark the state as visited
Push onto the stack all unvisited adjacent states
Return UNSUCCESSFUL CONCLUSION
- 在很多的游戏逻辑和谜题求解程序中,可以看到广泛的回溯算法的应用
3.3 内存管理
- 在程序执行过程中,其代码和数据都会占用计算机的内存。计算机的运行时间系统必须记录对程序员来说不可见的各种细节。这包括:
- 将变量和内存中存储的数据对象关联起来,以便引用这些变量的时候能够找到它们
- 记住所调用的方法或函数中的指令地址,以便当函数或方法完成执行的时候能够,执行返回到下一条指令
- 为函数或方法的参数和临时变量分配内存,这些参数和临时变量只有在方法或函数执行的时候才存在
- PVM( Python Virtual Machine ) 运行时的环境架构
- 从下往上,这些区域包括:
- Python虚拟机(PVM),它执行一个Python程序。PVM有两个内部变量,名为locationCounter和basePtr。locationCounter指向PVM下一步将要执行的程序。basePtr指向基本活动记录的顶部。
- 程序的所有子例程的字节码
- 程序的模块和类变量
- 调用栈。每次调用子例程的时候,都会创建一条活动记录并且将其压入到调用栈。当一个子例程完成执行并且将控制返回到调用它的子例程的时候,该活动记录从栈中弹出。栈中的活动记录的总数等于在执行的各种阶段当前调用的总数。
- 未使用的内存。这个区域的大小会增加或缩小以响应调用栈和对象堆的需求
- 对象堆。在Python中,所有的对象都存在于一个叫做堆的区域中。当对象实例化的时候,PVM必须在堆上寻找用于该对象的空间,并且当对象不再需要的时候,PVM的垃圾收集程序会恢复该空间以供将来使用。当空间变得较少的时候,堆会进一步扩展到标记未使用内存的区域
- 调用子例程的步骤
- 创建子例程的活动记录,并将其压入栈
- 在标记为 Prev basePtr 的区域中保存 basePtr 的当前值,并将 basePtr 设置为新的活动记录位置
- 在 Retrun Address 中,保存 locationCounter 的当前值,并将 locationCounter 设置为被调用子例程的第1条指令
- 将调用参数复制到 Parameters 的区域中
- 开始执行位于 locationCounter 所指示位置的子程序
- 在子例程执行的过程中,通过给 basePtr 加上一个偏移量,以引用活动记录中的临时变量和参数
- 在返回之前,一个子例程将其返回值存储在Return Value 的位置。
- 当子例程执行完成后,PVM执行以下操作:
- 使用活动记录中存储的值来恢复locationCounter 和 basePtr 的值,从而重新建立调用子例程所需的设置
- 从调用栈中弹出活动记录
- 在locationCounter 所指示的位置,继续指定调用子例程
4 栈的实现
4.1 测试驱动程序
- 代码:
# -*- encoding: utf-8 -*-
# Author: ZFT
from Data_structure.Chapter7.arraystack import ArrayStack
from Data_structure.Chapter7.linkedstack import LinkedStack
def test(stackType):
# Test an implementation with the same code.
s = stackType()
print("Length:", len(s))
print("Empty:",s.isEmpty())
print("Push 1-10")
for i in range(10):
s.push(i + 1)
print("Peeking:", s.peek())
print("Items(bottom to top):", s)
print("Length:", len(s))
print("Empty:", s.isEmpty())
theClone = stackType(s)
print("Items in clone(bottom to top)", theClone)
theClone.clear()
print("Length of clone after clear:",len(theClone))
print("Push 11")
s.push(11)
print("Popping items(top to bottom):", end = " ")
while not s.isEmpty():
print(s.pop(), end = " ")
print("\nLength:", len(s))
print("Empty:", s.isEmpty())
def main():
test(LinkedStack)
if __name__ == '__main__':
main()
4.2 将栈添加到集合层级中
- 一个集合可以实现可以通过成为集合层级的一部分,从而自由地获取一些功能
- 两个栈实现ArrayStack和LinkedStack可以按照相同的方式来处理。它们都实现了相同的、名为StackInterface的接口。它们都是AbstractStack的子类,而AbstractStack类又是AbstractCollection的一个子类。它们从AbstractStack类继承了add方法,从AbstractCollection继承了_size变量、isEmpty()、__len__、__str__、__add__和__eq__方法。因此,在ArrayStack和LinkedStack中,要实现的方法只有__init__、peek、push、pop、clear和__iter__方法
- 栈资源的层级图如下:
4.3 数组实现
- 在数组的尾部来添加和删除节点
- 当数组填满或者装填因子小于 1/4 时,需要对数组的容量进行调整
- 代码:
# -*- encoding: utf-8 -*-
# Author: ZFT
from Data_structure.Chapter4.arrays import Array
from Data_structure.Chapter7.abstractstack import AbstractStack
class ArrayStack(AbstractStack):
"""An array-based stack implementation."""
DEFAULT_CAPACITY = 10 # For all array stacks
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which includes the
contents of sourceCollection, if it's present.
"""
self._items = Array(ArrayStack.DEFAULT_CAPACITY)
AbstractStack.__init__(self, sourceCollection)
# Accessors
def __iter__(self):
"""Supports iteration over a view of self.
Visits items from bottom to top of stack.
"""
cursor = 0
while cursor < len(self._items):
yield self._items[cursor]
cursor += 1
def peek(self):
"""Returns the item at top of the stack.
Precondition: the stack is not empty.
Raises KeyError if the stack is empty.
"""
if self.isEmpty():
raise KeyError("The stack is Empty.")
return self._items[len(self._items) - 1]
# Mutator
def clear(self):
"""Makes self become empty."""
self._size = 0
self._items = Array(ArrayStack.DEFAULT_CAPACITY)
def push(self, item):
"""Inserts item at top of the stack."""
# Resize array size here if necessary
self._items[len(self._items)] = item
self._size += 1
def pop(self):
"""Removes and returns the item at top of the stack.
Precondition: the stack is empty.
Raises KeyError if the stack is empty.
PostCondition: the top item is removed from the stack.
"""
if self.isEmpty():
raise KeyError("The stack is Empty.")
oldItem = self._items[len(self._items) - 1]
self._size -= 1
# Resize the array size here if necessary
return oldItem
4.4 链表实现
- 在链表序列的头部来添加和删除节点
- 迭代遍历
- 尽管链表结构支持简单的push和pop,但__iter__方法的实现还是很复杂的,因为必须从链表结构的尾部到头部来访问各项。遗憾的是,要便利一个单链表结构,必须从其头部开始并且按照next链接直到其尾部
- 可以使用递归。在__iter__方法中,我们创建了一个临时的列表,并且定义了一个递归的辅助函数,它接收一个节点作为参数。在初次调用该函数的时候,参数节点是栈的链表结构的头(变量self._items)。如果这个节点不为None,我们使用该节点的next字段递归地调用该函数,从而一直向前直到到达结构的尾部。当这个调用返回的时候,我们将节点的数据添加到临时列表中。当辅助函数的顶级调用返回的时候,我们就返回该列表上的一个迭代器
- 代码:
# -*- encoding: utf-8 -*-
# Author: ZFT
from Data_structure.Chapter4.node import Node
from Data_structure.Chapter7.abstractstack import AbstractStack
class LinkedStack(AbstractStack):
"""Link-based stack implementation."""
def __init__(self, sourceCollecion = None):
self._items = None
AbstractStack.__init__(self, sourceCollecion)
# Accessors
def __iter__(self):
"""Supports iteration over a view of self.
Visits items from bottom to top of stack."""
def visitNodes(node):
if not node is None:
visitNodes(node.next)
tempList.append(node.data)
tempList = list()
visitNodes(self._items)
return iter(tempList)
def peek(self):
"""Returns the item at top of the stack."""
if self.isEmpty():
raise KeyError("The stack is empty.")
return self._items.data
# Mutator
def clear(self):
"""Makes self become empty."""
self._items = None
self._size = 0
def push(self, item):
"""Inserts item at top of the stack."""
self._items = Node(item, self._items)
self._size += 1
def pop(self):
"""Removes and returns the item at top of the stack.
Precondition: the stack is empty.
Raises KeyError if the stack is empty.
PostCondition: the top item is removed from the stack."""
if self.isEmpty():
raise KeyError("The stack is empty.")
oldItem = self._items.data
self._items = self._items.next
self._size -= 1
return oldItem
4.5 AbstractStack类的作用
- 定义 add 类,以避免修改AbstractCollection类
- 代码:
# -*- encoding: utf-8 -*-
# Author: ZFT
from Data_structure.Chapter7.abstractcollection import AbstractCollection
class AbstractStack(AbstractCollection):
"""An abstract stack implementation."""
def __init__(self, sourceCollection = None):
"""Sets the initial state of self, which incudes the
contents of sourceCollection, if it's present."""
AbstractCollection.__init__(self, sourceCollection)
# Mutator
def add(self, item):
"""Add item to self."""
self.push(item)
4.6 两种实现的时间和空间分析
- 时间性能
- 除了 __iter__ 方法,所有的栈方法的运行时间均不大于 O(1)
- 数组实现,在数组容量翻倍或者减半时,运行时间会增加到 O(n)
- 使用数组栈时,需要决定响应时间上的波动是否可以接受
- 数组实现,在数组容量翻倍或者减半时,运行时间会增加到 O(n)
- __iter__方法都是线性时间运行的
- 除了 __iter__ 方法,所有的栈方法的运行时间均不大于 O(1)
- 空间性能
- 链表实现使用了迭代,由于系统调用栈,从而导致内存线性增长
- 当数组的填充因子大于 1/2 时,数组的内存性能优于链表
5 案例学习:计算后缀表达式
5.1 要求
- 编写交互式程序,来计算后缀表达式
5.2 分析
- 用户交互
- 用户在提示符下输入一个表达式,程序显示结果。输入表达式时,限制在一行文本之内,标记之间可以有任意空白。用户按下Enter或 Return键后,按照每个标记之间只有一个空格的形式输出表达式。并且在后面新的一行中,输出表达式的值,或者是错误信息加上当前运行状态。
- 可能包含的错误
- 表达式包含了太多的运算数
- 表达式包含的运算数不够
- 表达式包含了不识别的标志
- 表达式包含了除以0的情况
5.3 设计
- 计算程序的交互图
- PFEvaluatorView类的实例变量和方法
PFEvaluatorView()
Create and saves a reference to the model.
run()
while True:
Retrieve the expressions string from keyboard.
Send it to the model for formatting and print the format string.
Send it to the model for evaluation.
Either print the value or catch exceptions raised by the evaluator.
ask the model for the associated details, and display error.
messages.
- PFEvaluatorModel 类的实例变量和方法
- Model 模型需要与扫描程序和计算程序进行通信。
- 方法如下:
format( expressionStr ):
Instantiate a scanner on the expression string.
Build a response string by iterating across the scanner and appending a
string representation of each token to the response string.
Return the response string.
evaluate( expressionStr ):
Ask the evaluator to evaluate the expression string.
Return the value.
evaluationStatus():
Ask the evaluator for its status.
Return the status
- PFEvaluator 类的实例变量和方法
- 计算程序的属性包括一个栈,一个扫描程序和一个名为 expressionSoFar的字符串变量
- 方法如下:
PFEvaluator( scanner )
Intialize expressionSoFar
Instantiate an ArrayStack
Save a reference to the scanner
evaluate()
Iterate across the scanner and evaluate the expression.
Raise exception in the following situation:
The scanner is None or empty
There are too many operands
There are too few operands
There are unrecognizable tokens.
A divide by 0 exception is raised by the PVM
evaluationStatus()
Return a multipart string that contains the portion of the expression
processed and the contents of the stack.
- Scanner 类的实例变量和方法
Scanner( sourceStr ):
Save a reference to the string that will be scanned and tokenized
hasNext()
Return True if the string contains another token and False otherwise.
next()
Return the next token. Raise an exception if hasnext() return False.
- Token 类的实例变量与方法
- Type定义
UNKNOWN = 0 #unknow
INT = 4 #interger
MINUS = 5 #minus operator
PLUS = 6 #plus operator
MUL = 7 #multiply operator
DIV = 8 #divide operator
- 方法如下:
Token( vlaue ):
Construct a new integer token with the specified value
Token(ch):
if ch is an operator( + - * / ), then construct a new operator token;
otherwise, construct a token of unknown type.
getType()
return a token's type
getValue()
return a token's value.
isOperator():
Return True if the token is an operator, and False Otherwise
__str__():
Return the token's numeric value as a string if the token is an
integer; otherwise, return the token's character representation.
5.4 实现
# -*- encoding: utf-8 -*-
# Author: ZFT
"""
Tokens for processing expressions.
"""
class Token(object):
UNKNOWN = 0 # unknown
INT = 4 # integer
MINUS = 5 # minus operator
PLUS = 6 # plus operator
MUL = 7 # multiply operator
DIV = 8 # divide operator
FIRST_OP = 5 # first operator code
def __init__(self, value):
if type(value) == int:
self._type = Token.INT
else:
self._type = self._makeType(value)
self._value = value
def isOperator(self):
return self._type >= Token.FIRST_OP
def __str__(self):
return str(self._value)
def getType(self):
return self._type
def getValue(self):
return self._value
def _makeType(self, ch):
if ch == '*':
return Token.MINUS
elif ch == '+':
return Token.PLUS
elif ch == '-':
return Token.MINUS
elif ch == '/':
return Token.DIV
else:
return Token.UNKNOWN
# -*- encoding: utf-8 -*-
# Author: ZFT
"""
Defines PEEvaluatorModel and PEEvaluator
"""
from Data_structure.Chapter7.PEEvaluatorModel.tokens import Token
from Data_structure.Chapter7.PEEvaluatorModel.scanner import Scanner
from Data_structure.Chapter7.arraystack import ArrayStack
class PEEvaluatorModel(object):
def evaluate(self, sourceStr):
self._evaluator = PEEvaluator(Scanner(sourceStr))
value = self._evaluator.evaluate()
return value
def format(self, sourceStr):
normalizedStr = ""
scanner = Scanner(sourceStr)
while scanner.hasNext():
normalizedStr += str(scanner.next()) + " "
return normalizedStr
def evaluationStatus(self):
return str(self._evaluator)
class PEEvaluator(object):
def __init__(self, scanner):
self._expressionSoFar = " "
self._operandStack = ArrayStack()
self._scanner = scanner
def evaluate(self):
while self._scanner.hasNext():
currentToken = self._scanner.next()
self._expressionSoFar += str(currentToken) + " "
if currentToken.getType() == Token.INT:
self._operandStack.push(currentToken)
elif currentToken.isOperator():
if len(self._operandStack) < 2:
raise AttributeError("Too few operands on the stack.")
t2 = self._operandStack.pop()
t1 = self._operandStack.pop()
result = Token(self._computeValue(currentToken, t1.getValue(), t2.getValue()))
self._operandStack.push(result)
else:
raise AttributeError("Unknown token type.")
if len(self._operandStack) > 1:
raise AttributeError("Too many operands on the stack.")
result = self._operandStack.pop()
return result.getValue()
def __str__(self):
result = "\n"
if self._expressionSoFar == "":
result += "Portion of expression processed: none\n"
else:
result += "Portion of expression processed: "+ self._expressionSoFar + "\n"
if self._operandStack.isEmpty():
result += "The stack is empty."
else:
result += "Operands on the stack: " + str(self._operandStack)
return result
def _computerValue(self, op, value1, value2):
result = 0
theType = op.getType()
if theType == Token.PLUS:
result = value1 + value2
elif theType == Token.MINUS:
result = value1 - value2
elif theType == Token.MUL:
result = value1 * value2
elif theType == Token.DIV:
result = value1 / value2
else:
raise AttributeError("Unknown operator")
return result
6 小结
- 栈是线性的集合,它只允许从一端访问,这一端叫作栈顶
- 栈上的其他操作,包括查看顶部的元素,确定元素的数目,判断栈是否为空,以及返回一个字符串表示
- 在应用中,栈以后进先出的方式管理数据项。这些应用包括匹配表达式中的方括号符号、计算后缀表达式、回溯算法,以及虚拟机上的子例程调用内存管理
- 数组和单链表结构支持栈的简单实现