LINUX.ORG.RU

Рекурсия против циклов

 


0

4

Есть г..код на Haskell и Ruby который просто перебирает комбинации - «aa», «ab», «ac» ... «az», «ba» ... «zz». При строке «aaaaaa» код на Haskell справился за 15 минут а на Ruby за 3. Почему Haskell так тормозит? Или скорость проявляется только при компиляции(код запускался в ghci)?

import Data.Char (ord, chr)

mut :: String -> String
mut s 
    | ch == 'z' = mut (init s) ++ "a"
    | otherwise = (init s) ++ [chr $ (+1) $ ord ch]
    where ch = last s

test :: String -> String
test s
    | s /= "zzzzzz" = test $ mut s
    | otherwise = s
def mut s
    if s[-1] == ?z then
        s = mut(s[0,s.length-1]) + ?a
    else
        s[-1] = s[-1].succ
    end
    s
end

def test s
    start = Time.now.to_i
    while (s != "zzzzzz")
        s = mut s
    end
    puts Time.now.to_i - start
end

Или скорость проявляется только при компиляции(код запускался в ghci)?

Проверь

vertexua ★★★★★
()

Перепиши все! У тебя код неэквивалентный. Практически уверен, хотя Руби не знаю.

mut (init s) ++ "a"

Ты себе представляешь как работает функция (++) в хаскеле? А как работает init?

Ты еще скажи хаскелю спасибо, что на твоем алгоритме так быстро отработал!

dave ★★★★★
()

Медленней раби быть невозможно, ты же это понимаешь и сам.

anonymous
()
Ответ на: комментарий от dave

Перепишу, скажи как. ++ конкат 2х листов, init функция возвращает список без последнего элемента.

RA
() автор топика
Ответ на: комментарий от RA

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

ЗЫ. вернее ласт не пересобирает, а проходит по всем элементам. от чего не сильно легче.

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)
Ответ на: комментарий от RA

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

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 1)

хаскелевская рекурсия по-идее не должна отличаться от циклов, там ведь TCO вроде есть. Видимо, какие-то фейлы дезигнеров

portquest2016
()

Или скорость проявляется только при компиляции

this. Собери бинарник с ghc -O2 и проверь.

Esper
()
Ответ на: комментарий от RA

Ты о чем-то таком? Совершенно лень вникать.

Prelude> let stupidPerms xs = if null xs then [[]] else [ y : ys | y <- ['a' .. 'z'], ys <- stupidPerms (tail xs) ]

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

dave ★★★★★
()
Ответ на: комментарий от AndreyKl

чет лень разбирать, в силу средней степени опьянения:), но обычно в таких случаях падение производительности проявляется в том, что невозможно выйти из цикла в нужном месте. То есть, даже если компилятор и разворачивает рекурсию в цикл,он не способен определить, где именно стоило бы выйти из цикла, где дальнейшие вычисления бессмысленны. В схеме для этого есть костыль в виде Ъ-continuations, а в хаскеле с этим не особо. Я, кстати, соврал, что хаскелевская рекурсия не должна отличатся от цикла, в смысле безусловных выходов она, таки, должна отличаться, она не отличается только в смысле расхода памяти и связаных с этим тормозов

portquest2016
()
Ответ на: комментарий от dave

кстати, тут надо заменить 'a', но это очевидно

dave ★★★★★
()
Ответ на: комментарий от portquest2016

чет лень разбирать, в силу средней степени опьянения:)

тогда поясню, раз уж ты залез сюда в таком состоянии)

но обычно в таких случаях падение производительности проявляется в том, что невозможно выйти из цикла в нужном месте.

да не, это не то, там банально init и last в рекурсии всё время дёргаются, в то время как в рубишной версии для доступа к последнему элементу используется индекс.

AndreyKl ★★★★★
()
Ответ на: комментарий от RA

понятно, но как я понимаю данные иммутабельные

ну есть библиотеки с данными вроде MArray MVector, где под капотом ST или IO монада

а значит только новые списки?

если алгоритм позволяет (или переделанный алгоритм), то можно и списком (linked list), хвост - то реализация расшарит наверняка, а GC недостижимые дропнет

а так, да на haskell вполне можно нарваться на проблемы такого рода, где-то алгоритм не подходящий, банально не хватает `seq` - и понеслась..

anonymous
()

код на Haskell справился за 15 ... а на Ruby за 3

такой думаю: «хм, неплохо для руби», а потом вчитался: «минут». МИНУТ, КАРЛ, МИНУТ!!! НЕ СЕКУНД!

anonymous
()

Это хорошо, что подобные индивидуумы пробуют хаскель через написание кривых алгоритмов, а потом делают соответствующие выводы и оставляют его. Нужно же «избегать популярности любой ценой» (С) :)

al = ['a'..'z']

foo n = length $ iterate (\l -> l >>= \x -> map (:x) al) [[]] !! n

main = print $ foo 6
Успешно	time: 2.38 memory: 5724 signal:0
308915776
https://ideone.com/Go72q7 2 секунды, Карл.

Ivana
()
Ответ на: комментарий от Ivana

iterate (\l -> l >>= \x -> map (:x) al)

Интересно, илитные цацкелисты в принципе не умеют писать читабельный код, или делают это специально, из какирской гордости?

Esper
()
Ответ на: комментарий от Esper

Пожалуйста, для ТС-подобных можно написать и читабельнее :)

iterate (flip map al . flip (:) =<<)
ЗЫ элитные хаскелисты не озадачиваются проблемой «написать читабельно для полного нуля».

Ivana
()
Ответ на: комментарий от Ivana

Пожалуйста, для ТС-подобных можно написать и читабельнее :)

Типа чтобы ему страшно стало и он бросил это дело?

элитные хаскелисты не озадачиваются проблемой «написать читабельно для полного нуля»

По-моему, на нём для полного нуля и не напишешь. Просто мне, как частичному нулю, непонятно, нафига делать это через монады.

Esper
()
Ответ на: комментарий от Esper

Типа чтобы ему страшно стало и он бросил это дело?

Чтобы он увидел 2 секунды, понял, что он начал в хаскеле крайне неудачно, бросил и начал снова - но не втупую переписывая императивные алгоритмы, а по-нормальному. А то топик в высшей степени комичен - надевать штаны через голову и ругать их что они так «тормозят»...

По-моему, на нём для полного нуля и не напишешь. Просто мне, как частичному нулю, непонятно, нафига делать это через монады.

А почему бы не через листомонаду - если она здесь ложится на задачу как доктор прописал? К тому же она достаточно проста. Хотя конечно сделать можно и по-другому, разными способами. Но те же листокомпрехеншенсы (вышкприведенные) - это банальный сахар для все той же листомонады.

Ivana
()
Ответ на: комментарий от Ivana

не втупую переписывая императивные алгоритмы, а по-нормальному

Ваще-т сам алгоритм нормальный, проблема в непонимании, как работает список.

К тому же она достаточно проста.

Проще видали: iterate (concatMap $ \x -> map (:x) al).

Но те же листокомпрехеншенсы (вышкприведенные) - это банальный сахар для все той же листомонады.

Что, правда? А как они рассахариваются?

Esper
()
Ответ на: комментарий от Esper

Проще видали

так конкатмап и есть листомонадный бинт. Проще - вопрос привычки.

А как они рассахариваются?

Семантически эквивалентно - через дунотацию вот так:

x = [ (a,b) | a <- [1..3], b <- [1..3], a/=b ]

y = do
    a <- [1..3]
    b <- [1..3]
    guard $ a/=b
    return (a, b)

main = print $ x == y
Но и то и другое в коре на самом деле рассахаривается в последовательно вложенные монадные бинты с лямбдами. Детальнее надо у элитных хаскелистов (С) спрашивать.

Ivana
()
Ответ на: комментарий от Ivana

Но и то и другое в коре на самом деле рассахаривается в последовательно вложенные монадные бинты с лямбдами.

Как do-нотация рассахаривается, я в курсе. А вот про guard в первый раз услышал. И вообще, я думал, что листомонада - это дурацкая приблуда для кодгольфа в духе Applicative ((->) a), а оказалось, такая базовая вещь. Прикольно.

Кстати, а почему ты (>>=) называешь «бинтом»?

Esper
()
Ответ на: комментарий от Esper

Любая монада - это базовая вещь, на которой и строятся все сахарные дунотации и монад компрехеншенсы (не только листовые, такой же синтаксис применим к любой монаде)

Кстати, а почему ты (>>=) называешь «бинтом»?

потому же, почему и код называю котом :) Но в случае с бинтом еще и аналогия со «связыванием» добавляется.

Ivana
()
Ответ на: комментарий от anonymous

Неудачно выебнулся:

согласен. Ну так если не умеешь выебываться - не выебывайся :)

Код-то не работает!

Ога: https://ideone.com/Ib6BQN

ЗЫ Продолжай выебываеться, может следующие попытки будут более удачны.

Ivana
()
Ответ на: комментарий от anonymous

а, =<< же не в скобочках. тогда ок все

anonymous
()

я тут это... код запустил.. мужик, худшего алгоритма перебора я не видел. про 3 минуты это не описка нифига (как и про 15). Чёрт побери, надо куда нибудь рекорды писать.

хорошая новость в том что тебе есть куда расти)

AndreyKl ★★★★★
()
Последнее исправление: AndreyKl (всего исправлений: 2)

А попрофайлить?

Я почему-то думаю, что большая часть времени тратится на init, last и (++).

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Как исправлять. Либо а) учесть особенности стандартного хаскельного String (это односвязный список символов; односвязный список заточен под работу с началом, а не с концом; так что тут достаточно будет перевернуть строки и менять сначала первую букву, потом вторую и т.д.), либо б) использовать другой, более подходящий строковый тип — ByteString, например; хотя и тогда будет тормозить на создании новых копий, так что можно совсем уж для скорости заюзать STArray и юзать его императивно.

Miguel ★★★★★
()
Ответ на: комментарий от Ivana

подобные индивидуумы
ТС-подобных

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

ругать их что они так «тормозят»

где я что то ругал? Или задание простого вопроса о работе Haskell это ругань?

С чего ты взял что мне нужно сгенирировать все возможные последовательности? Возможно это был просто тест работы функии которая начинает перебор отправля каждое новое значение в предикат? Ты как то много делаешь выводов...

RA
() автор топика
Ответ на: комментарий от AndreyKl

Решал эту задачу http://adventofcode.com/2015/day/11

По идеи мне не нужны все значение, ведь буквально следующая комбинация может быть верной. По первому этапу на этом алгоритме отрабатывает меньше чем за секунду.

Miguel пробовал использовать head reverse, еще дольше, такое ощущение что при реверсе берется не тот же список а пересобирается либо где то я накосячил.

Esper dave

RA
() автор топика
Ответ на: комментарий от RA

Ничего страшного нет в том, что возвращаются все значения, потому что они возвращаются лениво и тут же зачищаются сборщиком мусора. Список в haskell часто выступает как итератор в других языках.

Кстати, довольно типично, когда мы обобщаем задачу, чтобы решить частную задачу. Так иногда получается проще, а в данном случае и эффективнее.

Можно, конечно, и запрограммировать подход Ruby и в хаскеле, но зачем? Тогда нужно будет использовать Text и его билдеры. Фу, слишком громоздко, да и ни к чему!

dave ★★★★★
()
Последнее исправление: dave (всего исправлений: 1)
Ответ на: комментарий от RA

Эта штука со списком еще потоком называется, и потенциально она может быть бесконечной. Это один из изящных приемов программирования на Haskell.

Собственно, чтобы запустить поток, здесь мы запрашиваем его длину, что и переберет всю последовательность. Если не запросим, то ни один элемент последовательности даже и не подумает вычислиться!

dave ★★★★★
()
Последнее исправление: dave (всего исправлений: 1)
Ответ на: комментарий от RA

Попробуй для тестов вместо length вызвать, скажем, take 10 и оценить скорость. Take 10 переберет только 10 элементов последовательности. Это будет работать, даже если поток бесконечный

dave ★★★★★
()
Ответ на: комментарий от Ivana
al = ['a'..'z']

foo n = length $ iterate (\l -> l >>= \x -> map (:x) al) [[]] !! n

main = print $ foo 6

И эти люди ещё говорят, что Perl это ужасный язык, код на котором не читаем?

WARNING ★★★★
()
Ответ на: комментарий от RA

такое ощущение что при реверсе берется не тот же список а пересобирается

Естественно. Иммутабельность же.

Речь не о том, чтобы ревёрс делать. Речь о том, чтобы всегда работать с ревёрснутыми строчками.

Либо использовать другой тип данных.

Miguel ★★★★★
()
Ответ на: комментарий от Miguel

Ну так-то, да, сразу всё понятно и читабельность на высоте.

WARNING ★★★★
()
Ответ на: комментарий от RA

Решал эту задачу http://adventofcode.com/2015/day/11

inc [] = []
inc (c:cs) | c=='z' = 'a' : inc cs
           | otherwise = succ c : cs
 
valid s = p1 && p2 && p3 where
    l = map fromEnum s
    p1 = any id $ zipWith3 (\c b a -> b-a==1 && c-b==1) l (tail l) (tail.tail $ l)
    p2 = not . any (flip elem ("iol" :: String)) $ s
    lg = map length . group $ s
    p3 = any (>=4) lg || ((>=2) . length . filter (>=2) $ lg)
 
main = print . reverse. until valid inc . inc . reverse $ "hepxcrrq"
Успешно	time: 0.07 memory: 4708 signal:0

"hepxxyzz"

0.07 секунд, Карл! И вообще - http://www.cyberforum.ru/algorithms/thread1809345.html

Ты как то много делаешь выводов...

Зато ты их не делаешь. Я далеко не «элитный хацкелист», но тебе в этом топике написали немало вещей, над которыми можно призадуматься и сделать выводы. Но ты вместо этого продолжаешь ныть и звать бабушку и прочих «добрых советчиков» в топик... Хотя, это у тебя «хаскель тормозит», мне то действительно какое дело. Просто смешно, когда на ЛОРе появляется очередной специалист и говорит свое традиционное «еда невкусная, все плохо сделано...» (С)

Ivana
()
Ответ на: комментарий от Miguel

Короче можно

Сокращать - так сокращать!

foo = length . (iterate (>>= \x -> map (:x) al) [[]] !!)
Esper
()
Ответ на: комментарий от RA

В целом нормуль для начала, я думаю. Комментарии немного злые, но это ЛОР, что уж там, спасибо что совсем не закидали)

Я так понял ты тренируешься. В этом контексте решения предложенные другими участниками я бы изучил подробно и разобрался почему они быстрее работают. Это неплохая практика на мой взгляд.

Успехов!!

AndreyKl ★★★★★
()
Ответ на: комментарий от AndreyKl

Спасибо!! dave напомнил про линивость haskell и многое стало на свои места. Да, только осваиваю haskell и что бы прочитаное как то закреплять пытаюсь решать задачи(эта была первая). Есть опыт программирования на императивном языке и он несколько мешает, могу долго смотреть на решение какой то задачи и не понимать как это вобще это может работать, пока не вспомню о «потоке», о потребителях))

RA
() автор топика
Ответ на: комментарий от RA

Имей в виду, что вариант c iterate работает быстрее и намного, чему я был несколько удивлен)

dave ★★★★★
()
Ответ на: комментарий от RA

А, вообще, вот, что у меня получилось:

import System.Environment

myPerms :: String -> [String]
myPerms [] = [[]]
myPerms (x : xs) = [y : ys | y <- [x .. 'z'], ys <- myPerms xs]

main =
  do [s] <- getArgs
     print $ length (myPerms s)
dave ★★★★★
()
Последнее исправление: dave (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.