Рекурсивная работа с древовидной структурой: как получить состояние "всего" дерева?
Во-первых, контекст:
В качестве побочного проекта я строю систему компьютерной алгебры в Python, которая дает шаги, необходимые для решения уравнения.
До сих пор мне удавалось анализировать алгебраические выражения и уравнения в дереве выражений. Это структурировано что-то вроде этого (не фактический код - возможно, не работает):
# Other operators and math functions are based off this.
# Numbers and symbols also have their own classes with 'parent' attributes.
class Operator(object):
def __init__(self, *args):
self.children = args
for child in self.children:
child.parent = self
# the parser does something like this:
expr = Add(1, Mult(3, 4), 5)
Помимо этого, у меня есть ряд функций, которые работают рекурсивно, чтобы упростить выражения. Они не являются чисто функциональными, но я стараюсь не полагаться на изменчивость операций, вместо этого возвращая измененную копию узла, с которым я работаю. Каждая функция выглядит примерно так:
def simplify(node):
for index, child in enumerate(node.children):
if isinstance(child, Operator):
node.children[index] = simplify(node)
else:
# perform some operations to simplify numbers and symbols
pass
return node
Задача заключается в части "шаг за шагом". Я хотел бы, чтобы мои функции "упрощения" были всеми вложенными генераторами, которые "дают" шаги, необходимые для решения чего-либо. Таким образом, в принципе, каждый раз, когда каждая функция выполняет операцию, я хотел бы иметь возможность сделать что-то вроде этого: yield (deepcopy(node), expression, "Combined like terms.")
так что все, что полагается на эту библиотеку, может вывести что-то вроде:
5x + 3*4x + 3
5x + 12x + 3 Simplified product 3*4x into 12x
17x + 3 Combined like terms 5x + 12x = 17x
Тем не менее, каждая функция имеет только знания о node
он работает, но понятия не имеет, что в целом expression
похоже.
Итак, это мой вопрос: как лучше всего поддерживать "состояние" всего дерева выражений, чтобы каждый "шаг" знал все выражение?
Вот решения, которые я придумала:
- Выполняйте каждую операцию на месте и используйте глобальную переменную или переменную экземпляра в классе для хранения указателя на уравнение. Мне это не нравится, потому что юнит-тестирование сложнее, так как теперь я должен сначала настроить класс. Вы также теряете другие преимущества более функционального подхода.
- Пройдите через корень выражения к каждой функции. Однако это либо означает, что я должен повторять каждую операцию, чтобы также обновить выражение, либо что мне приходится полагаться на изменчивость.
- Пусть функция верхнего уровня "реконструирует" дерево выражений на основе каждого шага, который я получаю. Например, если я уступаю
5x + 4x = 9x
, пусть функция верхнего уровня найдет узел (5x + 4x) и заменит его на '9x'. Это кажется лучшим решением, но как лучше "реконструировать" каждый шаг?
Два последних связанных вопроса: имеет ли это смысл? У меня сейчас много кофеина, и я понятия не имею, насколько я чист.
Я слишком беспокоюсь о изменчивости? Это случай преждевременной оптимизации?
2 ответа
Вы можете спросить о молниях дерева. Проверьте: Функциональная жемчужина: плетение в сети и посмотрите, относится ли это к тому, что вы хотите. Прочитав ваш вопрос, я думаю, что вы просите сделать рекурсию по древовидной структуре, но сможете вернуться к вершине по мере необходимости. Молнии действуют как "хлебная крошка", чтобы позволить вам вернуться к предкам дерева.
У меня есть реализация одного в JavaScript.
Вы используете польскую запись для построения дерева?
Для пошагового упрощения вы можете просто использовать цикл до тех пор, пока в дереве не будет произведено никаких модификаций (операций).