Ответ 1
В Mathematica большая часть того, что вы делаете, основана на выражениях. Естественно, что выражения имеют древовидную структуру. Для переходов по глубине (которые, вероятно, наиболее распространены) вы можете использовать такие функции, как Scan
, Map
, Cases
и т.д. Разница между более традиционными языками заключается в том, что нет простого способа сохранить личность индивидуального node в дереве выражений, поскольку в Mathematica нет указателей. Кроме того, многие операции над выражениями, которые являются идиоматическими в Mathematica, будут копировать все выражение, когда вам нужно только изменить его в нескольких местах, потому что выражения неизменяемы.
Использование неизменяемых выражений Mathematica, поскольку деревья по-прежнему имеют ряд преимуществ. Во-первых, потому что они неизменны, легко понять, что они хранят, просто глядя на них (состояние и поведение не смешиваются). Другим является то, что существуют эффективные и общие функции, такие как Map
, MapIndexed
или Scan
, которые пересекают их. Например, шаблон дизайна посетителя невидимый - это просто Map[f,tree,Infinity]
, встроенный в langauge. Кроме того, существуют встроенные функции, такие как Cases
, Replace
, ReplaceAll
и т.д., Что позволяет писать очень сжатый и декларативный код для деструкции деревьев, поиска кусков деревьев с определенным синтаксисом или удовлетворения некоторого состояния, и т.д. Поскольку деревья не ограничиваются только созданием из списков и создаются из разных головок, можно эффективно использовать это, чтобы написать очень сжатый код обработки дерева. Наконец, свобода очень легко создавать любую древовидную структуру, которую вы хотите, значительно облегчает выполнение экспериментов и прототипов в духе поисковое и восходящее программирование, что сокращает цикл разработки и в конечном итоге приводит к лучшим проектам.
Тем не менее, вы можете, конечно, реализовать структуру данных "stateful" (изменчивый) дерева. Настоящая причина, по которой это еще не было сделано, - это, как я подозреваю, удар производительности, связанный со строительством, модификацией и перемещением такого дерева, поскольку на каждом этапе он будет проходить полный процесс символической оценки (см. этот для более подробной информации об этом). Для двух примеров того, как, например, двоичное дерево поиска может использоваться в контексте Mathematica для довольно эффективного кода, см. Мои сообщения здесь (общая символьная настройка) и здесь (в контексте Скомпилированного кода). Для общих путей построения структур данных по идиоматике в Mathematica я рекомендую книги Романа Мадера: "Программирование в математике", "Математический программист я и II" и особенно "Компьютерные науки в математике". В последнем он подробно обсуждает, как реализовать двоичное дерево поиска в Mathematica. EDIT Как отметил @Simon, разговор о @Daniel Lichtblau также является отличным ресурсом, который показывает, как создавать структуры данных и сделать их эффективными.
В отношении общих способов реализации структур данных в Mathematica, которые будут включать некоторое состояние, вот простой пример, извлеченный из моего сообщения в this Mathgroup thread - он реализует структуру данных "пары".
Unprotect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
ClearAll[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
Module[{first, second},
first[_] := {};
second[_] := {};
pair /: new[pair[]] := pair[Unique[]];
pair /: pair[tag_].delete[] := (first[tag] =.; second[tag] =.);
pair /: pair[tag_].setFirst[value_] := first[tag] = value;
pair /: pair[tag_].getFirst[] := first[tag];
pair /: pair[tag_].setSecond[value_] := second[tag] = value;
pair /: pair[tag_].getSecond[] := second[tag];
Format[pair[x_Symbol]] := "pair[" <> ToString[Hash[x]] <> "]";
];
Protect[pair, setFirst, getFirst, setSecond, getSecond, new, delete];
Вот как вы могли его использовать:
pr = new[pair[]];
pr.setFirst[10];
pr.setSecond[20];
{pr.getFirst[], pr.getSecond[]}
{10, 20}
Создание списка новых парных объектов:
pairs = Table[new[pair[]], {10}]
{"pair[430427975]", "pair[430428059]", "pair[430428060]", "pair[430428057]",
"pair[430428058]", "pair[430428063]", "pair[430428064]", "pair[430428061]",
"pair[430428062]", "pair[430428051]"}
Установка полей:
Module[{i},
For[i = 1, i <= 10, i++,
pairs[[i]].setFirst[10*i];
pairs[[i]].setSecond[20*i];]]
Проверка полей:
#.getFirst[] & /@ pairs
{10, 20, 30, 40, 50, 60, 70, 80, 90, 100}
#.getSecond[] & /@ pairs
{20, 40, 60, 80, 100, 120, 140, 160, 180, 200}
В сообщении, о котором я упоминал, есть более подробное обсуждение. Одна из больших проблем для "объектов", созданных таким образом, заключается в том, что для них нет автоматической сборки мусора, что может быть одной из основных причин, по которой расширения OOP, реализованные в самой Mathematica самого верхнего уровня, действительно не снимаются.
Существует несколько расширений OOP для Mathematica, таких как пакет classes.m
Романа Мадера (источник находится в его книге "Программист Mathematica" ), коммерческий пакет Objectica
и несколько других. Но до тех пор, пока сама Mathematica не обеспечит эффективные механизмы (возможно, основанные на каком-то указателе или ссылочном механизме) для создания изменяемых структур данных (если это когда-либо произойдет), вероятно, будет большой удар производительности, связанный с реализацией на высшем уровне таких структур данных в мм. Кроме того, поскольку mma основано на неизменности как одной из основных идей, не так просто сделать изменяемые структуры данных хорошо вписывающимися в другие идиомы программирования Mathematica.
EDIT
Ниже приведен пример реализации дерева с голыми бинами по строкам приведенного выше примера:
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[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;
];
Некоторые примеры использования:
In[68]:= root = new[node[]]
Out[68]= node[$7]
In[69]:= root.addChild[new[node[]], 1]
Out[69]= {node[$8]}
In[70]:= root.addChild[new[node[]], 2]
Out[70]= {node[$8], node[$9]}
In[71]:= root.getChild[1].addChild[new[node[]], 1]
Out[71]= {node[$10]}
In[72]:= root.getChild[1].getChild[1].setValue[10]
Out[72]= 10
In[73]:= root.getChild[1].getChild[1].getValue[]
Out[73]= 10
Для одного нетривиального примера использования этой изменяемой структуры данных дерева см. эту мою статью. Он также сталкивается с этим методом с еще более сильно использующим собственные структуры и функции Mathematica и хорошо иллюстрирует пункты, обсуждавшиеся в начале этого сообщения.