Làm thế nào để 'phá rừng' loại bỏ 'cây' khỏi một chương trình?


12

Tôi nghĩ hiểu cách phá rừng tiêu thụ và tạo ra một danh sách cùng một lúc (từ một nếp gấp và một hàm mở ra - xem câu trả lời hay này trên CodeReview ở đây ), nhưng khi tôi so sánh với mục wikipedia về kỹ thuật thì nó đã nói về 'loại bỏ cây 'từ một chương trình.

Tôi hiểu làm thế nào một chương trình có thể được phân tích thành một cây phân tích cú pháp (đúng không?), Nhưng ý nghĩa của việc sử dụng phá rừng này đối với một số loại đơn giản hóa (là nó?) Của các chương trình là gì? Và làm thế nào tôi có thể làm điều đó với mã của tôi?

Câu trả lời:


9

Yatima2975 dường như đã bao gồm hai câu hỏi đầu tiên của bạn, tôi sẽ cố gắng trả lời câu hỏi thứ ba. Để làm điều này tôi sẽ xử lý một trường hợp đơn giản phi thực tế, nhưng tôi chắc chắn bạn sẽ có thể tưởng tượng điều gì đó thực tế hơn.

Hãy tưởng tượng bạn muốn tính toán độ sâu của toàn bộ cây nhị phân của thứ tự . Loại cây nhị phân (không nhãn) là (theo cú pháp Haskell):n

type Tree = Leaf | Node Tree Tree

Bây giờ cây đầy đủ của trật tự là:n

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

Và độ sâu của cây được tính bằng

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

depth (fbạntôitôi n)nfbạntôitôidepthdepth (fbạntôitôi n)fbạntôitôi_depth :

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

Điều này tránh việc cấp phát bộ nhớ cho toàn bộ cây và nhu cầu thực hiện khớp mẫu, giúp cải thiện đáng kể hiệu năng. Ngoài ra, nếu bạn thêm tối ưu hóa

max t t --> t

fbạntôitôi_depth

Trình biên dịch chính duy nhất thực hiện phá rừng tự động là GHC và nếu tôi nhớ lại chính xác, điều này chỉ được thực hiện khi soạn các hàm dựng sẵn (vì lý do kỹ thuật).


Được trao giải vì tôi nhận được nhiều hơn câu trả lời này từ cách nó được tạo ra so với các câu trả lời khác, mặc dù về cơ bản chúng bao gồm cùng một lãnh thổ.
Cris Stringfellow

6

Đầ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 đó rgiá 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 Listthành ListFbằ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 unfoldListtheo 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 treeFoldthuậ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:


Câu trả lời tuyệt vời, cảm ơn. Các liên kết, và ví dụ chi tiết có giá trị.
Cris Stringfellow

3

Hơi khó hiểu một chút, nhưng nạn phá rừng được áp dụng (vào thời gian biên dịch) để loại bỏ các cây trung gian sẽ được tạo ra (trong thời gian chạy). Phá rừng không liên quan đến việc hack đi các phần của cây cú pháp trừu tượng (đó là loại bỏ nhánh chết :-)

Một điều khác có thể khiến bạn thất vọng là danh sách là những cái cây, chỉ là những thứ rất không cân đối!


À đúng rồi. Rất mất cân đối!
Cris Stringfellow
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.