通过Mathematica的交互式树进行代码操作 [英] code manipulation via interactive tree for Mathematica

查看:102
本文介绍了通过Mathematica的交互式树进行代码操作的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题使我考虑一种用于编辑代码的交互式方法.考虑到Mathematica的动态功能,我想知道是否可以实现这样的事情.

This question caused me to ponder an interactive method for editing code. I wonder if it is possible to implement something like this given the dynamic capabilities of Mathematica.

考虑一个表达式:

Text[Row[{PaddedForm[currentTime, {6, 3}, NumberSigns -> {"", ""}, NumberPadding -> {"0", "0"}]}]]

及其TreeForm:

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

I would like to be able to edit that tree directly, and then have the result translated back into Mathematica code. One should at least be able to:

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

我相信有些语言或环境专门从事这种操纵,但我并不认为这很吸引人,但是我有兴趣为特殊目的进行这种交互式树形编辑.

I believe that there are languages or environments which specialize in this kind of manipulation, and I do not find that attractive, but I am interested in having this kind of interactive tree editing for special purposes.

推荐答案

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

I will provide a partial solution, but the one that could get you started. I will use the mutable tree data structure from this post, since it seems like mutability is natural for this problem. Repeating it for convenience here:

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表达式创建可变树并从树中读回表达式的代码:

Here is the code to create a mutable tree from any Mathematica expression, and read the expression back from the tree:

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之类的简单表达式.关于它如何工作的一些评论:要从表达式创建可变的表达式树(由 -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函数遍历该树并构建该树存储的表达式.

You can test on simple expressions like a+b. A few comments on how this works: to create a mutable expression tree (built of node-s) from an expression, we call the makeExpressionTree function, which first creates the tree (call to makeExpressionTreeAux), and then indexes the nodes (call to indexNodes). The makeExpressionTree function is recursive, it recursively traverses the expression tree while copying its structure to the structure of the resulting mutable tree. One subtle point here is why we need things like val = Hold[Evaluate[Unique[]]], nd.setValue[val];, Evaluate[val[[1]]] = expr; rather than just nd.setValue[expr]. This is done with InputField[Dynamic[some-var]] in mind - for this, we need a variable to store the value (perhaps, one could write a more custom Dynamic to avoid this problem if one likes). So, after the tree is created, each node contains a value that is Hold[someSymbol], while someSymbol contains the value of an atom, or of a head, for non-atomic sub-part. The indexing procedure changes the value of each node from Hold[sym] to {index,Hold[symbol]}. Note that it uses the traverse function which implements the generic depth-first mutable tree traversal (similar to Map[f,expr, Infinity], but for mutable trees). Therefore, indexes are incremented in depth-first order. Finally, the expressionFromTree function traverses the tree and builds the expression that the tree stores.

以下是渲染可变树的代码:

Here is the code to render the mutable tree:

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[]]中.

This part works as follows: the getGraphRules function traverses the tree and collects parent-child pares of node indices (in the form of rules), the resulting set of rules is what the GraphPlot expects as a first argument. The getNodeIndexRules function traverses the tree and builds the hash table where keys are node indices and values are the nodes themselves. The makeSymbolRule function takes the node and returns the delayed rule of the form index:>node-var-symbol. It is important that the rule is delayed, so that the symbols do not evaluate. This is used to insert the symbol from the node tree into InputField[Dynamic[]].

这里是使用方法:首先创建一棵树:

Here is how you can use it: first create a tree:

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

然后渲染它:

renderTree[root]

您必须能够修改每个输入字段中的数据,尽管需要单击几下才能使光标出现在其中.例如,我将c编辑为c1,并且将b编辑为b1.然后,您将获得修改后的表达式:

You must be able to modify data in each input field, although it takes a few clicks to make the cursor appear there. For example, I edited c to be c1 and b to be b1. Then, you get the modified expression:

In[102]:= expressionFromTree[root]

Out[102]= (b1 + c1) d

此解决方案仅处理修改,而不处理节点等.但是,它可以作为起点,并且可以扩展为涵盖该范围.

This solution handles only modifications, but not removal of nodes etc. It can however be a starting point, and be extended to cover that as well.

编辑

这是一个简短得多的函数,基于相同的想法,但未使用可变树数据结构.

Here is a much shorter function, based on the same ideas but not using the mutable tree data structure.

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[]来查看表达式的当前值或将其分配给任何变量,或者可以使用

You can call getExpression[] at any time to see the current value of expression or assign it to any variable, or you can use

Dynamic[getExpression[]]

由于Mathematica本机树结构被重新用作树的骨架,其中所有有用的信息(头部和原子)均被符号替换,因此该方法产生的代码要短得多.只要我们可以访问原始符号而不仅仅是它们的值,这仍然是一棵可变的树,但是我们不必考虑为该树构建模块-我们使用表达式结构.这并不是要减少以前的较长解决方案,从概念上讲,我认为这是更清楚的,对于更复杂的任务,它可能仍然更好.

This method yields much shorter code since the Mathematica native tree structure is reused as a skeleton for the tree, where all informative pieces (heads and atoms) were replaces by symbols. This is still a mutable tree as long as we have access to original symbols and not just their values, but we don't need to think about building blocks for the tree - we use expression structure for that. This is not to diminish the previous longer solution, conceptually I think it is more clear, and it is probably still better for more complicated tasks.

这篇关于通过Mathematica的交互式树进行代码操作的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆