Яка різниця між ними?
У Вікіпедії мало інформації та чіткого коду, що пояснює ці терміни.
Назвіть кілька дуже простих прикладів, що пояснюють ці терміни?
Яким чином ядро рекурсії є подвійним?
Чи є класичні основні алгоритми?
Яка різниця між ними?
У Вікіпедії мало інформації та чіткого коду, що пояснює ці терміни.
Назвіть кілька дуже простих прикладів, що пояснюють ці терміни?
Яким чином ядро рекурсії є подвійним?
Чи є класичні основні алгоритми?
Відповіді:
Існує ряд хороших способів розглянути це. Найпростіше для мене - подумати про співвідношення "індуктивного" та "індуктивного визначень"
Індуктивне визначення набору йде так.
Набір "Nat" визначається як найменший набір таким чином, що "Нуль" знаходиться в Nat, а якщо n - у Nat, "Succ n" - у Nat.
Що відповідає наступним Ocaml
type nat = Zero | Succ of nat
Одне, що слід зазначити щодо цього визначення, - це число
omega = Succ(omega)
НЕ є учасником цього набору. Чому? Припустимо, що це було, тепер розглянемо множину N, яка має всі ті ж елементи, що і Nat, за винятком того, що вона не має омеги. Очевидно, що Нуль є в N, а якщо y - в N, Succ (y) - у N, але N менше, ніж Nat, що суперечить. Отже, омеги немає в Нат.
Або, можливо, корисніше для вченого-комп’ютера:
Враховуючи деякий набір "a", набір "Список a" визначається як найменший набір таким чином, що "Nil" є у списку a, а якщо xs є у списку a, а x є у "мінусах x xs" знаходиться в Списку a.
Що відповідає чомусь подібному
type 'a list = Nil | Cons of 'a * 'a list
Оперативне слово тут «найменший». Якби ми не сказали "найменшого", ми б не мали жодного способу сказати, чи є в Наті банан!
Знову ж таки,
zeros = Cons(Zero,zeros)
не є правильним визначенням списку нац, так само як омега не була дійсною Nat.
Індуктивне визначення даних на зразок цього дозволяє нам визначати функції, які працюють на них за допомогою рекурсії
let rec plus a b = match a with
| Zero -> b
| Succ(c) -> let r = plus c b in Succ(r)
Тоді ми можемо довести факти про це, наприклад "плюс нуль = а", використовуючи індукцію (конкретно, структурну індукцію)
Наше доведення випливає структурною індукцією на a.
Для базового випадку нехай буде нульовим. plus Zero Zero = match Zero with |Zero -> Zero | Succ(c) -> let r = plus c b in Succ(r)
тому ми знаємо plus Zero Zero = Zero
. Нехай a
буде нац. Припустимо індуктивну гіпотезу, що plus a Zero = a
. Тепер ми покажемо, що plus (Succ(a)) Zero = Succ(a)
це очевидно, оскільки, 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)
таким чином, шляхом індукції plus a Zero = a
для всіх a
у фіз
Звичайно, ми можемо довести більш цікаві речі, але це загальна ідея.
Поки ми мали справу з індуктивно визначеними даними, які ми отримали, дозволяючи це бути "найменшим" набором. Отже, зараз ми хочемо працювати з кодифікованими кодатами, які ми отримуємо, дозволяючи їм бути найбільшим набором.
Тому
Нехай множина. Множина "Потік a" визначається як найбільша множина така, що для кожного x у потоці a, x складається з упорядкованої пари (голова, хвіст), така, що голова знаходиться у a, а хвіст - у Stream of a
У Haskell ми б це виразили як
data Stream a = Stream a (Stream a) --"data" not "newtype"
Насправді в Haskell ми зазвичай використовуємо вбудовані списки, які можуть бути впорядкованою парою або порожнім списком.
data [a] = [] | a:[a]
Банан також не є членом цього типу, оскільки це не упорядкована пара чи порожній список. Але, зараз ми можемо сказати
ones = 1:ones
і це абсолютно правильне визначення. Більше того, ми можемо виконати спільну рекурсію на цих спільних даних. Власне, функція може бути одночасно рекурсивною та рекурсивною. У той час як рекурсія визначалася функцією, що має домен, що складається з даних, ко-рекурсія просто означає, що вона має спільний домен (також званий діапазоном), який є спільними даними. Примітивна рекурсія означала завжди "викликати себе" на менших даних, поки не досягнуть деяких найменших даних. Примітивна спільна рекурсія завжди "називає себе" на даних, більших або рівних тим, що ви мали раніше.
ones = 1:ones
є первісно ко-рекурсивним. У той час як функція map
(на кшталт «передчуття» в імперативних мовах) є як первісно рекурсивною (свого роду), так і примітивно корекурсивною.
map :: (a -> b) -> [a] -> [b]
map f [] = []
map f (x:xs) = (f x):map f xs
те саме стосується функції, zipWith
яка приймає функцію та пару списків та поєднує їх разом, використовуючи цю функцію.
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = (f a b):zipWith f as bs
zipWith _ _ _ = [] --base case
класичний приклад функціональних мов - послідовність Фібоначчі
fib 0 = 0
fib 1 = 1
fib n = (fib (n-1)) + (fib (n-2))
що є первісно рекурсивним, але може бути викладене більш елегантно як нескінченний список
fibs = 0:1:zipWith (+) fibs (tail fibs)
fib' n = fibs !! n --the !! is haskell syntax for index at
цікавий приклад індукції / співавтори - це доведення, що ці два визначення обчислюють одне і те ж. Це залишається як вправа для читача.
В основному, основна реакція є стилем рекурсії акумулятора , будуючи її результат на шляху вперед від вихідного випадку, тоді як регулярна рекурсія будує свій результат на зворотному шляху від базового випадку.
(зараз говорить Хаскелл). Ось чому foldr
(з суворою поєднує функцією) висловлює рекурсию, і foldl'
(із суворим гребенем. Е.) / scanl
/ until
/ iterate
/ unfoldr
/ І т.д. висловити корекурсію. Корекція є скрізь. foldr
з не суворим гребінцем. f. виражає хід хвороби рекурсії за модулем .
І охороняли рекурсії в Haskell просто як хвіст рекурсії по модулю мінуси .
Це рекурсія:
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]
(читати $
як "із"). Це основнийкурс:
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
= .....
Складки: http://en.wikipedia.org/wiki/Fold_(higher-order_function)
Перевірте це у блозі Вітоміра Ковановича . Я знайшов це до речі:
Ледача оцінка в одній дуже приємній особливості, знайденій в мовах програмування з функціональними можливостями програмування, такими як lisp, haskell, python тощо. Це означає, що оцінка значення змінної затримується до фактичного використання цієї змінної.
Це означає, що, наприклад, якщо ви хотіли створити список мільйонів елементів з чимось подібним,
(defn x (range 1000000))
він насправді не створений, але він просто вказаний і коли ви дійсно використовуєте цю змінну вперше, наприклад, коли ви хочете 10-й елемент перекладач цього списку створює лише перші 10 елементів цього списку. Отже, перший запуск (взяти 10 х) фактично створює ці елементи, а всі наступні виклики тієї ж функції працюють з уже наявними елементами.Це дуже корисно, оскільки ви можете створювати нескінченні списки без помилок пам'яті. Список буде великим, скільки саме ви просили. Звичайно, якщо ваша програма працює з великими колекціями даних, це може призвести до обмеження пам'яті у використанні цих нескінченних списків.
З іншого боку, основний струм є подвійним для рекурсії. Що це означає? Так само, як і рекурсивні функції, які виражаються в термінах самих себе, основні струмові змінні виражаються в термінах самих себе.
Найкраще це виражено на прикладі.
Скажімо, ми хочемо список усіх простих чисел ...