Решаю задачки на projecteuler.
Возникла проблема с решение задачи 14 на haskell-е (на С++ я её решил без проблем).
The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even)
n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence:
13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.
Алгоритм решение прост. Бежим по всем числам от 1 до 10^6 и параллельно заполняем таблицу
(число, длина последовательности). Если в процессе вычисления какой-то последовательности
встретилось число из таблицы, тогда берем длину для него.
Вот код. Работает при nnn = 10000.
При nnn = 1000000 думает очень долго.
module Main
where
import Array
-- Верхний предел для поиска
nnn :: Int
nnn = 1000000
-- Опредение функции f
f :: Integer -> Integer
f 1 = 1
f n | even n = n `div` 2
| otherwise = 3 * n + 1
-- Создаем список [(число, длина последовательности)]
seqlen :: [(Int, Int)]
seqlen = assocs $ seqlen' (array (1, nnn) $ (1,1):[(a, 0) | a <- [2..nnn]]) [2..(toInteger nnn)]
where
seqlen' a [] = a
seqlen' a (x:xs) = seqlen' na xs
where na = accum seq a $ snd $ test a x
test a x | x > toInteger nnn = (n + 1, l)
| elem == 0 = (n + 1, (fromInteger x, fromInteger n + 1):l)
| otherwise = (toInteger elem, [])
where (n, l) = test a (f x)
elem = a ! (fromInteger x)
-- Возвращает (число, длина последовательности)
maxseq :: (Int, Int)
maxseq = foldr (\x y-> if (snd x) > (snd y) then x else y ) (1,1) seqlen
main :: IO ()
main = do
putStrLn (show maxseq)
Можно ускорить?
Заменить везде Integer на Int не получается (некоторые члены последовательности не влезают).
Здравствуйте, Smal, Вы писали:
S>Привет.
S>Решаю задачки на projecteuler. S>Возникла проблема с решение задачи 14 на haskell-е (на С++ я её решил без проблем).
Я решал эту задачу на Q — интерпретируемом языке и работала она достаточно быстро (может в минуту и не укладывалась), так что думай над алгоритмом.
Поиск в таблице у Вас какую сложность имеет?
Мое решение на OCaml использовало Int64 для значений и хэш-таблицу для хранения уже найденных. Работало 4,4 секунды. Исходник на 10-й странице обсуждения задачи.
Здравствуйте, palm mute, Вы писали:
PM>Мое решение (компилировать обязательно с оптимизацией):
Спасибо за пример. Я разобрался. Как я и думал, я не правильно работал с массивом.
Я пытался менять значения в массиве, а надо было изначально заполнить его,
а потом просто вычислять.
Действительно, если не запоминать результаты для чисел больше миллиона, то быстрее.
Такой вариант на OCaml отрабатывает на 2,75 сек на P4 2,66:
open Int64;;
let mem = Hashtbl.create 10000000 and i2 = of_int 2 and i3 = of_int 3 and mil=of_int 1000000;;
let f n = if (rem n i2) = zero then div n i2 else add (mul i3 n) one;;
let rec rlen n = if n = one then 1 else
if n >= mil then 1 + rlen(f n) else
try Hashtbl.find mem n with Not_found-> let z = 1 + rlen(f n) in Hashtbl.add mem n z; z;;
let rec loop i maxlen bi =
if i>999999 then bi else
let t = rlen(of_int i) in if t > maxlen then loop (i+1) t i else loop (i+1) maxlen bi;;
print_int(loop 1 0 0);;
Вру, не то запускал. Вариант с массивом работает 1,2 секунды всего.
open Int64;;
let mem = Array.make 1000000 0 and i2 = of_int 2 and i3 = of_int 3 and mil=of_int 1000000;;
let f n = if (rem n i2) = zero then div n i2 else add (mul i3 n) one;;
let rec rlen n = if n = one then 1 else
if n >= mil then 1 + rlen(f n) else
let q = mem.(to_int n) in if q>0 then q else let z = 1 + rlen(f n) in mem.(to_int n)<-z; z;;
let rec loop i maxlen bi =
if i>999999 then bi else
let t = rlen(of_int i) in if t > maxlen then loop (i+1) t i else loop (i+1) maxlen bi;;
print_int(loop 1 0 0);;
Здравствуйте, Smal, Вы писали:
S>Можно ускорить? S>Заменить везде Integer на Int не получается (некоторые члены последовательности не влезают).
Решение влоб, работает порядка 30 секунд.
import Data.List (maximumBy)
import Data.Ord (comparing)
next :: Integer -> Integer
next n
| even n = n `quot` 2
| otherwise = 3 * n + 1
sequence_rule :: Integer -> [Integer]
sequence_rule 1 = [1]
sequence_rule n = n : (sequence_rule $ next n)
magic_number = 1000000
main :: IO ()
main = do
print $ maximumBy (comparing snd)
[(x, length $ sequence_rule x) | x <- [1..magic_number]]
Здравствуйте, xonixx, Вы писали:
X>Работает 18 сек. Решение почти в лоб. К стати, интересно, почему пролог так не популярен, хотя он явно проще хаскелла? )
Здравствуйте, xonixx, Вы писали:
X>Работает 18 сек. Решение почти в лоб. К стати, интересно, почему пролог так не популярен, хотя он явно проще хаскелла? )
Для меня область его применения уже.
Хаскель же здесь тормозит из-за своей лени — цепочки надо считать энергично, а не собирать их толпу. Можно примерно так (чистые списки, strict-свёртка):
import Data.List
next n | even n = n `div` 2
| otherwise = 3 * n + 1
chain = chain' 0
where
chain' ac 0 = ac
chain' ac 1 = ac
chain' ac n = chain' (ac + 1) (next n)
findMax n = strictMax $ map (\x -> (chain x, x)) [0..n]
where strictMax = foldl1' max
main = print $ snd $ findMax 1000000
Не сказал бы, что это решение смотрится хуже прологовского.