通过Mathematica的交互树进行代码操作

这个问题让我思考一个编辑代码的交互方法。 考虑到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[]], 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的延迟规则index:>node-var-symbol 。 规则延迟很重要,这样符号不会评估。 这用于将节点树中的符号插入到InputField[Dynamic[]]

    这里是你如何使用它:首先创建一棵树:

    root  = makeExpressionTree[(b + c)*d];
    

    然后渲染它:

    renderTree[root]
    

    您必须能够修改每个输入字段中的数据,但需要点击几下鼠标才能使光标出现在那里。 例如,我编辑cc1bb1 。 然后,你得到修改后的表达式:

    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原生树结构被重新用作树的骨架,所以所有信息片段(头和原子)都被符号替换,所以该方法产生更短的代码。 只要我们能够访问原始符号而不仅仅是它们的值,这仍然是一个可变的树,但我们不需要考虑为树构建模块 - 我们使用表达式结构。 这不是为了减少以前的更长时间的解决方案,在概念上我认为它更清楚,对于更复杂的任务来说,它可能还是更好。

    链接地址: http://www.djcxy.com/p/35567.html

    上一篇: code manipulation via interactive tree for Mathematica

    下一篇: A variation of IntegerPartition?