你已經得到了寫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))
爲簡潔起見,我使用了一個較短的列表並將其縮寫爲EmptyNode
至E
。
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)
當所有元素都被插入並且您到達列表的末尾時,您將刪除該節點而不是執行任何操作。最小的改變來解決這個將改變爲insert
第二個方程的第一款以
|x == [] = Node e izq der
這將然而,仍然留下一個失敗案例(你已經有了),
insert [] EmptyNode = insert (tail []) (Node (head []) EmptyNode EmptyNode)
會引起*** Exception: Prelude.tail: empty list
。
除了成爲上述錯誤的原因之外,這裏使用的head
和tail
也是高度單一的。定義這種功能的常用方法是在列表上進行模式匹配。另外單向性是你檢查一個空的列表,x == []
,慣用的方法是使用null x
。在這種情況下,其他守衛要求元素類型爲Ord
的實例,因此沒有語義變化,但通常,x == []
對元素類型施加了Eq
約束,而null x
對任意類型起作用。
最後,儘管你認爲你的警衛head x == e
,head x < e
,head x > e
包括所有的可能性(爲有效Ord
情況下,他們這樣做 - 除外浮點類型,其中NaN
既不等於也不高於比任何值小也不大,但是否這些Ord
實例是有效的是辯論的問題),編譯器不能確定這一點,並且(當被要求警告通常應該是這樣的事情時,總是編譯-Wall
)警告關於非窮舉模式定義爲insert
。要以編譯器知道所有案例都涵蓋的方式覆蓋所有案例,最後一個警衛應具有otherwise
條件。
使您的代碼放到一個更地道的形狀(和固定的優秀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 [email protected](x:_) [email protected](Node e izq der)
| x == e = t
| x < e = Node e (insert l izq) der
| otherwise = Node e izq (insert l der)
結果現在我們可以尋找進一步的問題。 insert
的一個可能意想不到的方面是,如果要插入的元素列表的頭部已經在樹中,則整個列表被丟棄並且樹完全不變,例如,
insert [1 .. 10] (Node 1 EmptyNode EmptyNode) = Node 1 EmptyNode EmptyNode
處理這種事情的通常方法是隻刪除列表頭並仍插入其餘元素。這將改變該定義的最後一個方程來實現
insert [email protected](x:xs) [email protected](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
總是產生一個樹,其中每個節點只有一個(或無,爲最低)非空子樹,即構建的樹基本上是一個列表。
定義的最後等式中的子句強烈地表明樹應該是二叉搜索樹,但該屬性不被定義維護。例如
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. suggested before,以分離出插入單個元件成樹
insertOne :: Ord a => a -> ArbolBinario a -> ArbolBinario a
insertOne x EmptyNode = Node x EmptyNode EmptyNode
insertOne x [email protected](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
)。
「buildTree」的實現是摺疊而不是遞歸的最佳選擇。這不僅更簡單,而且不需要列表反轉。 –
是的,你是對的。謝謝你指出這個 – user1406062
看來我錯過了這種情況:insert2 [] t = t並在創建節點之前調用我的funcion發送尾部。非常感謝您的詳細解釋。 Haskell是一個棘手但很酷的編程語言:) – rdk1992