Đầu tiên, danh sách là một loại cây. Nếu chúng ta biểu thị một danh sách dưới dạng một danh sách được liên kết , thì đó chỉ là một cây có mỗi nút có 1 hoặc 0 con cháu.
Cây phân tích chỉ là một cách sử dụng cây như một cấu trúc dữ liệu. Cây có nhiều ứng dụng trong khoa học máy tính, bao gồm sắp xếp, thực hiện bản đồ, mảng kết hợp, v.v.
Nói chung, danh sách, cây, vv là các cấu trúc dữ liệu đệ quy: Mỗi nút chứa một số thông tin và một thể hiện khác của cùng cấu trúc dữ liệu. Folding là một hoạt động trên tất cả các cấu trúc như vậy mà chuyển đổi đệ quy các nút thành các giá trị "từ dưới lên". Mở ra là quá trình ngược lại, nó chuyển đổi các giá trị thành các nút "từ trên xuống".
Đối với một cấu trúc dữ liệu nhất định, chúng ta có thể xây dựng một cách cơ học các chức năng gấp và mở của chúng.
Ví dụ, hãy lấy danh sách. (Tôi sẽ sử dụng Haskell cho các ví dụ khi được nhập và cú pháp của nó rất rõ ràng.) Danh sách là kết thúc hoặc giá trị và "đuôi".
data List a = Nil | Cons a (List a)
Bây giờ hãy tưởng tượng chúng ta đang gấp một danh sách. Ở mỗi bước, chúng ta có nút hiện tại được gấp lại và chúng ta đã gấp các nút phụ đệ quy của nó. Chúng ta có thể đại diện cho trạng thái này như
data ListF a r = NilF | ConsF a r
trong đó r
giá trị trung gian được xây dựng bằng cách gấp danh sách con. Điều này cho phép chúng tôi thể hiện chức năng gấp qua danh sách:
foldList :: (ListF a r -> r) -> List a -> r
foldList f Nil = f NilF
foldList f (Cons x xs) = f (ConsF x (foldList f xs))
Chúng tôi chuyển đổi List
thành ListF
bằng cách đệ quy gấp lại danh sách con của nó và sau đó sử dụng một hàm được xác định trên ListF
. Nếu bạn nghĩ về nó, đây chỉ là một đại diện khác của tiêu chuẩn 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
Chúng ta có thể xây dựng unfoldList
theo cùng một cách:
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')
Một lần nữa, nó chỉ là một đại diện khác của unfoldr
:
unfoldr :: (r -> Maybe (a, r)) -> r -> [a]
(Lưu ý rằng Maybe (a, r)
đẳng cấuListF a r
.)
Và chúng ta cũng có thể xây dựng một chức năng phá rừng:
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)
Nó chỉ đơn giản là bỏ qua trung gian List
và hợp nhất các chức năng gấp và mở ra với nhau.
Quy trình tương tự có thể được áp dụng cho bất kỳ cấu trúc dữ liệu đệ quy nào. Ví dụ: một cây có các nút có thể có 0, 1, 2 hoặc hậu duệ có giá trị trên các nút phân nhánh 1- hoặc 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)
Tất nhiên, chúng ta có thể tạo ra deforestTree
một cách máy móc như trước đây.
(Thông thường, chúng tôi sẽ thể hiện treeFold
thuận tiện hơn như:
treeFold' :: (r -> r -> r) -> (a -> r -> r) -> (a -> r) -> Tree a -> r
)
Tôi sẽ bỏ qua các chi tiết, tôi hy vọng mô hình là rõ ràng.
Xem thêm: