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

Posted

技术标签:

【中文标题】通过 Mathematica 的交互式树进行代码操作【英文标题】:code manipulation via interactive tree for Mathematica 【发布时间】:2011-09-02 13:42:42 【问题描述】:

This question 让我开始思考编辑代码的交互式方法。考虑到 Mathematica 的动态功能,我想知道是否有可能实现这样的东西。

考虑一个表达式:

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

还有它的TreeForm

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

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

我相信有专门从事这种操作的语言或环境,我不觉得这很有吸引力,但我对这种用于特殊目的的交互式树编辑很感兴趣。

【问题讨论】:

我认为自然的方式是使用XXX/Link之类的orange.biolab.si/doc/catalog10/Classify/…(我的意思是,只是界面,不是分类部分) 您能告诉我们这些特殊用途吗?我很难想象这会有什么用。 @Sjoerd,对不起,我之前忘了回答你。我没有任何宏伟的计划,它只是有时可能有用的替代方案。还有其他问题,如 MathCAD、SPICE 和(我不记得另一个),它们使用了可视化块组装范例。这对于一般编程来说会很乏味,但它确实有它的位置。 【参考方案1】:

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

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 形式的延迟规则。延迟规则很重要,这样符号就不会计算。这用于将符号从节点树插入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 原生树结构被重新用作树的骨架,其中所有信息片段(头部和原子)都被符号替换。只要我们可以访问原始符号而不仅仅是它们的值,这仍然是一棵可变树,但是我们不需要考虑树的构建块——我们为此使用表达式结构。这并不是要减少之前较长的解决方案,从概念上讲我认为它更清晰,并且对于更复杂的任务可能仍然更好。

【讨论】:

唉,我现在没有时间解释这是如何工作的,但我会在时间允许的情况下尽快解释。 好吧,它可以按照承诺来修改叶子。可惜没有其他人认为这很有趣可以投票...... @acl 也许,对于大多数人来说,太多的代码无法快速理解重点。对我来说,这是一个有趣的练习。此外,将表达式转换为可变树并返回的代码非常通用,也许可以找到其他用途。无论如何,谢谢你的支持! Leonid,我会留出一些时间来理解这一点,但请不要认为我忘恩负义。 @Mr.Wizard 请注意,我从未想过这个想法。顺便说一句,我添加了一些解释,希望能让代码不那么晦涩。

以上是关于通过 Mathematica 的交互式树进行代码操作的主要内容,如果未能解决你的问题,请参考以下文章

MacOS | Wolfram Mathematica 12.0

mathematica中怎么用if函数控制图形的显示与隐藏

在 Mathematica 7 中取消/注释掉代码的键盘快捷键?

如何在 C# 中实现交互式决策树

4W字,最强Matplotlib 实操指南

如何用mathematica制作混沌摆动画?