问题描述
|
这个问题使我考虑了一种用于编辑代码的交互式方法。考虑到Mathematica的动态功能,我想知道是否可以实现这样的事情。
考虑一个表达式:
Text[Row[{PaddedForm[currentTime,{6,3},NumberSigns -> {\"\",\"\"},NumberPadding -> {\"0\",\"0\"}]}]]
和它的TreeForm
:
我希望能够直接编辑该树,然后将结果转换回Mathematica代码。一个人至少应该能够:
重命名节点,替换符号
删除节点,将其叶子还原到上面的节点
重新排列节点和叶子的顺序(参数的顺序)
我相信有些语言或环境专门从事这种操纵,但我并不认为这很吸引人,但是我有兴趣为特殊目的进行这种交互式树编辑。
解决方法
我将提供部分解决方案,但可以帮助您入门。我将使用本文中的可变树数据结构,因为看来可变性对于这个问题是很自然的。为了方便起见,在此重复:
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[]],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
这样的简单表达式。关于它是如何工作的一些评论:要从一个表达式创建一个可变的表达式树(由ѭ5built-s构建),我们调用makeExpressionTree
函数,该函数首先创建树(调用makeExpressionTreeAux
),然后索引节点(调用call7ѭ)。 indexNodes
)。 “ 6”函数是递归的,它递归地遍历表达式树,同时将其结构复制到生成的可变树的结构中。这里的一个微妙之处是为什么我们需要诸如val = Hold[Evaluate[Unique[]]]
,nd.setValue[val];
,ѭ12things之类的东西,而不仅仅是nd.setValue[expr]
。这是在考虑了“ 14”的前提下完成的-为此,我们需要一个变量来存储值(也许,如果有人喜欢,可以编写一个更自定义的“ 15”以避免这种问题)。因此,在创建树之后,每个节点包含的值是Hold[someSymbol]
,而someSymbol
包含一个原子或头部的值(对于非原子子部分)。索引过程将每个节点的值从“ 18”更改为“ 19”。请注意,它使用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
函数获取节点并返回returns28ѭ形式的延迟规则。延迟规则很重要,这样符号就不会求值。这用于将符号从节点树插入到“ 29”中。
使用方法如下:首先创建一棵树:
root = makeExpressionTree[(b + c)*d];
然后渲染它:
renderTree[root]
您必须能够修改每个输入字段中的数据,尽管需要单击几次才能使光标出现在其中。例如,我将“ 32”编辑为“ 33”,将“ 34”编辑为“ 35”。然后,您将获得修改后的表达式:
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),Infinity},Heads -> True];
assignments =
Cases[newExpr,set[_,val_] :> set[sym,val],Heads -> True];
newExpr = newExpr /. set[_,val_] :> sym;
assignments /. set -> Set;
TreePlot[grules,VertexRenderingFunction -> (Inset[
InputField[Dynamic[#2],FieldSize -> 10] /. indRules,#] &)]
]
使用方法如下:
renderTreeAlt[(a + b) c + d]
您可以随时调用getExpression[]
来查看表达式的当前值或将其分配给任何变量,或者可以使用
Dynamic[getExpression[]]
由于Mathematica本机树结构被重新用作树的骨架,其中所有信息部分(头部和原子)均被符号替换,因此该方法产生的代码更短。只要我们可以访问原始符号而不仅仅是它们的值,这仍然是一棵可变的树,但是我们不需要考虑该树的构建块-我们使用表达式结构。这并不是要减少以前的较长解决方案,从概念上讲,我认为它是更清楚的,对于更复杂的任务,它可能仍然更好。