|
![]() |
#1 |
Moderator
|
Цитата:
Рекурсия для Фибоначчи?
Без дополнительного кэширования полученных результатов? И без дополнительных комментариев почему именно так? мне бы тоже не понравилось. Если не ошибаюсь, подобная задача расбирается в одной из первых глав классической книги "Структура и интерпретация компьютернах программ". |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
![]() |
#2 |
Участник
|
Цитата:
тут дело в ленивых вычислениях. ну и отсутствие побочных эффектов, принятое в функциональных языках (если не увлекаться монадами, конечно) именно ленивые вычисления и отсутствие побочных эффектов и позволяют так легко использовать рекурсию. но даже на функциональных языках есть проблема повторных memcaсhe конечно спасает отца русской демократии во многих случаях. но обычно за счет непомерно увеличенного потребления памяти. так скорее всего, в функциональном языке придется хранить все числа последовательности. или алгоритм будет каким-то безумным. обрати внимание, что для линейной оценки времени выполнения достаточно хранить только два числа последовательности Фибонначи. а для меньшей оценки (f(n) < On(n)) достаточно хранить только три числа. Последний раз редактировалось mazzy; 10.02.2017 в 20:53. |
|
![]() |
#3 |
Участник
|
Предыдущий мой пост зачеркнуть и забыть.
Цитата:
и объяснишь про "оптимизацию хвостовой рекурсии" для ряда чисел Фибоначчи? ну, или ссылки хотя бы ))) Последний раз редактировалось mazzy; 10.02.2017 в 21:03. |
|
![]() |
#4 |
Moderator
|
Когда функция вызвает другую функцию (себя, как частный случай) она кладет на стек (который имет фиксированный и довольно небольшой размер) переменные, которые надо передать в функцию, и адрес возврата.
В рекурсивных программах глубина рекурсии может быть большой и стек может "внезапно переполнится". Если компилятор поддерживает оптимизацию хвостовой рекурсии и мы правильно написали программу (оба момента важны - не любую рекурсию можно оптимизировать), то мы можем избежать использования стека. Для того, чтобы эта магия произошла рекурсивный вызов должен быть последней операцией в вызывающей функции. А результатом функции должен быть результат вот этого рекурсивного вызова. В этом случае адрес возврата и так находится в стеке, а значения параметров просто подменяются на новые. Получается, что стек не растет по мере рекурсивного вызова функций (а это критично для функциональных языков программирования, где цикл сделать не всегда возможно) независимо от глубины рекурсии. |
|
![]() |
#5 |
Участник
|
|
|
![]() |
#6 |
Moderator
|
А можно на примере факториала ? У меня просто слайды остались с одного из докладов http://www.evernote.com/l/ABEfDwoBJN...J5KRvLmJcd7RA/ (на примере F#). Там как раз показан неочивидный момент, когда рекурсия не может быть оптимизирована.
|
|
|
За это сообщение автора поблагодарили: mazzy (2). |
![]() |
#7 |
Участник
|
Не-а. ))))))
Для факториала как раз замечательно выполняется оптимизация хвостовой рекурсии даже вручную. На собеседовании могут давать факториал, а могут давать фибоначчи )))) Особое удовольствие опрашивающий получает, если кандидат врубается и сразу рассказывает об отличиях и применимости оптимизации хвостовой рекурсии. или сразу рассказывает об оценке сложности алгоритма. или еще что-нибудь интересное. бывает, конечно, когда кандидат не врубается и предлагает "удалиться". что ж... в этом случае вряд ли такому кандидату стоит задавать другие вопросы. |
|
![]() |
#8 |
Moderator
|
Она и для фибоначчи замечательно выполняется:
X++: (define (fib n) (fib-iter 1 0 n)) (define (fib-iter a b count) (if (= count 0) b (fib-iter (+ a b) a (- count 1)))) |
|
![]() |
#9 |
Участник
|
Цитата:
собственно возвращаемся к моему посту: Цитата:
Сообщение от mazzy
![]() тут дело даже не в функциональном языке или процедурном.
тут дело в ленивых вычислениях. ... именно ленивые вычисления и отсутствие побочных эффектов и позволяют так легко использовать рекурсию. ... обрати внимание, что для линейной оценки времени выполнения достаточно хранить только два числа последовательности Фибонначи. а для меньшей оценки (f(n) < On(n)) достаточно хранить только три числа. и вот я совсем не уверен, что оптимизация сработает с таким выражением. И именно эта ленивость и позволяет избавиться от повторных вычислений, не так ли? ленивость и отсутствие побочных эффектов. Если бы подобная ленивость была бы в обычном языке, то... Сейчас в .net вводят LINQ и Lazy-интерфейсы... это конечно не то, но достаточно близко... можешь рассказать подробнее как "это" работает? Цитата:
не... я надеялся, что ты расскажешь всем. у тебя это хорошо получается. кроме того, здесь есть и другие функциональщики, которые могут подключиться к обсуждению. в свое время я тебя приглашал, но у тебя была более привлекательная работа. до сих пор жалею, что тогда не смог предложить тебе лучшие условия. |
|
![]() |
#10 |
Moderator
|
И, извини, но я все-таки спрошу:
Цитата:
может, просто приведешь пример?
ну, или ссылки хотя бы ))) Цитата:
Для факториала как раз замечательно выполняется оптимизация хвостовой рекурсии даже вручную. На собеседовании могут давать факториал, а могут давать фибоначчи ))))
.... бывает, конечно, когда кандидат не врубается и предлагает "удалиться". что ж... в этом случае вряд ли такому кандидату стоит задавать другие вопросы. ![]() |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
![]() |
#11 |
Moderator
|
Цитата:
А в функциональном языке ленивое выражение (+ a b) будет в стеке n раз ))))
и вот я совсем не уверен, что оптимизация сработает с таким выражением. И именно эта ленивость и позволяет избавиться от повторных вычислений, не так ли? ленивость и отсутствие побочных эффектов. Половина функциональных языков программирования принципиально не поддерживают ленивость. Вот от слова "вообще". Нет ее там. А в тех языках где есть - я 10 раз подумаю, прежде чем ее использовать. Потому что в языках с поддержкой ленивых вычислений надо четко представлять, где и в какой момент это вычисление произойдет. А иначе оно произойдет в самый непоходящий для пользователя момент (в виде проблем с производительностью). Нет в моем примере никакой ленивости. Теперь побочные эффекты. Их здесь нет, но если этот код переписать на C# или Java - их тоже не появится. Им просто не откуда здесь взяться. Ну и вообще, если говорить про функциональные языки программирования - то из тех, что используются в production я знаю только 1 - haskell. Все остальные функциональные языки программирования допускают побочные эффекты. Цитата:
А в функциональном языке ленивое выражение (+ a b) будет в стеке n раз ))))
Я же описал выше, что произойдет ![]() При очередном рекурсивном вызове три параметра со стека будет сниматься и три добавляться. По сути - будет просто происходить их замена. Цитата:
можешь рассказать подробнее как "это" работает?
Все равно не понятно ? |
|
![]() |
#12 |
Участник
|
Получается, что тот кусок кода, который я написал вчера ночью за две минуты, укладывая сына спать, не имея Аксапты под рукой, добавил мне минус в карму?
|
|
![]() |
#13 |
Участник
|
Цитата:
ваш кусок кода выполняет вычисления за один проход без повторных вычислений. что является достижением. и потребляет фиксированную память. вот только int32 и панковский while(true)... ну и int2str... живите теперь с этим. )))) |
|
![]() |
#14 |
Участник
|
|
|
![]() |
#15 |
Moderator
|
Можно еще вот так попробовать расписать - вызовы функций, как они будут происходить
X++: fib 5 fib-iter 1 0 5 fib-iter 1 1 4 fib-iter 2 1 3 fib-iter 3 2 2 fib-iter 5 3 1 fib-iter 8 5 0 -> 5 Обрати внимание, что каждый последующий вызов - он самодостаточен. То есть. когда fib-iter 8 5 0 вернет 5-ку ему эту 5-ку надо просто вернуть, а не делать с ней что-то еще вверх по стеку вызовов. Это потому, что у нас последний вызов в функции выглядит так: X++: (fib-iter (+ a b) a (- count 1)))) X++: a + (fib-iter (+ a b) a (- count 1)))) Вот. Все - лучше мне уже на форуме не объяснить ![]() p.s. Цитата:
И именно эта ленивость и позволяет избавиться от повторных вычислений, не так ли?
Цитата:
обрати внимание, что для линейной оценки времени выполнения достаточно хранить только два числа последовательности Фибонначи. а для меньшей оценки (f(n) < On(n)) достаточно хранить только три числа.
![]() Последний раз редактировалось Андре; 10.02.2017 в 22:41. |
|
|
За это сообщение автора поблагодарили: mazzy (2). |
![]() |
#16 |
Участник
|
Цитата:
т.е. от повторных вычислений можно избавиться и в обычных процедурных языках? что-то типа такого Код: public var fib(var n) { fibb-iter(1, 0, n); } private var fib-iter(var a, var b, var n) { if(n <= 0) return b; else return fib-iter(a+b, b, n-1); } правда без "оптимизации хвостовой рекурсии" рекурсивные вызовы ограничены размером стека. а с оптимизацией стек вообще расти не будет. так? чертовы функциональщики... ))))) а разве функциональный язык не разворачивает всю эту байду в символьное супер-выражение, которое будет вычислено на самом последнем этапе? |
|
![]() |
#17 |
Участник
|
Цитата:
может, зря я на него бочку качу... |
|
![]() |
#18 |
Участник
|
Маззи, пожалуйста не приводите мой код как пример дешевого программиста
![]() Я выскочка с гуманитарным образованием. Для людей с неправильным происхождением свойственно натыкаться на грабли на пути к совершенству. Сейчас я понял, что моя вчерашняя попытка написать пример была ошибкой. Но из-за некоторых таких попыток мне все-таки удалось кое-чего достичь в жизни. И я пока еще выживаю в конкурентной борьбе. Я готов быть аутсайдером в окружении богов. Готов получать 500 тыс рублей среди людей, которые получают миллион рублей. Поэтому и встреваю в беседы великих ![]() |
|
|
За это сообщение автора поблагодарили: mazzy (2), ax_mct (5). |
![]() |
#19 |
Moderator
|
Цитата:
и получаем линейное, а не экспоненциальное время выполнения. и никаких повторных вычислений.
правда без "оптимизации хвостовой рекурсии" рекурсивные вызовы ограничены размером стека. а с оптимизацией стек вообще расти не будет. так? Цитата:
а разве функциональный язык не разворачивает всю эту байду в символьное супер-выражение, которое будет вычислено на самом последнем этапе?
|
|
|
За это сообщение автора поблагодарили: mazzy (2). |
![]() |
#20 |
Участник
|
Маззи, а как вам такой вариант?
X++: static void Job46(Args _args) { int j; real fibonaci(int n) { real first = 0; real second = 1; real result = 0; int i; for (i = 0; i < n; i++) { result = first + second; second = first; first = result; } return result; } ; for (j = 0; j < 50; j++) { info(strfmt("%1", fibonaci(j))); } } |
|
|
За это сообщение автора поблагодарили: mazzy (2). |