8

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.

Community
  • 1
  • 1
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • I think the natural way is to use XXX/Link and something like this http://orange.biolab.si/doc/catalog10/Classify/InteractiveTreeBuilder.htm (I mean, just the interface, not the classification part) – Dr. belisarius May 26 '11 at 14:34
  • Could you enlighten us wrt those special purposes? I have a hard time imagining how this could ever be useful. – Sjoerd C. de Vries May 26 '11 at 14:35
  • @Sjoerd, sorry, I forgot to answer you before now. I don't have any grand plans, it's just an alternative that might be useful at times. There are other problems like MathCAD, SPICE, and (I cannot recall the other), that use a visual block assembly paradigm. It would be tedious for general programming, but it does have its place. – Mr.Wizard May 27 '11 at 10:29

1 Answers1

14

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.

Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • Alas I don't have time at the moment to explain how this works, but I will do that as soon as time allows. – Leonid Shifrin May 26 '11 at 14:48
  • well, it works as promised for modifying leaves. Shame nobody else thought this was interesting enough to upvote... – acl May 26 '11 at 21:20
  • 1
    @acl Perhaps, too much code for most people to quickly see the point. For me, this was an interesting exercise. Besides, the code to convert expressions to mutable trees and back is quite general and perhaps could find other uses. Anyways, thanks for the upvote! – Leonid Shifrin May 26 '11 at 21:50
  • Leonid, I am going to have set aside some time to understand this, but please do not think I am ungrateful. – Mr.Wizard May 27 '11 at 06:27
  • @Mr.Wizard Mind you, this thought never crossed my mind. By the way, I added some explanations which hopefully will make the code less obscure. – Leonid Shifrin May 27 '11 at 10:21
  • @Mr.Wizard I added a much shorter version, which may also interest you - see my edit. – Leonid Shifrin May 27 '11 at 18:25
  • Thank you. I shall enjoy digging into this when I have some time. – Mr.Wizard May 27 '11 at 18:28
  • Leonid, I believe the vote I just gave you is number 400, which should earn you the silver Mathematica badge. Congratulations! – Mr.Wizard May 28 '11 at 12:58
  • @Mr.Wizard Thanks! Did not get it yet, it's probably on the way :) But you are quite close to the same badge yourself, probably a matter of a day or 2 if not today. – Leonid Shifrin May 28 '11 at 20:28
  • It better be on the way or I'll protest. I know I am close; I was actually racing you for a while when I saw where we were, but it is only fitting that you reach it first. I shall try to give longer, better answers like you do, but I cannot match you. (see the one I just posted, and give me feedback, please.) – Mr.Wizard May 28 '11 at 20:33
  • @Mr.Wizard Well, these days I don't have the energy and time for a long race :). Too bad - a few years back I'd make for a much better contender, since my brain wasn't yet spoiled by C, Java (especially), Javascript and a bunch of other stuff :). I mean, it's probably good to know that stuff, but there is a price to pay - I don't attempt to do crazy things in Mathematica as much as I did once. Jokes aside, that last post of yours is pretty good. – Leonid Shifrin May 28 '11 at 20:53
  • @telefunkenvf14 Glad you liked it. I think this is a nice example to illustrate the use of the mutable tree data type. – Leonid Shifrin Jun 20 '11 at 19:50
  • Dear @Leonid Shifrin, thanks a lot for all this. This is very inspiring. Beware, your second shorter version has a few flaws. Notably, it does not handle numerical arguments well. Try renderTreeAlt[(a + b) c + 2] – ogerard Apr 12 '13 at 05:04