В начало > Два способа подсчета палиндромов
Было дело, возник вопрос: как посчитать количество палиндромов в строке. Напомню, что палиндром - строка, правая часть которой является зеркальным отображением левой. К примеру: abccba.
Основная идея - сгенерировать все подстроки для данной строки, а затем определить, какая из них является палиндромом. Следующий код хорошо справляется с задачей:
word = 'satorarepotennetoperarotas'
words = set(word[i:j] for i in range(len(word)) for j in range(len(word),i,-1))
for w in sorted(words,key=len):
if w == ''.join(reversed(w)):
print w
Одна ремарка - этот простой пример находит не совсем палиндромы: находятся все строки, которые читаются одинаково и в прямом, и в обратном направлении.
Первый способ справляется замечательно. Однако, генерировать кучу подстрок и проверять их на "палиндромность" как-то скучно. Можно придумать способ веселее. Вспомним, что палиндромы описываются классом языков, называемых контекстно-свободными. Эти языки могут быть заданы двумя способами - либо контекстно-свободной грамматикой, либо механизмом под названием PDA (push down automata) - автомат с магазинной памятью.
PDA - это по сути недетерминированный автомат со стеком. На вершине стека расположен символ, который мы читаем при переходе. Если символ совпадает с меткой перехода, то мы можем перейти в новое состояние. При этом мы можем либо разместить символ на стеке, либо убрать. Автомат приходит в конечное состояние тогда, когда стек пустеет.
Следующий код делает всю грязную работу.
# -*- coding: utf-8 -*-
def start(tape, stack, cargo):
if not tape: return
yield (start, tape[1:], stack[:], cargo)
yield (pop, tape[:], stack[:], cargo)
def pop(tape, stack, cargo):
if not tape: return
if stack and tape[0] == stack[-1]:
yield (push, tape[1:], stack[:-1], cargo + [tape[0]]) # Change state to 'push'
yield (pop, tape[1:], stack + [tape[0]], cargo + [tape[0]]) # Loop
def push(tape, stack, cargo):
if not stack:
print .join(cargo)
if tape and stack and stack[-1] == tape[0]:
yield (push, tape[1:], stack[:-1], cargo + [tape[0]]) # Loop
def traverse(tape):
agenda = []
agenda.append((start, list(tape), [], []))
while agenda:
state, tape, stack, cargo = agenda.pop()
for aid in state(tape, stack, cargo):
agenda.append(aid)
traverse('satorarepotennetoperarotas')
Результат:
satorarepotennetoperarotas
atorarepotennetoperarota
torarepotennetoperarot
orarepotennetoperaro
rarepotennetoperar
arepotennetopera
repotennetoper
epotennetope
potennetop
otenneto
tennet
enne
nn
Здесь всего три состояния. Состояние start поглощает "лишние" символы и спонтанно переводит автомат в состояние pop. Состояние pop "забрасывает" символы на стек и спонтанно переходит на push. Последнее считывает символы со стека. Если это удается и стек опустошается (т.е. все символы на стеке считались в обратном порядке), то печатается результат, хранящийся в списке cargo.
Данный код не самый оптимальный и по скорости уступает первому (в питоновской реализации). Однако, он хорошо демонстрирует применимость PDA для работы с текстом. Данным способом можно реализовать довольно сложные схемы обработки.