Я приведу частичное решение, но такое, которое может помочь вам начать.Я буду использовать изменяемую структуру данных дерева из этого поста, так как кажется, что изменчивость естественна для этой проблемы.Повторим это для удобства здесь:
Module[{parent, children, value},
children[_] := {};
value[_] := Null;
node /: new[node[]] := node[Unique[]];
node /: node[tag_].getChildren[] := children[tag];
node /: node[tag_].addChild[child_node, index_] :=
children[tag] = Insert[children[tag], child, index];
node /: node[tag_].removeChild[child_node, index_] :=
children[tag] = Delete[children[tag], index];
node /: node[tag_].getChild[index_] := children[tag][[index]];
node /: node[tag_].getValue[] := value[tag];
node /: node[tag_].setValue[val_] := value[tag] = val;
];
Вот код для создания изменяемого дерева из любого выражения Mathematica и считывания выражения обратно из дерева:
Clear[makeExpressionTreeAux];
makeExpressionTreeAux[expr_?AtomQ] :=
With[{nd = new[node[]], val = Hold[Evaluate[Unique[]]]},
nd.setValue[val];
Evaluate[val[[1]]] = expr;
nd];
makeExpressionTreeAux[expr_] :=
With[{nd = new[node[]], val = Hold[Evaluate[Unique[]]]},
nd.setValue[val];
Evaluate[val[[1]]] = Head[expr];
Do[nd.addChild[makeExpressionTreeAux[expr[[i]]], i], {i, Length[expr]}];
nd];
Clear[expressionFromTree];
expressionFromTree[nd_node] /; nd.getChildren[] == {} := (nd.getValue[])[[-1, 1]];
expressionFromTree[nd_node] :=
Apply[(nd.getValue[])[[-1, 1]], Map[expressionFromTree, nd.getChildren[]]];
Clear[traverse];
traverse[root_node, f_] :=
Module[{},
f[root];
Scan[traverse[#, f] &, root.getChildren[]]];
Clear[indexNodes];
indexNodes[root_node] :=
Module[{i = 0},
traverse[root, #.setValue[{i++, #.getValue[]}] &]];
Clear[makeExpressionTree];
makeExpressionTree[expr_] :=
With[{root = makeExpressionTreeAux[expr]},
indexNodes[root];
root];
Вы можете проверитьна простых выражениях, таких как a+b
.Несколько комментариев о том, как это работает: чтобы создать изменяемое дерево выражений (построенное из node
-s) из выражения, мы вызываем функцию makeExpressionTree
, которая сначала создает дерево (вызов makeExpressionTreeAux
), а затеминдексирует узлы (вызов indexNodes
).Функция makeExpressionTree
является рекурсивной, она рекурсивно пересекает дерево выражений, копируя его структуру в структуру результирующего изменяемого дерева.Здесь есть один тонкий момент: почему нам нужны такие вещи, как val = Hold[Evaluate[Unique[]]]
, nd.setValue[val];
, Evaluate[val[[1]]] = expr;
, а не просто nd.setValue[expr]
.Это делается с учетом InputField[Dynamic[some-var]]
- для этого нам нужна переменная для хранения значения (возможно, можно написать более нестандартный Dynamic
, чтобы избежать этой проблемы, если захотите).Таким образом, после создания дерева каждый узел содержит значение Hold[someSymbol]
, тогда как someSymbol
содержит значение атома или головы для неатомарной части.Процедура индексирования изменяет значение каждого узла с Hold[sym]
на {index,Hold[symbol]}
.Обратите внимание, что он использует функцию traverse
, которая реализует общий обход изменяемого дерева в глубину (аналогично Map[f,expr, Infinity]
, но для изменяемых деревьев).Следовательно, индексы увеличиваются в порядке глубины.Наконец, функция expressionFromTree
обходит дерево и создает выражение, которое хранит дерево.
Вот код для рендеринга изменяемого дерева:
Clear[getGraphRules];
getGraphRules[root_node] :=
Flatten[
Map[Thread,
Rule @@@
Reap[traverse[root,
Sow[{First[#.getValue[]],
Map[First[#.getValue[]] &, #.getChildren[]]}] &]][[2, 1]]]]
Clear[getNodeIndexRules];
getNodeIndexRules[root_node] :=
Dispatch@ Reap[traverse[root, Sow[First[#.getValue[]] -> #] &]][[2, 1]];
Clear[makeSymbolRule];
makeSymbolRule[nd_node] :=
With[{val = nd.getValue[]},
RuleDelayed @@ Prepend[Last[val], First[val]]];
Clear[renderTree];
renderTree[root_node] :=
With[{grules = getGraphRules[root],
ndrules = getNodeIndexRules[root]},
TreePlot[grules, VertexRenderingFunction ->
(Inset[
InputField[Dynamic[#2], FieldSize -> 10] /.
makeSymbolRule[#2 /. ndrules], #] &)]];
Эта часть работает следующим образом:функция getGraphRules
пересекает дерево и собирает родительские и дочерние пары индексов узлов (в форме правил), результирующий набор правил - это то, что GraphPlot
ожидает в качестве первого аргумента.Функция getNodeIndexRules
пересекает дерево и создает хеш-таблицу, где ключи - это индексы узлов, а значения - сами узлы.Функция makeSymbolRule
берет узел и возвращает отложенное правило в виде index:>node-var-symbol
.Важно, чтобы правило было отложено, чтобы символы не оценивались.Это используется для вставки символа из дерева узлов в InputField[Dynamic[]]
.
Вот как вы можете его использовать: сначала создайте дерево:
root = makeExpressionTree[(b + c)*d];
Затем визуализируйте его:
renderTree[root]
Вы должны иметь возможность изменять данные в каждомполе ввода, хотя требуется несколько щелчков мыши, чтобы курсор появился там.Например, я отредактировал c
на c1
и b
на b1
.Затем вы получите модифицированное выражение:
In[102]:= expressionFromTree[root]
Out[102]= (b1 + c1) d
Это решение обрабатывает только модификации, но не удаление узлов и т. Д. Однако оно может быть отправной точкой и может быть расширено, чтобы охватить это.
РЕДАКТИРОВАТЬ
Вот гораздо более короткая функция, основанная на тех же идеях, но без использования изменяемой структуры данных дерева.
Clear[renderTreeAlt];
renderTreeAlt[expr_] :=
Module[{newExpr, indRules, grules, assignments, i = 0, set},
getExpression[] := newExpr;
newExpr = expr /. x_Symbol :> set[i++, Unique[], x];
grules =
Flatten[ Thread /@ Rule @@@
Cases[newExpr, set[i_, __][args___] :>
{i, Map[If[MatchQ[#, _set], First[#], First[#[[0]]]] &, {args}]},
{0, Infinity}]];
indRules = Dispatch@
Cases[newExpr, set[ind_, sym_, _] :> (ind :> sym), {0, Infinity}, Heads -> True];
assignments =
Cases[newExpr, set[_, sym_, val_] :> set[sym , val], {0, Infinity},Heads -> True];
newExpr = newExpr /. set[_, sym_, val_] :> sym;
assignments /. set -> Set;
TreePlot[grules, VertexRenderingFunction -> (Inset[
InputField[Dynamic[#2], FieldSize -> 10] /. indRules, #] &)]
]
Здеськак вы его используете:
renderTreeAlt[(a + b) c + d]
Вы можете вызвать getExpression[]
в любое время, чтобы увидеть текущее значение выражения или присвоить его любой переменной, или вы можете использовать
Dynamic[getExpression[]]
Этот метод дает гораздо более короткий код, поскольку структура дерева Mathematica повторно используется в качестве каркаса для дерева, где все информативные части (головы и атомы) были заменены символами.Это все еще изменчивое дерево, если у нас есть доступ к исходным символам, а не только к их значениям, но нам не нужно думать о строительных блоках для дерева - мы используем для этого структуру выражения.Это не умаляет более раннее более долгое решение, концептуально я думаю, что оно более ясное и, вероятно, еще лучше для более сложных задач.