web-dev-qa-db-ja.com

Mathematicaのツリーデータ構造

私は数学を主に数学のワークベンチとして、そして比較的小さなアドホックプログラムを書くために使ってきました。しかし、私はMathematicaでプログラムするつもりのシステムを設計しています。データをツリーに保存し、ツリーを検索してトラバースする必要があります。ツリーの実装方法は知っていますが、標準のテスト済みコードを好みます。 Mathematicaユーザーウィキで基本的なデータ構造用のパッケージの種類を調べました。 Mathematicaのドキュメントに小さな例がありますが、私は何も見つけませんでした。

今私の質問に:

  1. どこかで利用可能なデータ構造の(オープンソース)パッケージはありますか?

  2. データ構造に関してどのようなアプローチを使用しましたか?独自のutilパッケージを徐々に開発していますか?

(質問ではなく、ただの発言です。たぶん...(たくさんの利用可能な)オープンソースパッケージがないことが、Mathematicaにそれに値する勢いがない理由です。鶏/卵の問題、私は恐れています。)

40
nilo de roock

Mathematicaでは、あなたがすることのほとんどは式に基づいています。式は当然ツリー構造を持っています。深さ優先探索(おそらく最も一般的)の場合、ScanMapCasesなどの関数を使用できます。より伝統的な言語との違いはMathematicaにはポインタがないので、式ツリーの個々のノードのアイデンティティを保持する簡単な方法はありません。また、数式は不変であるため、Mathematicaで慣用的な式に対する多くの操作では、式をいくつかの場所で変更するだけでよい場合に、式全体がコピーされます。

不変のMathematica式をツリーとして使用することには、まだいくつかの利点があります。 1つは、不変であるため、それらを見るだけで何が格納されているかを簡単に理解できることです(状態と動作が混在していません)。もう1つは、MapMapIndexedScanなど、それらをトラバースする効率的で汎用的な関数があることです。たとえば、ビジターデザインパターンは invisible -です。これはMap[f,tree,Infinity]であり、言語に組み込まれています。また、CasesReplaceReplaceAllなどの組み込み関数があり、ツリーを分解したり、次の部分を見つけたりするための非常に簡潔で宣言的なコードを記述できます。特定の構文を持つツリー、または特定の条件を満たすツリーなど。ツリーはリストからのみ構築され、異なるヘッドから構築されることに制限されないため、これを効果的に使用して、非常に簡潔なツリー処理コードを記述できます。最後に、必要なツリー構造を非常に簡単に構築できる自由により、 探索的およびボトムアッププログラミング の精神で、実験やプロトタイプの実行がはるかに簡単になり、開発サイクルが短縮され、最終的にはより良いデザインに。

そうは言っても、「ステートフル」(可変)ツリーデータ構造を確実に実装できます。それがまだ行われていない本当の理由は、一般的に、そのようなツリーの構築、変更、およびトラバースに関連するパフォーマンスの低下です。これは、すべてのステップで完全なシンボリック評価プロセスが実行されるためです( this を参照)。 =詳細については投稿してください)。たとえば、バイナリ検索ツリーをMathematicaコンテキストで使用して非常に効率的なコードを作成する方法の2つの例については、私の投稿を参照してください ここ (一般的な記号設定)および ここ (コンパイルされたコードのコンテキストで)。 Mathematicaで慣用的にデータ構造を構築する一般的な方法については、Roman Maederの本、「Programming in Mathematica」、「Mathematica Programmer I&II」、特に「Computer ScienceinMathematica」をお勧めします。後者では、彼はMathematicaで二分探索木を実装する方法について詳細に議論しています。 [〜#〜] edit [〜#〜]@ Simonが述べたように、@ Daniel Lichtblauの話も素晴らしいリソースであり、データ構造を構築して作成する方法を示しています。効率的。

いくつかの状態を組み込むMathematicaでデータ構造を実装する一般的な方法に関して、これは this Mathgroupスレッドの私の投稿から抽出された簡単な例です-それは「ペア」データ構造を実装します。

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} 

私が言及した投稿では、より詳細な議論があります。この方法で作成された「オブジェクト」の大きな問題の1つは、オブジェクトの自動ガベージコレクションがないことです。これが、トップレベルのMathematica自体に実装されたOOP拡張機能が本当に離陸しません。

MathematicaにはいくつかのOOP拡張機能があります。たとえば、RomanMaederによるclasses.mパッケージ(ソースは彼の「MathematicaProgrammer」の本にあります)、Objecticaコマーシャルなどです)。しかし、Mathematica自体が可変データ構造を構築するための効率的なメカニズム(おそらく何らかのポインタまたは参照メカニズムに基づく)を提供するまで(これが起こった場合)、おそらくトップに関連する大きなパフォーマンスヒットがあります- mmaでのそのようなデータ構造のレベル実装。また、mmaはコアアイデアの1つとして不変性に基づいているため、可変データ構造をMathematicaプログラミングの他のイディオムにうまく適合させることは非常に簡単ではありません。

[〜#〜]編集[〜#〜]

上記の例に沿った、必要最低限​​のステートフルツリーの実装を次に示します。

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

この可変ツリーデータ構造の使用の重要な例の1つについては、 this post ofmineを参照してください。また、この方法をMathematicaのネイティブデータ構造と関数をより頻繁に再利用する方法と対峙させ、この投稿の冒頭で説明したポイントをよく示しています。

43
Leonid Shifrin

私は数学を主に数学のワークベンチとして、そして比較的小さなアドホックプログラムを書くために使ってきました。

Mathematicaはこれで本当に優れています。

データ構造に関してどのようなアプローチを使用しましたか?独自のutilパッケージを徐々に開発していますか?

Mathematicaで独自のデータ構造を作成することは、効率的に処理できないため避けています。具体的には、一般的なデータ構造はMathematicaでは他の場所よりも10〜1,000倍遅くなる傾向があり、実用性が大幅に制限されます。たとえば、 赤黒木で深さの範囲を計算する場合、MathematicaはF#よりも100倍遅い

リストを使用した論理プログラミングは、Mathematicaが通常、他のコンパイル言語よりも桁違いに遅い一例です。次のMathematicaプログラムは、リンクリストを使用してn-queens問題を解決します。

safe[{x0_, y0_}][{x1_, y1_}] := 
 x0 != x1 && y0 != y1 && x0 - y0 != x1 - y1 && x0 + y0 != x1 + y1

filter[_, {}] := {}
filter[p_, {h_, t_}] := If[p[h], {h, filter[p, t]}, filter[p, t]]

search[n_, nqs_, qs_, {}, a_] := If[nqs == n, a + 1, a]
search[n_, nqs_, qs_, {q_, ps_}, a_] := 
 search[n, nqs, qs, ps, 
  search[n, nqs + 1, {q, qs}, filter[safe[q], ps], a]]

ps[n_] := 
 Fold[{#2, #1} &, {}, Flatten[Table[{i, j}, {i, n}, {j, n}], 1]]

solve[n_] := search[n, 0, {}, ps[n], 0]

同等のF#は次のとおりです。

let safe (x0, y0) (x1, y1) =
  x0<>x1 && y0<>y1 && x0-y0<>x1-y1 && x0+y0<>x1+y1

let rec filter f = function
  | [] -> []
  | x::xs -> if f x then x::filter f xs else filter f xs

let rec search n nqs qs ps a =
  match ps with
  | [] -> if nqs=n then a+1 else a
  | q::ps ->
      search n (nqs+1) (q::qs) (filter (safe q) ps) a
      |> search n nqs qs ps

let ps n =
  [ for i in 1..n do
      for j in 1..n do
        yield i, j ]

let solve n = search n 0 [] (ps n) 0

solve 8

エイトクイーンの問題を解くには、Mathematicaで10.5秒、F#で0.07秒かかります。したがって、この場合、F#はMathematicaより150倍高速です。

Stack Overflowの質問 Mathematicaの「リンクリスト」とパフォーマンス はもっと極端な例を示しています。そのMathematicaコードをF#に素朴に変換すると、Mathematicaよりも4,000倍から200,000倍速く実行される同等のプログラムが得られます。

let Rand = System.Random()
let xs = List.init 10000 (fun _ -> Rand.Next 100)
Array.init 100 (fun _ ->
  let t = System.Diagnostics.Stopwatch.StartNew()
  ignore(List.length xs)
  t.Elapsed.TotalSeconds)

具体的には、Mathematicaは1回の反復を実行するのに0.156秒から16秒かかりますが、F#は42µsから86µsかかります。

本当にMathematicaに留まりたいのなら、私がしていることすべてをMathematicaのいくつかの組み込みデータ構造に靴べらで詰め込みます。 Dispatch

8
Jon Harrop