Sự khác biệt giữa đệ quy và corecursion là gì?


55

Sự khác biệt giữa những điều này là gì?

Trên Wikipedia, có rất ít thông tin và không có mã rõ ràng giải thích các điều khoản này.

Một số ví dụ rất đơn giản giải thích các điều khoản này là gì?

Làm thế nào là corecursion kép của đệ quy?

Có bất kỳ thuật toán corecusive cổ điển?


45
Xem câu trả lời cho SO stackoverflow.com/questions/10138735/ (xin lỗi, không thể tự dừng lại)
Dấu hiệu suất cao

7
@HighPerformanceMark, nó không giải thích được corecursion là gì, chúng tôi cần một câu hỏi khác
Abyx

5
Nhưng nghiêm túc, những gì sai với giải thích Wikipedia về các điều khoản này?
Dấu hiệu suất cao

5
Các giải thích corecursion trên wikipedia là khủng khiếp. Tôi nghi ngờ nó có ý nghĩa với bất cứ ai chưa biết corecursion là gì.
Marcin

9
@ Hiệu suất cao Mark: Tôi đã nhấp vào liên kết ba lần vì nghĩ rằng có một lỗi trước khi tôi hiểu cách chơi chữ. LOL
Giorgio

Câu trả lời:


24

Có một số cách tốt để xem xét điều này. Điều dễ nhất đối với tôi là suy nghĩ về mối quan hệ giữa "Định nghĩa quy nạp" và "Định nghĩa cưỡng chế"

Một định nghĩa quy nạp của một bộ đi như thế này.

Tập hợp "Nat" được định nghĩa là tập nhỏ nhất sao cho "Không" nằm trong Nat và nếu n ở Nat thì "Succ n" nằm trong Nat.

Tương ứng với Ocaml sau

type nat = Zero | Succ of nat

Một điều cần lưu ý về định nghĩa này là một con số

omega = Succ(omega)

KHÔNG phải là thành viên của bộ này. Tại sao? Giả sử rằng, bây giờ hãy xem xét tập N có tất cả các yếu tố giống như Nat ngoại trừ nó không có omega. Rõ ràng Zero ở trong N và nếu y ở N, Succ (y) ở N, nhưng N nhỏ hơn Nat là một mâu thuẫn. Vì vậy, omega không có trong Nat.

Hoặc, có lẽ hữu ích hơn cho một nhà khoa học máy tính:

Với một số tập hợp "a", tập hợp "Danh sách của a" được định nghĩa là tập nhỏ nhất sao cho "Nil" nằm trong Danh sách của a và nếu xs nằm trong Danh sách của a và x nằm trong "Nhược điểm x xs" nằm trong danh sách của a.

Mà tương ứng với một cái gì đó như

type 'a list = Nil | Cons of 'a * 'a list

Từ phẫu thuật ở đây là "nhỏ nhất". Nếu chúng ta không nói "nhỏ nhất", chúng ta sẽ không có cách nào để nói nếu bộ Nat có chứa một quả chuối!

Lần nữa,

zeros = Cons(Zero,zeros)

không phải là một định nghĩa hợp lệ cho một danh sách các nats, giống như omega không phải là một Nat hợp lệ.

Xác định dữ liệu theo cách tự nhiên như thế này cho phép chúng ta xác định các hàm hoạt động trên nó bằng cách sử dụng đệ quy

let rec plus a b = match a with
                   | Zero    -> b
                   | Succ(c) -> let r = plus c b in Succ(r)

sau đó chúng ta có thể chứng minh sự thật về điều này, như "cộng với số không = a" bằng cách sử dụng cảm ứng (cụ thể là cảm ứng cấu trúc)

Bằng chứng của chúng tôi tiến hành bằng cách cảm ứng cấu trúc trên a.
Đối với trường hợp cơ sở, hãy để một số không. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)vì vậy chúng tôi biết plus Zero Zero = Zero. Hãy alà một nat. Giả sử giả thuyết quy nạp rằng plus a Zero = a. Bây giờ chúng tôi cho thấy rằng plus (Succ(a)) Zero = Succ(a)điều này là hiển nhiên vì plus (Succ(a)) Zero = match a with |Zero -> Zero | Succ(a) -> let r = plus a Zero in Succ(r) = let r = a in Succ(r) = Succ(a) Như vậy, bằng cách cảm ứng plus a Zero = acho tất cả mọi người atrong nat

Tất nhiên chúng ta có thể chứng minh những điều thú vị hơn, nhưng đây là ý tưởng chung.

Cho đến nay chúng tôi đã xử lý dữ liệu được xác định theo quy nạp mà chúng tôi có được bằng cách để nó là tập hợp "nhỏ nhất". Vì vậy, bây giờ chúng tôi muốn làm việc với codata được xác định rõ ràng mà chúng tôi nhận được bằng cách để nó là tập hợp lớn nhất.

Vì thế

Hãy để một bộ. Tập hợp "Luồng của a" được định nghĩa là tập lớn nhất sao cho mỗi x trong luồng của a, x bao gồm cặp theo thứ tự (đầu, đuôi) sao cho đầu nằm trong a và đuôi nằm trong Luồng của a

Trong Haskell, chúng tôi sẽ thể hiện điều này như

data Stream a = Stream a (Stream a) --"data" not "newtype"

Trên thực tế, trong Haskell, chúng tôi sử dụng các danh sách tích hợp thông thường, có thể là một cặp theo thứ tự hoặc một danh sách trống.

data [a] = [] | a:[a]

Banana cũng không phải là thành viên của loại này, vì nó không phải là một cặp theo thứ tự hoặc danh sách trống. Nhưng, bây giờ chúng ta có thể nói

ones = 1:ones

và đây là một định nghĩa hoàn toàn hợp lệ. Hơn nữa, chúng ta có thể thực hiện đệ quy trên dữ liệu đồng này. Trên thực tế, một hàm có thể vừa là đệ quy vừa là đệ quy. Trong khi đệ quy được định nghĩa bởi hàm có một miền bao gồm dữ liệu, đồng quy đệ quy chỉ có nghĩa là nó có một miền đồng (còn gọi là phạm vi) là đồng dữ liệu. Đệ quy nguyên thủy có nghĩa là luôn luôn "tự gọi mình" trên dữ liệu nhỏ hơn cho đến khi đạt được một số dữ liệu nhỏ nhất. Đồng quy nguyên thủy luôn "tự gọi" dữ liệu lớn hơn hoặc bằng với những gì bạn có trước đây.

ones = 1:ones

là chủ yếu đồng đệ quy. Trong khi chức năng map(giống như "foreach" trong các ngôn ngữ mệnh lệnh) vừa là đệ quy nguyên thủy (sắp xếp) và đồng quy nguyên thủy.

map :: (a -> b) -> [a] -> [b]
map f []     = []
map f (x:xs) = (f x):map f xs

tương tự với hàm zipWithcó một hàm và một cặp danh sách và kết hợp chúng lại với nhau bằng hàm đó.

zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _           = [] --base case

ví dụ kinh điển của các ngôn ngữ chức năng là chuỗi Fibonacci

fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))

vốn là đệ quy nguyên thủy, nhưng có thể được thể hiện thanh lịch hơn như một danh sách vô hạn

fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at

một ví dụ thú vị về cảm ứng / cưỡng chế đang chứng minh rằng hai định nghĩa này tính toán cùng một thứ. Điều này được để lại như một bài tập cho người đọc.


1
@ user1131997 Cảm ơn. Tôi đang lên kế hoạch dịch một số mã sang Java, hãy theo dõi
Philip JF

@PhilipJF: Tôi cảm thấy ngu ngốc nhưng tôi không hiểu tại sao "... Rõ ràng Zero ở trong N và nếu y ở N, Succ (y) ở N ...". Điều gì xảy ra nếu y là một số thứ thỏa mãn Succ (y) = omega? (vì bạn không sử dụng bất kỳ thuộc tính nào của Zero và Succ, tôi có thể thay thế Succ = root vuông và Zero = 2)
Ta Thanh Dinh

... và sau đó tôi thấy omega = 1.
Ta Thanh Dinh

Mục tiêu là cho thấy omega không có trong tự nhiên. Chúng tôi làm điều này bằng mâu thuẫn. Nếu omega ở trong tự nhiên hơn tập N = nat - {omega} sẽ thỏa mãn luật. Đó là bởi vì nat thỏa mãn luật pháp. Nếu y ở N, 1. y không phải là omega và 2. y ở nat. Từ 2 chúng ta biết Succ (y) trong nat và bởi 1 y không phải omega Succ (y) không phải là omega. Do đó Succ (y) trong N. N cũng bao gồm số không. Nhưng, N nhỏ hơn nat. Đây là một mâu thuẫn. Vì vậy, nat không bao gồm omega.
Philip JF

Đây chỉ là một chút dối trá vì ocaml có đệ quy giá trị mà tôi thực sự nên sử dụng SML, đây là ngôn ngữ "chính thống" duy nhất hỗ trợ lý luận quy nạp.
Philip JF

10

Về cơ bản, corecursion là kiểu tích lũy đệ quy , xây dựng kết quả của nó trên con đường phía trước từ trường hợp bắt đầu, trong khi đệ quy thông thường xây dựng kết quả của nó trên đường trở về từ trường hợp cơ sở.

(nói Haskell bây giờ). Đó là lý do tại sao foldr(với chức năng kết hợp chặt chẽ) diễn tả đệ quy và foldl'(với lược nghiêm ngặt. F.) / scanl/ until/ iterate/ unfoldr/ Vv thể hiện sự ăn mòn. Corecursion ở khắp mọi nơi. foldrvới lược không nghiêm ngặt. đụ. diễn tả đuôi đệ quy modulo .

Và đệ quy được bảo vệ của Haskell giống như đệ quy modulo nhược điểm .

Đây là đệ quy:

fib n | n==0 = 0
      | n==1 = 1
      | n>1  = fib (n-1) + fib (n-2)

fib n = snd $ g n
  where
    g n | n==0 = (1,0)
        | n>0  = let { (b,a) = g (n-1) } in (b+a,b)

fib n = snd $ foldr (\_ (b,a) -> (b+a,b)) (1,0) [n,n-1..1]

(đọc $là "của"). Đây là sự ăn cắp:

fib n = g (0,1) 0 n where
  g n (a,b) i | i==n      = a 
              | otherwise = g n (b,a+b) (i+1)

fib n = fst.snd $ until ((==n).fst) (\(i,(a,b)) -> (i+1,(b,a+b))) (0,(0,1))
      = fst $ foldl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
      = fst $ last $ scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..n]
      = fst (fibs!!n)  where  fibs = scanl (\(a,b) _ -> (b,a+b)) (0,1) [1..]
      = fst (fibs!!n)  where  fibs = iterate (\(a,b) -> (b,a+b)) (0,1)
      = (fibs!!n)  where  fibs = unfoldr (\(a,b) -> Just (a, (b,a+b))) (0,1)
      = (fibs!!n)  where  fibs = 0:1:map (\(a,b)->a+b) (zip fibs $ tail fibs)
      = (fibs!!n)  where  fibs = 0:1:zipWith (+) fibs (tail fibs)
      = (fibs!!n)  where  fibs = 0:scanl (+) 1 fibs
      = .....

Nếp gấp: http://en.wikipedia.org/wiki/Fold_(higher-order_feft)


4

Kiểm tra điều này tại blog của Vitomir Kovanovic . Tôi tìm thấy nó đến điểm:

Đánh giá lười biếng trong một tính năng rất hay được tìm thấy trong các ngôn ngữ lập trình có khả năng lập trình chức năng như lisp, haskell, python, v.v ... Việc đánh giá giá trị biến bị trì hoãn đối với việc sử dụng biến đó.

Điều đó có nghĩa là ví dụ nếu bạn muốn tạo một danh sách hàng triệu phần tử với thứ như thế này (defn x (range 1000000))thì nó không thực sự được tạo ra, nhưng nó chỉ được chỉ định và khi bạn thực sự sử dụng biến đó lần đầu tiên, ví dụ như khi bạn muốn phần tử thứ 10 thông dịch viên danh sách đó chỉ tạo ra 10 yếu tố đầu tiên của danh sách đó. Vì vậy, lần chạy đầu tiên (lấy 10 x) thực sự tạo ra các phần tử này và tất cả các lệnh gọi tiếp theo đến cùng chức năng đang hoạt động với các phần tử đã tồn tại.

Điều này rất hữu ích vì bạn có thể tạo danh sách vô hạn mà không bị lỗi bộ nhớ. Danh sách sẽ lớn chỉ bằng bao nhiêu bạn yêu cầu. Tất nhiên, nếu chương trình của bạn đang làm việc với các bộ sưu tập dữ liệu lớn, nó có thể đạt giới hạn bộ nhớ trong việc sử dụng các danh sách vô hạn này.

Mặt khác, corecursion là kép để đệ quy. Điều này có nghĩa là gì? Cũng giống như các hàm đệ quy, được thể hiện bằng các thuật ngữ của chính chúng, các biến corecursive được thể hiện bằng các thuật ngữ của chính chúng.

Điều này được thể hiện tốt nhất trên ví dụ.

Hãy nói rằng chúng tôi muốn danh sách tất cả các số nguyên tố ...


1
trang blog đã chết.
Jason Hu
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.