Qual è la differenza tra ricorsione e corecursione?


55

Qual è la differenza tra questi?

Su Wikipedia, ci sono poche informazioni e nessun codice chiaro che spiega questi termini.

Quali sono alcuni esempi molto semplici che spiegano questi termini?

In che modo la corecursione è il doppio della ricorsione?

Esistono algoritmi corecusive classici?


45
Vedi la risposta a SO stackoverflow.com/questions/10138735/… (scusa, non riuscivo a fermarmi)
High Performance Mark

7
@HighPerformanceMark, non spiega cos'è la corecursion, abbiamo bisogno di un'altra domanda
Abyx

5
Ma seriamente, cosa c'è di sbagliato nella spiegazione di Wikipedia di questi termini?
Contrassegno ad alte prestazioni

5
La spiegazione della corecursion su Wikipedia è terribile. Dubito che abbia senso per chiunque non sappia già cosa sia la corecursion.
Marcin,

9
@ High Performance Mark: ho cliccato tre volte sul link pensando che ci fosse un errore prima di capire il gioco di parole. LOL
Giorgio,

Risposte:


24

Esistono diversi modi per esaminarlo. La cosa più semplice per me è pensare alla relazione tra "induttive" e "definizioni induttive"

Una definizione induttiva di un set va così.

Il set "Nat" è definito come il set più piccolo in modo che "Zero" sia in Nat, e se n è in Nat "Succ n" è in Nat.

Che corrisponde al seguente Ocaml

type nat = Zero | Succ of nat

Una cosa da notare su questa definizione è che un numero

omega = Succ(omega)

NON è un membro di questo set. Perché? Supponiamo che lo fosse, ora considera l'insieme N che ha tutti gli stessi elementi di Nat, tranne per il fatto che non ha omega. Chiaramente Zero è in N, e se y è in N, Succ (y) è in N, ma N è più piccolo di Nat, il che è una contraddizione. Quindi, Omega non è in Nat.

O, forse più utile per un informatico:

Dato un insieme di "a", l'insieme "Elenco di un" è definito come l'insieme più piccolo in modo tale che "Zero" sia nell'Elenco di a e che se xs è nell'Elenco di ae x è in un "Contro x xs" è nella lista di a.

Che corrisponde a qualcosa di simile

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

La parola chiave qui è "il più piccolo". Se non dicessimo "il più piccolo" non avremmo modo di sapere se il set Nat contenesse una banana!

Ancora,

zeros = Cons(Zero,zeros)

non è una definizione valida per un elenco di nat, proprio come omega non era un nat valido.

Definire i dati in modo induttivo in questo modo ci consente di definire funzioni che funzionano su di esso usando la ricorsione

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

possiamo quindi provare fatti al riguardo, come "più uno Zero = a" usando l' induzione (in particolare l'induzione strutturale)

La nostra prova procede per induzione strutturale su a.
Per il caso base lasciate essere zero. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)quindi lo sappiamo plus Zero Zero = Zero. Lascia che asia un nat. Assumi l'ipotesi induttiva che plus a Zero = a. Mostriamo ora che plus (Succ(a)) Zero = Succ(a)questo è ovvio dal momento che 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) , per induzione plus a Zero = aper tutti i anat

Ovviamente possiamo dimostrare cose più interessanti, ma questa è l'idea generale.

Finora abbiamo trattato dati definiti induttivamente che abbiamo ottenuto lasciando che fosse l'insieme "più piccolo". Quindi ora vogliamo lavorare con codata definiti in modo induttivo che otteniamo lasciando che sia il set più grande.

Così

Lascia che sia un set. L'insieme "Stream di a" è definito come l'insieme più grande in modo tale che per ogni x nello stream di a, x sia costituito dalla coppia ordinata (testa, coda) in modo tale che head sia in a e tail sia in Stream di a

In Haskell lo esprimiamo come

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

In realtà, in Haskell usiamo normalmente gli elenchi predefiniti, che possono essere una coppia ordinata o un elenco vuoto.

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

La banana non è un membro di questo tipo, poiché non è una coppia ordinata o l'elenco vuoto. Ma ora possiamo dire

ones = 1:ones

e questa è una definizione perfettamente valida. Inoltre, possiamo eseguire la ricorsione su questi co-dati. In realtà, è possibile che una funzione sia ricorsiva e ricorsiva. Mentre la ricorsione è stata definita dalla funzione che ha un dominio costituito da dati, la ricorsione ricorre solo a un dominio (chiamato anche intervallo) che è co-dati. La ricorsione primitiva significava sempre "chiamarsi" su dati più piccoli fino a raggiungere alcuni dati più piccoli. La ricorsione ricorsiva primitiva si "chiama sempre" su dati maggiori o uguali a quelli che avevi prima.

ones = 1:ones

è ricorsivamente primitivo. Mentre la funzione map(una specie di "foreach" nei linguaggi imperativi) è sia ricorsivamente primitiva (una sorta di) sia ricorsivamente primitiva.

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

lo stesso vale per la funzione zipWithche accetta una funzione e una coppia di liste e le combina insieme usando quella funzione.

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

il classico esempio di linguaggi funzionali è la sequenza di Fibonacci

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

che è primariamente ricorsivo, ma può essere espresso più elegantemente come un elenco infinito

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

un interessante esempio di induzione / coinduzione sta dimostrando che queste due definizioni calcolano la stessa cosa. Questo è lasciato come esercizio per il lettore.


1
@ user1131997 Grazie. Sto programmando di tradurre parte del codice in Java, rimanete sintonizzati
Philip JF,

@PhilipJF: Mi sento stupido ma non vedo perché "... Chiaramente Zero è in N, e se y è in N, Succ (y) è in N ...". Cosa succede se y è qualcosa che soddisfa Succ (y) = omega? (poiché non usi alcuna proprietà di Zero e Succ, posso sostituire Succ = radice quadrata e Zero = 2)
Ta Thanh Dinh,

... e poi vedo omega = 1.
Ta Thanh Dinh,

l'obiettivo è mostrare che omega non è in nat. Lo facciamo per contraddizione. Se omega fosse nat rispetto all'insieme N = nat - {omega} soddisferebbe le leggi. Questo perché nat soddisfa le leggi. Se y in N, 1. y non è omega e 2. y in nat. Da 2 conosciamo Succ (y) in nat, e per 1 y non è omega Succ (y) non è omega. Quindi Succ (y) in N. N include anche zero. Ma N è più piccolo di nat. Questa è una contraddizione. Pertanto, nat non include omega.
Philip JF,

Questo è tutto un po 'di menzogna poiché Ocaml ha una ricorsione del valore. Avrei davvero dovuto usare SML, che è l'unico linguaggio "mainstream" che supporta il ragionamento induttivo.
Philip JF,

10

Fondamentalmente, la corecursione è in stile accumulatore ricorsivo , costruendo il suo risultato sulla via da seguire dal caso iniziale, mentre la ricorsione regolare costruisce il suo risultato sulla via del ritorno dal caso base.

(parlando ora Haskell). Ecco perché foldr(con una rigorosa funzione di combinazione) esprime la ricorsione e (con una combinazione foldl'rigorosa f.) / scanl/ until/ iterate/ unfoldr/ Ecc. Esprime la corecursione. La corecursione è ovunque. foldrcon pettine non rigido. f. esprime la ricorsione della coda modulo cons .

E la ricorsione protetta di Haskell è proprio come la ricorsione della coda modulo contro .

Questa è la ricorsione:

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]

(leggi $come "di"). Questa è corecursion:

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
      = .....

Pieghe: http://it.wikipedia.org/wiki/Fold_(higher-order_function)


4

Controllare questo al blog Vitomir Kovanovic s' . L'ho trovato al punto:

Valutazione pigra in una caratteristica molto bella che si trova nei linguaggi di programmazione con funzionalità di programmazione funzionale come lisp, haskell, python ecc. Risulta che la valutazione del valore della variabile sia ritardata all'utilizzo effettivo di quella variabile.

Ciò significa che, ad esempio, se si desidera creare un elenco di milioni di elementi con qualcosa del genere, (defn x (range 1000000))non viene effettivamente creato, ma viene semplicemente specificato e quando si utilizza realmente quella variabile per la prima volta, ad esempio quando si desidera il decimo elemento di quell'interprete di elenco crea solo i primi 10 elementi di tale elenco. Quindi la prima esecuzione di (take 10 x) crea effettivamente questi elementi e tutte le chiamate successive alla stessa funzione funzionano con elementi già esistenti.

Questo è molto utile perché puoi creare infiniti elenchi senza errori di memoria esaurita. L'elenco sarà grande quanto hai richiesto. Naturalmente, se il tuo programma funziona con raccolte di dati di grandi dimensioni, può colpire il limite di memoria nell'uso di questi elenchi infiniti.

D'altra parte la corecursion è doppia alla ricorsione. Cosa significa? Bene, proprio come le funzioni ricorsive, che sono espresse nei termini di se stesse, le variabili corecursive sono espresse nei termini di se stesse.

Questo è meglio espresso nell'esempio.

Diciamo che vogliamo un elenco di tutti i numeri primi ...


1
il blog è morto.
Jason Hu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.