Использование стека для постфиксных вычислений



Стеки

Абстрактный стек

Рассмотрим широко известный абстрактный тип данных cтек. Стек является коллекцией в том смысле, что эта структура данных содержит многочисленные элементы. Другие коллекции, с которыми мы встречались, это словари и списки.

Абстрактный тип данных определяется операциями, которые можно выполнять над ним, или, другими словами, интерфейсом. Интерфейс стека включает следующие операции:

__init__

Инициализировать новый пустой стек.

push

Добавить новый элемент к стеку.

pop

Удалить и вернуть элемент из стека. Удаляемый и возвращаемый элемент — всегда последний добавленный в стек элемент.

is_empty

Проверить, пуст ли стек.

Стек иногда называют структурой данных last in, first out (англ.: последним вошел, первым вышел), или LIFO, потому, что последний добавленный в стек элемент всегда извлекается первым.

Реализация стека с помощью списка

Операции над списком, которые предоставляет Python, похожи на операции из интерфейса стека. Напишем код, который реализует операции над стеком с помощью операций над списком.

Этот код будет реализацией абстрактного типа данных стек. Вообще, реализация есть набор методов, которые синтаксически и семантически удовлетворяют требованиям интерфейса.

Вот реализация стека, использующая список Python:

class Stack :

def __init__(self):

   self.items = []

 

def push(self, item):

   self.items.append(item)

 

def pop(self):

   return self.items.pop()

 

def is_empty(self):

   return (self.items == [])

Класс Stack содержит атрибут items, список Python, содержащий элементы стека. Инициализирующий метод присваивает переменной items пустой список.

Чтобы поместить новый элемент в стек, push добавляет его в конец списка items.

 

Чтобы извлечь элемент из стека, pop использует одноименный метод списка, удаляющий и возвращающий последний элемент списка.

 

И, наконец, чтобы проверить, пуст ли стек, is_empty сравнивает items с пустым списком.

Подобную реализацию, где методы состоят из вызова других существующих методов, иногда называют фасадом. Программисты используют эту метафору, чтобы описать код, скрывающий детали реализации и предоставляющий более простой, или более стандартный, интерфейс.

Помещение в стек и извлечение из стека

Наш стек является обобщенной структурой данных. Это значит, что мы можем поместить в него элемент любого типа. В следующем примере в стек помещаются два целых числа и строка:

>>> s = Stack()

>>> s.push(54)

>>> s.push(45)

>>> s.push("+")

С помощью is_empty и pop мы можем извлечь и вывести на печать все элементы стека:

while not s.is_empty():

print s.pop(),

Получаем + 45 54. Иными словами, мы только что использовали стек, чтобы распечатать элементы в обратном порядке! Хотя это и не стандартный формат вывода списка на печать, однако, с использованием стека вывести список в обратном порядке оказалось очень просто.

Интересно сравнить этот фрагмент кода с реализацией print_backward из предыдущей главы. Существует естественная аналогия между рекурсивной версией print_backward и данным алгоритмом, использующим стек. Разница в том, что print_backward использует стек среды выполнения для размещения узлов списка, и распечатывает их на выходе из рекурсии. Наш стековый алгоритм делает то же самое, только использует объект Stack вместо стека среды выполнения.

Использование стека для постфиксных вычислений

В большинстве языков программирования, математические выражения записываются с операторами между операндов, например, 1 + 2. Такая запись называется инфиксной. Альтернативной формой записи выражений является постфиксная, или обратная польская запись, в которой оператор следует за операндами: 1 2 +.

Постфиксное выражение естественным образом вычисляется с помощью стека:

1. Начав слева, будем брать один терм (оператор или операнд) за один раз.

o Если терм операнд, поместим его в стек.

o Если терм оператор, извлечем два операнда из стека, выполним над ними операцию, и поместим результат в стек.

2. Когда мы доберемся до конца выражения, на стеке останется ровно один операнд. Это и будет результат.

Лексический анализ

Для того, чтобы реализовать описанный алгоритм, необходимо пройти по строке и разбить ее на операнды и операторы. Этот процесс есть ни что иное, как лексический анализ, а его результат – отдельные части строки – называются лексемами. Возможно, вы помните эти понятия из главы 1.

Python предоставляет функции split в модулях string и re (от англ. regular expressions – регулярные выражения). Функция string.split разбивает строку на части, используя указанный символ в качестве разделителя. Например:

>>> import string

>>> string.split("Now is the time", " ")

['Now', 'is', 'the', 'time']

В данном случае разделителем является пробел, и строка разбивается по пробелам.

Более мощная функция re.split позволяет использовать в качестве разделителя регулярное выражение. Регулярное выражение – это способ описания целого множества строк, удовлетворяющих заданному условию. Например, [A-z] есть множество всех букв латинского алфавита, а [0-9] есть множество всех цифр. Оператор ^ “отрицает” множество, так что [^0-9] означает все символы, кроме цифр. Это именно то, что нам нужно для разбиения на лексемы постфиксного выражения:

>>> import re

>>> re.split("([^0-9])", "123 456+8/")

['123', ' ', '456', '+', '8', '/', '']

Обратите внимание, что порядок аргументов re.split отличается от порядка аргументов функции string.split, здесь разделитель идет перед анализируемой строкой.

Результирующий список лексем включает операнды 123, 456, 8, а также операторы + и /. Он также содержит пробел и пустую строку.

Мы посмотрим на следующие реализации стека:

§ list

§ collections.deque

§ queue.LifoQueue


Дата добавления: 2022-12-03; просмотров: 100; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!