This question让我思考一种编辑代码的交互方法。我想知道是否有可能在Mathematica的动态功能下实现这样的东西。
考虑一个表达式:
Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]
及其TreeForm
:
我希望能够直接编辑该树,然后将结果转换回Mathematica代码。至少应该能够:
我相信有些语言或环境专门用于这种操作,我并不觉得这很有吸引力,但我有兴趣为特殊目的进行这种交互式树编辑。
答案 0 :(得分:14)
我将提供部分解决方案,但可以帮助您入门。我将使用来自this post的可变树数据结构,因为看起来可变性对于这个问题是很自然的。为方便起见,重复一遍:
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
函数,该函数首先创建树(调用{{1然后索引节点(调用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[]
此方法产生的代码要短得多,因为Mathematica原生树结构被重新用作树的骨架,其中所有信息部分(头部和原子)都被符号替换。这仍然是一个可变树,只要我们可以访问原始符号而不仅仅是它们的值,但我们不需要考虑树的构建块 - 我们使用表达式结构。这不是为了减少之前的较长解决方案,从概念上讲我认为它更清晰,对于更复杂的任务来说可能更好。