Tree Mathematica的交互式树代码操作

Tree Mathematica的交互式树代码操作,tree,wolfram-mathematica,interactive,Tree,Wolfram Mathematica,Interactive,促使我思考一种编辑代码的交互式方法。考虑到Mathematica的动态功能,我想知道是否有可能实现这样的功能 考虑一个表达式: Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]] In[102]:= expressionFromTree[root] Out[102]= (b1 + c1) d 以及它的树窗体: 我希望能够直接编辑该树

促使我思考一种编辑代码的交互式方法。考虑到Mathematica的动态功能,我想知道是否有可能实现这样的功能

考虑一个表达式:

Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]
In[102]:= expressionFromTree[root]

Out[102]= (b1 + c1) d
以及它的
树窗体

我希望能够直接编辑该树,然后将结果翻译回Mathematica代码。一个人至少应该能够:

  • 重命名节点,替换符号
  • 删除节点,将其叶子恢复到上面的节点
  • 对节点和叶子重新排序(参数的顺序)

我相信有一些语言或环境专门从事这种操作,我不觉得这有什么吸引力,但我有兴趣为特殊目的进行这种交互式树编辑。

我将提供一个部分解决方案,但可以让您开始。我将使用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
这样的简单表达式。关于其工作原理的一些注释:要从表达式创建一个可变表达式树(由
节点构建),我们调用
makeExpressionTree
函数,它首先创建树(调用
makeExpressionTreeAux
),然后索引节点(调用
indexNodes
)。
makeExpressionTree
函数是递归的,它递归地遍历表达式树,同时将其结构复制到生成的可变树的结构中。这里有一个微妙的问题,就是为什么我们需要像
val=Hold[Evaluate[Unique[]]]]
nd.setValue[val]
评估[val[[1]]]=expr而不仅仅是
nd.setValue[expr]
。这是通过
InputField[Dynamic[some var]]
实现的——为此,我们需要一个变量来存储值(如果愿意,可以编写一个更自定义的
Dynamic
来避免这个问题)。因此,在创建树之后,每个节点都包含一个值,该值为
Hold[someSymbol]
,而
someSymbol
包含一个原子的值或非原子子部分的头的值。索引过程将每个节点的值从
Hold[sym]
更改为
{index,Hold[symbol]}
。请注意,它使用了
遍历
函数,该函数实现了通用的深度优先可变树遍历(类似于
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
函数遍历树并收集节点索引的父子pare(以规则的形式),得到的规则集是
GraphPlot
作为第一个参数所期望的。
getNodeIndexRules
函数遍历树并构建哈希表,其中键是节点索引,值是节点本身。
makeSymbolRule
函数获取节点并返回延迟规则,格式为
index:>node var symbol
。规则延迟很重要,这样符号就不会计算。这用于将符号从节点树插入到
InputField[Dynamic[]]

以下是如何使用它:首先创建一棵树:

root  = makeExpressionTree[(b + c)*d];
然后渲染它:

renderTree[root]
您必须能够修改每个输入字段中的数据,尽管需要单击几下才能使光标显示在那里。例如,我将
c
编辑为
c1
并将
b
编辑为
b1
。然后,得到修改后的表达式:

Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]
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原生树结构被重用为树的骨架,其中所有信息片段(头部和原子)都被符号替换。这仍然是一个可变的树,只要我们能够访问原始符号,而不仅仅是它们的值,但我们不需要考虑树的构建块——我们使用表达式结构。这并不是要减少以前较长的解决方案,从概念上讲,我认为它更清晰,对于更复杂的任务可能更好。

我认为自然的方法是使用XXX/Link和类似的东西(我的意思是,只是界面,而不是分类部分),您能告诉我们这些特殊用途吗?我很难想象这会有什么用处。@Sjoerd,对不起,我以前忘记回答你了。我没有任何宏伟的计划,这只是一个有时可能有用的替代方案。还有其他问题,如MathCAD、SPICE和(我记不起另一个)使用视觉块组装范式。这对于一般的编程来说会很乏味,但它确实有它的位置。唉,我现在没有时间来解释它是如何工作的,但我会在时间允许的情况下尽快这样做。好吧,它在修改叶子方面是按照承诺工作的。遗憾的是,没有人认为这足够有趣,可以投票…@acl也许,太多的代码让大多数人很快就明白了这一点。对我来说,这是一个有趣的练习。此外,还提供了将表达式转换为可变tr的代码