[решено] Haskell числа фибоначчи (скорость)

Модератор: Модераторы разделов

frp
Сообщения: 1445
ОС: Debian Squeeze

[решено] Haskell числа фибоначчи

Сообщение frp »

Нашел туториал по Haskell. Первый пример:

Код: Выделить всё

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

- ghci:
>:load file.hs
>fib 1
1
>fib 5
5
>fib 50
... и тут очень долго ждем, пока оно посчитает ...

А все, насколько я понял, очень просто - функция рекурсивная и одно и то же вычисляется очень много раз. Как сделать быстрее?

PS. Никакого опыта функционального программирования не имею
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: [решено] Haskell числа фибоначчи

Сообщение drBatty »

frp писал(а):
12.06.2010 16:28
А все, насколько я понял, очень просто - функция рекурсивная и одно и то же вычисляется очень много раз. Как сделать быстрее?

PS. Никакого опыта функционального программирования не имею

никак.
есть формула, связанная с золотым сечением, поищите в вике, она считает сразу, и формула примитивная.
(она даёт нецелые числа - округлите)
но если у вас алгоритм изначально рекурсивный, то вы его не развернётё. Может математики и будут со мной спорить, но на практике это так - например возмите структуру файловой системы, она рекурсивная, и все программы для её обработки - тоже. Ваша формула тоже рекурсивная, и она не разворачивается.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: [решено] Haskell числа фибоначчи

Сообщение drBatty »

PS: тут можно оптимизировать - допустим функция даёт 2 результата: fib(n-1) и fib(n). тогда не надо вычислять 2 раза в каждом вызове, а тот тут в каждой итерации приходится считать например для 4
(2 + 1) + ((2 + 1) + 3)
а можно просто
1) fib(3) = (2+1), 2
2) fib (4) = fib(3) + fib(2)
что даёт нам 2 сложения, а не 4.
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: [решено] Haskell числа фибоначчи

Сообщение t.t »

Математики спорить не будут, только одно уточнение сделают: та прямая формула, о которой Вы говорите, и была получена "развёртыванием" рекурсивной последовательности. В остальном всё верно: программными методами её развернуть не удастся; математическими можно лишь заново вывести ту же самую формулу.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
drBatty
Сообщения: 8735
Статус: GPG ID: 4DFBD1D6 дом горит, козёл не видит...
ОС: Slackware-current

Re: [решено] Haskell числа фибоначчи

Сообщение drBatty »

PS: это так называемая "хвостовая рекурсия", применяется в любых языках начиная с машинных кодов, если нам надо выполнить 2 рекурсивных действия, то мы выполняем рекурсивно только первое, а второе может выполнить и сама программа. не сомневаюсь, такое есть и в вашем языке. только я не знаю как (да и не очень хочу знать - мозг сломаю) ;)
http://emulek.blogspot.ru/ Windows Must Die
Учебник по sed зеркало в github

Скоро придёт
Осень
Спасибо сказали:
neol
Сообщения: 600
ОС: Debian Stable

Re: [решено] Haskell числа фибоначчи

Сообщение neol »

Haskell не знаю, следующий код взял с одного очень древнего холивара (ссылка). Кое-какое описание там приводится.

Код: Выделить всё

import IO
import CPUTime
import Numeric

fib = 1 : 1 : [a + b | (a, b) <- zip fib (tail fib)]

main = do
  putStrLn (show (fib!!46))
  time <- getCPUTime
  putStrLn (showFFloat (Just 6) (fromInteger time / 1e12) "")
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: [решено] Haskell числа фибоначчи

Сообщение Portnov »

Собственно, всё уже объяснили. Код в первом посте хорош наглядностью. Но использует т.н. "древовидную рекурсию" (когда каждый вызов fib вызывает fib ещё 2 раза, те, в свою очередь - каждый ещё по 2, итд). Вот вам и тормоза. Собственно, то же самое получим при попытке этот код переписать дословно, скажем, на паскале или С.

Методов избавления от таких тормозов два. Первый - тот же, что императивных языках: мемоизация. Тут, правда, свои методы. Вот код в посте neol использует именно мемоизацию. Определяемый в нём список fib - это бесконечный ленивый список, к моменту вычисления n-го элемента списка все предыдущие уже вычислены.

Кстати, ту же строчку можно записать покрасивее:

Код: Выделить всё

fib = 1: 1: zipWith (+) fib (tail fib)


Другой метод "избавления от тормозов" специфичен для ФЯ: преобразование рекурсии к виду хвостовой рекурсии, такую компилятор развернёт в цикл.

Незнакомые термины см. в википедии ;)
Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
Crazy
Сообщения: 862
Статус: Адепт Дзен.
ОС: Mint, Win7.

Re: [решено] Haskell числа фибоначчи

Сообщение Crazy »

Я понимаю, что рекурсия от бога, а итерация от человека, но числа Фибоначчи не тот случай.
Это как раз тот случай, когда лучше использовать итерацию. :happy:

Desipere in loco
Спасибо сказали:
watashiwa_daredeska
Бывший модератор
Сообщения: 4038
Статус: Искусственный интеллект (pre-alpha)
ОС: Debian GNU/Linux

Re: [решено] Haskell числа фибоначчи

Сообщение watashiwa_daredeska »

neol писал(а):
12.06.2010 17:40
Haskell не знаю, следующий код взял с одного очень древнего холивара
Квинтэссенция холивара: The Evolution of a Haskell Programmer. Как раз про факториалы :)
Спасибо сказали:
Аватара пользователя
Crazy
Сообщения: 862
Статус: Адепт Дзен.
ОС: Mint, Win7.

Re: [решено] Haskell числа фибоначчи

Сообщение Crazy »

watashiwa_daredeska писал(а):
12.06.2010 18:25
neol писал(а):
12.06.2010 17:40
Haskell не знаю, следующий код взял с одного очень древнего холивара
Квинтэссенция холивара: The Evolution of a Haskell Programmer. Как раз про факториалы :)


Не Haskell, но все равно выглядит убого.

Код: Выделить всё

#include <iostream>

typedef unsigned int uint;

void fibonachi( uint& Rez,  uint n, uint pos, uint old1, uint old2)
{
    switch(n)
    {
        case 0: Rez = 0; break;
        case 1: Rez = 1; break;
    }
    if( n != pos)
    {
        Rez = old1;
        fibonachi(Rez, n, pos+1, old1 + old2, Rez);
    }
}
int recFib(int n)
{
    if( n == 2)    return 1;
    if( n == 1) return 0;
    return ( recFib(n-1) + recFib(n-2) );
}

int main()
{
    uint rez = 0;
    fibonachi(rez, 10, 0, 0 , 1);
    std::cout<< rez  <<std::endl;
    std::cout<< recFib(10) <<std::endl;
    return 0;
}

Desipere in loco
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: [решено] Haskell числа фибоначчи

Сообщение NickLion »

Crazy писал(а):
12.06.2010 18:18
Это как раз тот случай, когда лучше использовать итерацию. :happy:

Это как раз тот случай, когда лучше использовать формулу ;)
tau = (sqrt(5.0)+1.0)/2.0;
Fibb(n) = (int) floor(0.5+(pow(tau,n)-pow(-tau,-n))/sqrt(5.0)));

PS извиняюсь, что не Хаскелль, а С, в связи с полным незнанием первого.

PPS формула точна - округление - для борьбы с эффектами ограниченной точности FPU.
Спасибо сказали:
frp
Сообщения: 1445
ОС: Debian Squeeze

Re: [решено] Haskell числа фибоначчи

Сообщение frp »

Crazy писал(а):
12.06.2010 18:18
Я понимаю, что рекурсия от бога, а итерация от человека, но числа Фибоначчи не тот случай.
Это как раз тот случай, когда лучше использовать итерацию. :happy:

Покажите мне итерацию в чисто функциональном ЯП.
А с приведенными примерами разобрался.
Спасибо сказали:
Аватара пользователя
Crazy
Сообщения: 862
Статус: Адепт Дзен.
ОС: Mint, Win7.

Re: [решено] Haskell числа фибоначчи

Сообщение Crazy »

NickLion писал(а):
12.06.2010 20:21
Crazy писал(а):
12.06.2010 18:18
Это как раз тот случай, когда лучше использовать итерацию. :happy:

Это как раз тот случай, когда лучше использовать формулу ;)

Это скучно и не интересно.

Desipere in loco
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: [решено] Haskell числа фибоначчи

Сообщение NickLion »

Crazy писал(а):
12.06.2010 21:33
Это скучно и не интересно.

Не скажите, вывести эту формулу и её запрограммировать намного интереснее, нежели в сотый раз писать цикл.
Спасибо сказали:
Аватара пользователя
sash-kan
Администратор
Сообщения: 13939
Статус: oel ngati kameie
ОС: GNU

Re: [решено] Haskell числа фибоначчи

Сообщение sash-kan »

NickLion писал(а):
12.06.2010 20:21
Это как раз тот случай, когда лучше использовать формулу
как насчёт числа фибоначчи от, к примеру, 500000? предупреждаю — у меня в буфер терминала точное значение не поместилось. последние цифры, для сверки с точностью вашего алгоритма: 18469780453125.
Писать безграмотно - значит посягать на время людей, к которым мы адресуемся, а потому совершенно недопустимо в правильно организованном обществе. © Щерба Л. В., 1957
при сбоях форума см.блог
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: [решено] Haskell числа фибоначчи

Сообщение Portnov »

frp писал(а):
12.06.2010 20:53
Покажите мне итерацию в чисто функциональном ЯП.


Итерация она разная бывает тоже. Ну хотя бы:

Код: Выделить всё

$ ghci
Prelude> map (^2) [1..10]
[1,4,9,16,25,36,49,64,81,100]


Или там:

Код: Выделить всё

Prelude> import Control.Monad
Prelude Control.Monad> forM [1..10] print
1
2
3
4
5
6
7
8
9
10
[(),(),(),(),(),(),(),(),(),()]


:)
Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
sash-kan
Администратор
Сообщения: 13939
Статус: oel ngati kameie
ОС: GNU

Re: [решено] Haskell числа фибоначчи

Сообщение sash-kan »

upd. вычисления, кстати, заняли 8 секунд.

Код: Выделить всё

11> Start=time(),F=fibonacci:f(500000),End=time().
{22,31,37}
12> Start.
{22,31,29}

для 1000000 считалось немножко дольше. одну минуту:

Код: Выделить всё

13> f().
ok
14> Start=time(),F=fibonacci:f(1000000),End=time().
{22,34,18}
15> Start.
{22,33,18}
последние цифры: 9893411568996526838242546875

p.s. это erlang.
Писать безграмотно - значит посягать на время людей, к которым мы адресуемся, а потому совершенно недопустимо в правильно организованном обществе. © Щерба Л. В., 1957
при сбоях форума см.блог
Спасибо сказали:
NickLion
Сообщения: 3408
Статус: аватар-невидимка
ОС: openSUSE Tumbleweed x86_64

Re: [решено] Haskell числа фибоначчи

Сообщение NickLion »

sash-kan писал(а):
12.06.2010 22:23
NickLion писал(а):
12.06.2010 20:21
Это как раз тот случай, когда лучше использовать формулу
как насчёт числа фибоначчи от, к примеру, 500000? предупреждаю — у меня в буфер терминала точное значение не поместилось. последние цифры, для сверки с точностью вашего алгоритма: 18469780453125.

Немного не понял к чему это. Какой бы алгоритм ни использовать, для получения точного значения 500000-го числа Фиббоначчи нужно использовать длинную арифметику.
Впрочем, проверил - приближённое значение тоже не влезло в тип double. Так что надо писать длинку.
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: [решено] Haskell числа фибоначчи

Сообщение t.t »

sash-kan писал(а):
12.06.2010 22:23
NickLion писал(а):
12.06.2010 20:21
Это как раз тот случай, когда лучше использовать формулу
как насчёт числа фибоначчи от, к примеру, 500000? предупреждаю — у меня в буфер терминала точное значение не поместилось. последние цифры, для сверки с точностью вашего алгоритма: 18469780453125.
Насколько я помню, формула Бине точна для любых значений n.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: [решено] Haskell числа фибоначчи

Сообщение Portnov »

Формула-то точна, а в сопроцессор не любые числа влезают :) А если использовать длинную арифметику, так не всё ли равно - складывать по определению или возводить в степень по формуле? Кстати, если будут оценки, что быстрее - тоже интересно.
Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
sash-kan
Администратор
Сообщения: 13939
Статус: oel ngati kameie
ОС: GNU

Re: [решено] Haskell числа фибоначчи

Сообщение sash-kan »

upd2. ага, это я слишком примитивный алгоритм использовал.
вот тут он как номер два изложен.
а если воспользоваться номером три, то 1000000 вычисляется за четыре секунды.

NickLion писал(а):
12.06.2010 22:47
Немного не понял к чему это.
вы написали, что вычисление происходит точное. я и предложил проверить.

t.t писал(а):
12.06.2010 22:53
Насколько я помню, формула Бине точна для любых значений n.
это которую NickLion предлагает? звыняйтэ, в арифметике не силён. может и точно посчитает, только где ж такой компьютер взять, чтоб, допустим fib(1000000) посчитать?
Писать безграмотно - значит посягать на время людей, к которым мы адресуемся, а потому совершенно недопустимо в правильно организованном обществе. © Щерба Л. В., 1957
при сбоях форума см.блог
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: [решено] Haskell числа фибоначчи

Сообщение Portnov »

Мдя, а эрланг оказывается медленный :) Тот же "алгоритм 3" по той же ссылке, но на хаскеле:

Код: Выделить всё

fibo 0 _ pair = pair
fibo n (fib1,fib2) pair
  | n `mod` 2 == 0 = let squareFib = fib1*fib1
                     in  fibo (n `div` 2) (2*fib1*fib2 - squareFib, squareFib + fib2*fib2) pair
fibo n pair@(fibA1,fibA2) (fibB1,fibB2) =
  fibo (n-1) pair (fibA1*fibB2 + fibB1*(fibA2 - fibA1), fibA1*fibB1 + fibA2*fibB2)

fibo3 n = fst $ fibo n (1,1) (0,1)

main = forM_ [500000, 1000000] (print . fibo3)


(печатает значения по очереди для 500000 и 1000000):

Код: Выделить всё

$ time ./fib
./fib  0,40s user 0,01s system 72% cpu 0,564 total
Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
sash-kan
Администратор
Сообщения: 13939
Статус: oel ngati kameie
ОС: GNU

Re: [решено] Haskell числа фибоначчи

Сообщение sash-kan »

Portnov писал(а):
12.06.2010 23:24
а эрланг оказывается медленный
а кто говорил, что он быстрее компилируемого языка? не в плавках его сила.

p.s. да и машина у меня по нынешним меркам — слабачок. amd64 800MHz, 1Gb memory.
p.p.s. в мегагерцах не уверен. по крайней мере так говорит:
$ grep -i hz /proc/cpuinfo
cpu MHz : 800.000
Писать безграмотно - значит посягать на время людей, к которым мы адресуемся, а потому совершенно недопустимо в правильно организованном обществе. © Щерба Л. В., 1957
при сбоях форума см.блог
Спасибо сказали:
frp
Сообщения: 1445
ОС: Debian Squeeze

Re: [решено] Haskell числа фибоначчи

Сообщение frp »

Натолкнулся на еще одну проблему.

Код: Выделить всё

fact = 0:1:2:[ fact!!(n-1) * n | n <- [3,4..] ]

Список факториалов (ниоткуда не копировал, сам написал). Дальше 15 выдает неправильный результат. В чем проблема?

UPD. Происходит переполнение. Как его избежать?
Спасибо сказали:
frp
Сообщения: 1445
ОС: Debian Squeeze

Re: [решено] Haskell числа фибоначчи

Сообщение frp »

frp писал(а):
13.06.2010 19:43
UPD. Происходит переполнение. Как его избежать?

Пока решил через костыли:

Код: Выделить всё

natural :: [Int]
natural = [3,4..]
fact :: [Integer]
fact = 0:1:2:[ fact!!(n-1) * toInteger n | n <- natural ]
Спасибо сказали:
frp
Сообщения: 1445
ОС: Debian Squeeze

Re: [решено] Haskell числа фибоначчи

Сообщение frp »

Костыль получилось значительно сократить и сделать не костылем:

Код: Выделить всё

fact = 0:1:2:[ fact!!(n-1) * toInteger n | n <- [3,4..] ]
Спасибо сказали:
Аватара пользователя
Portnov
Модератор
Сообщения: 1786
Статус: Матёрый линуксоид
ОС: Debian testing/unstable

Re: [решено] Haskell числа фибоначчи

Сообщение Portnov »

Ну про факториалы-то выше вон хорошую ссылку привели :)
Работа: Ubuntu 9.10
Дом: Debian testing/unstable и на всякий случай winxp в virtualbox.
Для разнообразия: моя домашняя страница -http://iportnov.ru
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: [решено] Haskell числа фибоначчи

Сообщение t.t »

Portnov писал(а):
12.06.2010 22:59
Формула-то точна, а в сопроцессор не любые числа влезают :) А если использовать длинную арифметику, так не всё ли равно - складывать по определению или возводить в степень по формуле? Кстати, если будут оценки, что быстрее - тоже интересно.
Ответ очевиден: зависит от реализации длинной арифметики. (: Да и от реализации развёртывания рекурсии, строго говоря, тоже. Так что в общем случае эта задача решения не имеет. (:
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
Crazy
Сообщения: 862
Статус: Адепт Дзен.
ОС: Mint, Win7.

Re: [решено] Haskell числа фибоначчи

Сообщение Crazy »

t.t писал(а):
12.06.2010 22:53
sash-kan писал(а):
12.06.2010 22:23
NickLion писал(а):
12.06.2010 20:21
Это как раз тот случай, когда лучше использовать формулу
как насчёт числа фибоначчи от, к примеру, 500000? предупреждаю — у меня в буфер терминала точное значение не поместилось. последние цифры, для сверки с точностью вашего алгоритма: 18469780453125.
Насколько я помню, формула Бине точна для любых значений n.

Вы батенька забыли про инструментальную погрешность. Очень забавляет, когда математики с легким росчерком пера пишут, что что-то можно вычислять сколь угодно точно.

Desipere in loco
Спасибо сказали:
frp
Сообщения: 1445
ОС: Debian Squeeze

Re: [решено] Haskell числа фибоначчи

Сообщение frp »

Crazy писал(а):
13.06.2010 21:44
что-то можно вычислять сколь угодно точно.

Можно. Создав умную программу, которая будет каждый раз разворачивать эту формулу :) выполнять над ней всякие мат. преобразования :) и .т.д. и т.п., но это медленнее, чем посчитать традиционным способом (хотя... бинарное возведение в степень, прочие оптимизации, может, и немного быстрее будет).
Спасибо сказали: