2011-05-26 32 views
8

This question讓我思考一種編輯代碼的交互方法。考慮到Mathematica的動態功能,我是否有可能實現這樣的功能。通過Mathematica交互式樹的代碼操作

考慮的表達式:

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

而其TreeForm

enter image description here

我想能夠直接編輯樹,然後讓翻譯回數學碼結果。人們應該至少能夠:

  • 重命名節點,替換符號
  • 刪除節點,恢復他們的葉到節點之上
  • 重排序的節點和葉(的參數的順序)

我相信有專門從事這種操作的語言或環境,並且我沒有發現這種吸引力,但我有興趣將這種交互式樹編輯用於特殊目的。

+0

我認爲自然的方法是使用XXX/Link和類似http://orange.biolab.si/doc/catalog10/Classify/InteractiveTreeBuilder.htm(我的意思是,只是界面,而不是分類部分) – 2011-05-26 14:34:31

+0

你能指導我們這些特殊目的嗎?我很難想象這會如何有用。 – 2011-05-26 14:35:48

+0

@Sjoerd,對不起,我現在忘了回答你了。我沒有任何宏偉計劃,這只是一個可能有用的替代方案。還有其他問題,如MathCAD,SPICE和(我不記得其他問題),它們使用視覺塊組裝範例。對於一般的編程來說這將是乏味的,但它確實有它的位置。 – 2011-05-27 10:29:35

回答

13

我將提供一個部分解決方案,但可以讓你開始。我將使用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; 
]; 

下面是從任何數學表達式創建一個可變的樹,閱讀表達從樹後面的代碼:

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] := 
[email protected] 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] 

你必須能夠在每個輸入字段修改數據,但它需要點擊幾下鼠標就會出現光標。例如,我編輯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 = [email protected] 
     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原生樹結構被重新用作樹的骨架,其中所有有信息的部分(頭部和原子)被符號替換,所以該方法產生更短的代碼。只要我們能夠訪問原始符號而不僅僅是它們的值,這仍然是一個可變的樹,但我們不需要考慮爲樹構建模塊 - 我們使用表達式結構。這不是爲了減少以前的更長時間的解決方案,在概念上我認爲它更清楚,對於更復雜的任務來說,它可能還是更好。

+0

唉,我現在沒時間解釋這是如何工作的,但我會盡快做到這一點。 – 2011-05-26 14:48:29

+0

好吧,它可以像修改葉子所承諾的那樣工作。恥辱沒有人認爲這是足夠有趣的upvote ... – acl 2011-05-26 21:20:01

+1

@acl也許,對於大多數人來說,太多的代碼很快就能看到這一點。對我而言,這是一個有趣的練習。此外,將表達式轉換爲可變樹並返回的代碼非常普遍,也許可以找到其他用途。無論如何,感謝upvote! – 2011-05-26 21:50:14