「森林破壊」はプログラムから「木」をどのように削除しますか?


12

私はどのように森林伐採が消費理解だと思うと同時に、リストを生成し(倍からと展開]機能- ここにコードレビューでこの良い答えを参照)、しかし、私はそれを比較した場合、技術上のWikipediaのエントリ、それは削除する「について話しましたプログラムからの木。

プログラムを構文解析ツリーに解析する方法は理解できます(そうですか?)が、プログラムのある種の単純化(それ?)に森林破壊を使用する意味は何ですか?そして、どのように自分のコードにそれをしますか?

回答:


9

Yatima2975は最初の2つの質問をカバーしているようです。3番目の質問をカバーしようとします。これを行うには、非現実的に単純なケースを扱いますが、より現実的な何かを想像できると確信しています。

次数nの完全な二分木の深さを計算したいと想像してください。(ラベルなし)バイナリツリーのタイプは(Haskell構文で)です。n

type Tree = Leaf | Node Tree Tree

順序完全なツリーは次のとおりです。n

full : Int -> Tree
full n | n == 0 = Leaf
full n = Node (full (n-1)) (full (n-1))

そして、木の深さは

depth : Tree -> Int
depth Leaf = 0
depth (Node t1 t2) = 1 + max (depth t1) (depth t2)

今あなたが見ることができる任意の計算最初であろうコンストラクトための完全なツリーNを用いてfをU LのLを、その後解体使用してそのツリーをDとE のP Tの時間。森林破壊は、そのようなパターン(構造とそれに続く分解)がしばしば短絡する可能性があるという観察に依存しています:d e p t hf udepth fあなたはll nnfあなたはlldepthf u l l _ d e p t hを1回呼び出す:depth fあなたはll nfあなたはll_depth

full_depth : Int -> Int
full_depth n | n == 0 = 0
full_depth n = 1 + max (full_depth (n-1)) (full_depth (n-1))

これにより、フルツリーのメモリ割り当て、およびパターンマッチングを実行する必要がなくなり、パフォーマンスが大幅に向上します。さらに、最適化を追加する場合

max t t --> t

次に、指数時間の手順を線形時間の手順に変更しました... が整数の恒等式であると認識した追加の最適化があれば、それはクールでしょう。そのような最適化が実際に使用されること。fあなたはll_depth

自動森林減少を実行する唯一のメインストリームコンパイラはGHCであり、正しく思い出すと、これは(技術的な理由により)組み込み関数を作成するときにのみ実行されます。


基本的に同じ領域を網羅しているにもかかわらず、他の回答よりも定式化された方法からこの回答を多く得たため、表彰されました。
クリスストリングフェロー

6

まず、リストは一種のツリーです。リストをリンクされたリストとして表す場合、それは各ノードが1または0の子孫を持つツリーです。

構文解析ツリーは、データ構造としてツリーを利用するだけです。ツリーには、並べ替え、マップの実装、連想配列など、コンピューターサイエンスの多くの用途があります。

一般に、リスト、ツリーなどは再帰的なデータ構造です。各ノードには、いくつかの情報と同じデータ構造の別のインスタンスが含まれます。折り畳みは、そのようなすべての構造に対する操作であり、ノードを値を「ボトムアップ」に再帰的に変換します。展開は逆のプロセスで、値を「トップダウン」でノードに変換します。

特定のデータ構造に対して、折り畳みおよび展開関数を機械的に構築できます。

例として、リストを見てみましょう。(例としてHaskellを使用します。入力されており、構文が非常にきれいです。)リストは、終了または値であり、「テール」です。

data List a = Nil | Cons a (List a)

ここで、リストを折り畳んでいると想像してみましょう。各ステップで、折りたたむ現在のノードがあり、すでに再帰サブノードを折り畳んでいます。この状態を次のように表すことができます

data ListF a r = NilF | ConsF a r

ここでr、サブリストを折り畳むことによって構築される中間値です。これにより、リスト上の折りたたみ関数を表現できます。

foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil            = f NilF
foldList f (Cons x xs)    = f (ConsF x (foldList f xs))

サブリストを再帰的に折り畳むことでに変換ListListF、で定義された関数を使用しますListF。考えてみると、これは単なる標準の別の表現ですfoldr

foldr :: (a -> r -> r) -> r -> List a -> r
foldr f z = foldList g
  where
    g NilF          = z
    g (ConsF x r)   = f x r

unfoldList同じように構築できます:

unfoldList :: (r -> ListF a r) -> r -> List a
unfoldList f r = case f r of
                  NilF        -> Nil
                  ConsF x r'  -> Cons x (unfoldList f r')

繰り返しますが、それは単に別の表現ですunfoldr

unfoldr :: (r -> Maybe (a, r)) -> r -> [a]

(にMaybe (a, r)同型であることに注意してくださいListF a r。)

また、森林破壊機能も構築できます。

deforest :: (ListF a r -> r) -> (s -> ListF a s) -> s -> r
deforest f u s = f (map (deforest f u) (u s))
  where
    map h NilF        = NilF
    map h (ConsF x r) = ConsF x (h r)

それは単に中間体Listを省き、折りたたみ機能と展開機能を融合します。

同じ手順を任意の再帰的なデータ構造に適用できます。たとえば、ノードが0、1、2、または1または0分岐ノードの値を持つ子孫を持つことができるツリー:

data Tree a = Bin (Tree a) (Tree a) | Un a (Tree a) | Leaf a

data TreeF a r = BinF r r | UnF a r | LeafF a

treeFold :: (TreeF a r -> r) -> Tree a -> r
treeFold f (Leaf x)       = f (LeafF x)
treeFold f (Un x r)       = f (UnF x (treeFold f r))
treeFold f (Bin r1 r2)    = f (BinF (treeFold f r1) (treeFold f r2))

treeUnfold :: (r -> TreeF a r) -> r -> Tree a
treeUnfold f r = case f r of
                  LeafF x         -> Leaf x
                  UnF x r         -> Un x (treeUnfold f r)
                  BinF r1 r2      -> Bin (treeUnfold f r1) (treeUnfold f r2)

もちろん、deforestTree以前と同じように機械的に作成できます。

(通常、次のようにtreeFoldもっと便利に表現します。

treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r

詳細は省略しますが、パターンが明らかであることを願っています。

こちらもご覧ください:


すばらしい回答、ありがとう。リンクと詳細な例は貴重です。
クリスストリングフェロー

3

少し混乱しますが、(実行時に)作成される中間ツリーを削除するために(コンパイル時に)森林破壊が適用されます。森林伐採は、抽象構文木の一部をハッキングすることを含みません(それは、死んだ枝の除去です:-)

別のことに気づいたのは、リストツリーであり、非常に不均衡なリストだということです!


ああ、はい。非常にアンバランスです!
クリスストリングフェロー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.