通过Mathematica的交互式树进行代码处理

Mr.*_*ard 8 tree wolfram-mathematica interactive

这个问题让我思考一种编辑代码的交互方法.我想知道是否有可能在Mathematica的动态功能下实现这样的东西.

考虑一个表达式:

Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]
Run Code Online (Sandbox Code Playgroud)

它的TreeForm:

在此输入图像描述

我希望能够直接编辑该树,然后将结果转换回Mathematica代码.至少应该能够:

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

我相信有些语言或环境专门用于这种操作,我觉得这并不具有吸引力,但我有兴趣为特殊目的进行这种交互式树编辑.

Leo*_*rin 14

我将提供部分解决方案,但可以帮助您入门.我将使用帖子中的可变树数据结构,因为看起来可变性对于这个问题是很自然的.为方便起见,重复此处:

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;
];
Run Code Online (Sandbox Code Playgroud)

以下是从任何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];
Run Code Online (Sandbox Code Playgroud)

你可以测试简单的表达式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], #] &)]];
Run Code Online (Sandbox Code Playgroud)

这部分的工作原理如下:getGraphRules函数遍历树并收集节点索引的父子对(以规则的形式),得到的规则集是GraphPlot期望的第一个参数.该getNodeIndexRules函数遍历树并构建哈希表,其中键是节点索引,值是节点本身.该makeSymbolRule函数接受节点并返回表单的延迟规则index:>node-var-symbol.规则延迟很重要,因此符号不会被评估.这用于将节点树中的符号插入InputField[Dynamic[]].

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

root  = makeExpressionTree[(b + c)*d];
Run Code Online (Sandbox Code Playgroud)

然后渲染它:

renderTree[root]
Run Code Online (Sandbox Code Playgroud)

您必须能够修改每个输入字段中的数据,尽管只需点击几下就可以使光标出现在那里.例如,我编辑c成为c1b将来b1.然后,您获得修改后的表达式:

In[102]:= expressionFromTree[root]

Out[102]= (b1 + c1) d
Run Code Online (Sandbox Code Playgroud)

该解决方案仅处理修改,但不处理节点的移除等.然而,它可以是一个起点,并且可以扩展以覆盖它.

编辑

这是一个更短的功能,基于相同的想法,但不使用可变树数据结构.

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, #] &)]
]
Run Code Online (Sandbox Code Playgroud)

以下是您使用它的方式:

renderTreeAlt[(a + b) c + d]
Run Code Online (Sandbox Code Playgroud)

您可以随时调用getExpression[]以查看表达式的当前值或将其指定给任何变量,或者您可以使用

Dynamic[getExpression[]]
Run Code Online (Sandbox Code Playgroud)

此由于Mathematica的天然树结构方法产生更短的代码被再次用作用于树,其中所有信息块(头部和原子)分别为用符号内容替换的骨架.这仍然是一个可变树,只要我们可以访问原始符号而不仅仅是它们的值,但我们不需要考虑树的构建块 - 我们使用表达式结构.这不是为了减少之前的更长时间的解决方案,从概念上讲我认为它更清晰,对于更复杂的任务来说它可能更好.