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

发布于 2024-11-10 07:29:14 字数 654 浏览 5 评论 0原文

这个问题让我思考一个互动问题编辑代码的方法。我想知道考虑到 Mathematica 的动态功能是否可以实现这样的东西。

考虑一个表达式:

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

及其 TreeForm:

在此处输入图像描述

我希望能够直接编辑该树,然后将结果转换回 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.

Consider an expression:

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

And its TreeForm:

enter image description here

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:

  • rename nodes, replacing symbols
  • delete nodes, reverting their leaves to the node above
  • reorder nodes and leaves (the order of arguments)

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

轮廓§ 2024-11-17 07:29:15

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

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函数遍历树并收集节点索引的父子对(以规则的形式),结果集Rules 是 GraphPlot 期望的第一个参数。 getNodeIndexRules 函数遍历树并构建哈希表,其中键是节点索引,值是节点本身。 makeSymbolRule 函数获取节点并返回 index:>node-var-symbol 形式的延迟规则。延迟规则很重要,这样符号就不会计算。这用于将节点树中的符号插入到InputField[Dynamic[]]中。

使用方法如下:首先创建一棵树:

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

然后渲染它:

renderTree[root]

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

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原生树结构被重新用作树的骨架,其中所有信息片段(头和原子)都被符号替换。只要我们能够访问原始符号而不仅仅是它们的值,这仍然是一个可变树,但我们不需要考虑树的构建块 - 我们使用表达式结构。这并不是要削弱之前较长的解决方案,从概念上我认为它更清晰,并且对于更复杂的任务来说它可能仍然更好。

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;
];

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];

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], #] &)]];

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];

Then render it:

renderTree[root]

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.

EDIT

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, #] &)]
]

Here is how you use it:

renderTreeAlt[(a + b) c + d]

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文