0

そこで、整数のリストを入力として、Haskell でバイナリ ツリーを作成するように依頼されました。以下は私のコードです。私の問題は、リストの最後の要素がツリーに挿入されていないことです。たとえば、[1,2,3,4] は「3」までのみツリーに挿入され、4 はツリーに挿入されません。

    data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a) | EmptyNode
deriving(Show)

    insert(x) EmptyNode= insert(tail x) (Node (head x) EmptyNode EmptyNode)

    insert(x) (Node e izq der)
     |x == [] = EmptyNode --I added this line to fix the Prelude.Head Empty List error, after I added this line the last element started to be ignored and not inserted in the tree
     |head x == e = (Node e izq der)
     |head x < e = (Node e (insert x izq) der)
     |head x > e = (Node e izq (insert x der))

ここで何が起こっているのかについてのアイデアはありますか? 助けていただければ幸いです

4

3 に答える 3

4

ここで役に立たないのは、懸念が混在していることです。リストをツリーに挿入する単一のメソッドを用意する代わりに、単一の要素をツリーに挿入する関数を用意してから、リスト内のすべての要素をツリーに追加する別の関数を用意してみませんか? 例えば。

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a)
                    | EmptyNode
                    deriving(Show)

-- insert handles only one element
insert :: (Ord a) => a -> ArbolBinario a -> ArbolBinario a
insert x EmptyNode = Node x EmptyNode EmptyNode
insert x n@(Node e izq der)
  | x == e = n
  | x < e = (Node e (insert x izq) der)
  | x > e = (Node e izq (insert x der))

-- insertList folds the list into a tree
insertList :: (Ord a) => [a] -> ArbolBinario a -> ArbolBinario a
insertList xs t = foldl (\a x -> insert x a) t xs

のようなものを使用するfoldlと、リストの最後に到達したときに何が起こるかを心配する必要がなくなります。結果は次のとおりです。

insertList [5, 3, 7, 9, 1, 4, 2, 6] EmptyNode
-- output:
-- Node 5 (Node 3 (Node 1 EmptyNode (Node 2 EmptyNode EmptyNode)) (Node 4 EmptyNode EmptyNode)) (Node 7 (Node 6 EmptyNode EmptyNode) (Node 9 EmptyNode EmptyNode))

それが役立つことを願っています。

編集 - -

また、他の Haskell 関数が行うことに従って、引数の順序を変更する方がおそらく良い考えであることも指摘したいと思います。これを参照してください:

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a)
                    | EmptyNode
                    deriving(Show)

insert :: (Ord a) => ArbolBinario a -> a -> ArbolBinario a
insert EmptyNode x = Node x EmptyNode EmptyNode
insert n@(Node e izq der) x
  | x == e = n
  | x < e = (Node e (insert izq x) der)
  | x > e = (Node e izq (insert der x))

insertList :: (Ord a) => ArbolBinario a -> [a] -> ArbolBinario a
insertList = foldl insert

これは、通常の容疑者(折り畳み、スキャンなど) で関数をより適切に使用できることを意味します。の定義がいかにinsertList簡単になったかがわかります。

参考までに:)

于 2012-09-22T03:33:15.093 に答える
3

関数をよりきれいに書くための提案がいくつかありましたinsertが、これまでのところ、実装の何が問題だったのか誰も教えてくれませんでした。

data ArbolBinario a = Node a (ArbolBinario a) (ArbolBinario a) | EmptyNode
                      deriving(Show)

insert(x) EmptyNode= insert(tail x) (Node (head x) EmptyNode EmptyNode)

insert(x) (Node e izq der)
 |x == [] = EmptyNode
 |head x == e = (Node e izq der)
 |head x < e = (Node e (insert x izq) der)
 |head x > e = (Node e izq (insert x der))

簡潔にするために、短いリストを使用して に省略EmptyNodeEます。

insert [1,2] E
~> insert [2] (Node 1 E E)
--   2 > 1, so the last clause, head x > e, is used
~> Node 1 E (insert [2] E)
-- insertion in empty tree, first equation is used
~> Node 1 E (insert [] (Node 2 E E))
-- Now the first clause of the second equation is used
~> Node 1 E (E)

すべての要素が挿入され、リストの最後に到達したら、何もしないのではなく、ノードを削除します。これを修正するための最小限の変更は、2 番目の方程式の最初の節を に変更することですinsert

 |x == [] = Node e izq der

ただし、それでも1つの失敗ケースが残ります(すでに発生しています)。

insert [] EmptyNode = insert (tail []) (Node (head []) EmptyNode EmptyNode)

原因になり*** Exception: Prelude.tail: empty listます。

上記のエラーの原因であることに加えて、ここでのheadandの使用tailも非常に一義的です。このような関数を定義する通常の方法は、リストでパターン マッチを行うことです。また、空リストのチェックは一義的でありx == []、慣用的な方法はそのために使用null xすることです。この場合、他のガードは要素の型が のインスタンスである必要があるOrdため、セマンティックの変更はありませんが、一般に要素の型に制約をx == []課しますが、任意の型で機能します。Eqnull x

head x == e最後に、ガード、head x < e、がすべての可能性をカバーしていると思いますがhead x > e(有効なインスタンスの場合、それらはカバーします - aが任意の値と等しくも小さくも大きくもないOrd浮動小数点型を除きますが、これらのインスタンスが有効かどうかは問題です)議論)、コンパイラはそれを確信することができず、(そのようなことについて警告するように求められた場合、通常はそうあるべきですが、常に でコンパイルします) の定義の網羅的でないパターンについて警告します。すべてのケースがカバーされていることをコンパイラーが認識できるように、すべてのケースをカバーするには、最後のガードに条件が必要です。NaNOrd-Wallinsertotherwise

コードをより慣用的な形にする (そして未解決のinsert [] EmptyNodeバグを修正する) と、

insert :: Ord a => [a] -> ArbolBinario a -> ArbolBinario a
insert []      t         = t     -- if there's nothing to insert, don't change anything
insert (x:xs)  EmptyNode = insert xs (Node x EmptyNode EmptyNode)
-- Using as-patterns, `l` is the entire list, `x` its head, `t` the entire tree
insert l@(x:_) t@(Node e izq der)
    | x == e             = t
    | x < e              = Node e (insert l izq) der
    | otherwise          = Node e izq (insert l der)

これで、さらに問題を探すことができます。のおそらく意図しない側面の 1 つinsertは、挿入される要素のリストの先頭が既にツリーにある場合、リスト全体が破棄され、ツリーが完全に変更されないことです。たとえば、

insert [1 .. 10] (Node 1 EmptyNode EmptyNode) = Node 1 EmptyNode EmptyNode

このようなことを処理する通常の方法は、リストの先頭のみを削除し、残りの要素を挿入することです。これは、定義の最後の式を次のように変更することによって達成されます。

insert l@(x:xs) t@(Node e izq der)
    | x == e             = insert xs t
    | x < e              = Node e (insert l izq) der
    | otherwise          = Node e izq (insert l der)

おそらく意図しない側面は

  • insert xs EmptyNodeは常に、すべてのノードが 1 つだけ (最下位の場合は 1 つもありません) の空でないサブツリーを持つツリーを生成します。つまり、構築されたツリーは基本的にリストです。
  • 定義の最後の方程式の節は、ツリーが二分探索ツリーであることを強く示唆していますが、そのプロパティは定義によって維持されていません。例えば

    insert [1,10] (Node 3 (Node 2 E E) (Node 7 E E))
    ~> Node 3 (insert [1,10] (Node 2 E E)) (Node 7 E E)
    ~> Node 3 (Node 2 (insert [1,10] E) E) (Node 7 E E)
    ~> Node 3 (Node 2 (insert [10] (Node 1 E E)) E) (Node 7 E E)
    ~> Node 3 (Node 2 (Node 1 E (Node 10 E E)) E) (Node 7 E E)
    
                           3
                          / \
                         /   \
                        2     7
                       /
                      /
                     1
                      \
                       \
                        10
    

これらの問題を解決する最善の方法は、OJとしてです。単一の要素をツリーに挿入する場合を区別するために、前に提案した

insertOne :: Ord a => a -> ArbolBinario a -> ArbolBinario a
insertOne x EmptyNode = Node x EmptyNode EmptyNode
insertOne x t@(Node e izq der)
    | x == e    = t
    | x < e     = Node e (insertOne x izq) der
    | otherwise = Node e izq (insertOne x der)

それを使用して、リストから各要素を挿入し、上からその位置を見つけます。

insertList :: Ord a => [a] -> ArbolBinario a -> ArbolBinario a
insertList []     t = t
insertList (x:xs) t = insertList xs (insertOne x t)
-- Alternative way:
-- insertList (x:xs) t = insertOne x (insertList xs t)

これらの計算パターンは非常に一般的であるため、 で定義されている関数に取り込まれていますPrelude

insertList xs t = foldl (flip insertOne) t xs
-- or, for the alternative way:
-- insertList xs t = foldr insertOne t xs

ご覧のとおり、自然な引数の順序がinsertOneである左の折りたたみでは、flipコンビネータを適用してその引数の順序を交換する必要があります。これは、リストの自然な折りたたみ操作が右の折りたたみであるという事実を示唆していますfoldr

ただし、insertOne何かを行う前にツリー引数を知る必要があるため、右の折り畳みで使用するように調整された関数ではなく、左の折り畳みで使用する方が効率的です (ただし、実際に効率を上げるには、foldl'から入手可能な厳密な左折Data.Listりと、より厳密なバージョンのinsertOne) を使用する必要があります。

于 2012-09-22T13:41:43.753 に答える
2

この問題を解決するために、別の関数を使用しました

data Tree a = Node a (Tree a) (Tree a) | EmptyNode deriving(Show)

insert :: Ord a => a -> Tree a -> Tree a
insert x EmptyNode = Node x EmptyNode EmptyNode
insert x (Node y left right)
    | x == y = (Node y left right)
    | x <  y = (Node y (insert x left) right)
    | x >  y = (Node y left (insert x right))

buildtree :: Ord a => [a] -> Tree a
buildtree []     = EmptyNode
buildtree (x:xs) = insert x (buildtree xs)

tree :: Ord a => [a] -> Tree a
tree xs = buildtree (reverse xs)

二分木を構築するには、関数を呼び出す必要がありbuildtreeます。問題は、最初にリストを逆にする必要があることです。だから、tree仕事をします。

*Main> insert 5 (insert 12 (insert 2 (insert 10 EmptyNode)))
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

*Main> tree [10,2,12,5]
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

*Main> buildtree [5,12,2,10]
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)

アップデート

このようにして、別の関数の使用を避けることができます。

insert2 :: Ord a => [a] -> Tree a -> Tree a
insert2 []     t = t
insert2 (x:xs) EmptyNode = insert2 xs (Node x EmptyNode EmptyNode)
insert2 (x:xs) (Node y left right)
            | x == y = insert2 xs (Node y left right)
            | x <  y = insert2 xs (Node y (insert2 [x] left) right)
            | x >  y = insert2 xs (Node y left (insert2 [x] right))

ただし、foldl やその他の方法を使用するほど効率的ではありません。唯一の利点は、1 つの関数のみを使用してすべてを実行することです。その逆は必要ありません。

*Main> insert2 [10,2,12,5] EmptyNode
Node 10 (Node 2 EmptyNode (Node 5 EmptyNode EmptyNode)) (Node 12 EmptyNode EmptyNode)
于 2012-09-22T03:32:23.883 に答える