манипулирование кодом через интерактивное дерево для Mathematica - PullRequest
8 голосов
/ 26 мая 2011

Этот вопрос заставил меня задуматься над интерактивным методом редактирования кода. Интересно, возможно ли реализовать что-то подобное, учитывая динамические возможности Mathematica.

Рассмотрим выражение:

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

И его TreeForm:

enter image description here

Я хотел бы иметь возможность редактировать это дерево напрямую, а затем перевести результат обратно в код Mathematica. Надо хотя бы уметь:

  • переименовывать узлы, заменяя символы
  • удаляет узлы, возвращая их листья узлу выше
  • переупорядочить узлы и листья (порядок аргументов)

Я считаю, что существуют языки или среды, которые специализируются на такого рода манипуляциях, и я не нахожу это привлекательным, но мне интересно иметь такой вид интерактивного редактирования дерева для специальных целей.

1 Ответ

14 голосов
/ 26 мая 2011

Я приведу частичное решение, но такое, которое может помочь вам начать.Я буду использовать изменяемую структуру данных дерева из этого поста, так как кажется, что изменчивость естественна для этой проблемы.Повторим это для удобства здесь:

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 повторно используется в качестве каркаса для дерева, где все информативные части (головы и атомы) были заменены символами.Это все еще изменчивое дерево, если у нас есть доступ к исходным символам, а не только к их значениям, но нам не нужно думать о строительных блоках для дерева - мы используем для этого структуру выражения.Это не умаляет более раннее более долгое решение, концептуально я думаю, что оно более ясное и, вероятно, еще лучше для более сложных задач.

...