#haskell #memoization
#haskell #запоминание
Вопрос:
import Data.List (intercalate)
import Control.Concurrent (threadDelay)
import Data.Maybe (fromJust)
import System.IO
-- I love how amazingly concise Haskell code can be. This same program in C, C or Java
-- would be at least twice as long.
pascal :: Int -> Int -> Int
pascal row col | col >= 0 amp;amp; col <= row =
if row == 0 || col == 0 || row == col
then 1
else pascal (row - 1) (col - 1) pascal (row - 1) col
pascal _ _ = 0
pascalFast' :: [((Int, Int), Int)] -> Int -> Int -> Int
pascalFast' dict row col | col > row = 0
pascalFast' dict row col | row == 0 || col == 0 || row == col = 1
pascalFast' dict row col =
let value1 = lookup (row - 1, col - 1) dict
value2 = lookup (row - 1, col) dict
in if not(value1 == Nothing || value2 == Nothing)
then (fromJust value1) (fromJust value2)
else let dict' = ((row - 1, col), pascalFast' dict (row - 1) col) : dict
dict'' = ((row - 1, col - 1), pascalFast' dict' (row - 1) (col - 1)) : dict'
in (pascalFast' dict'' (row - 1) col) (pascalFast' dict'' (row - 1) (col - 1))
pascalFast = pascalFast' []
pascalsTriangle :: Int -> [[Int]]
pascalsTriangle rows =
[[pascal row col | col <- [0..row]] | row <- [0..rows]]
main :: IO ()
main = do
putStrLn ""
putStr "Starting at row #0, how many rows of Pascal's Triangle do you want to print out? "
hFlush stdout
numRows <- (s -> read s :: Int) <$> getLine
let triangle = pascalsTriangle numRows
longestStringLength = (length . show) $ foldl1 max $ flatten triangle
triangleOfStrings = map (intercalate ", ") $ map (map (pad longestStringLength)) triangle
lengthOfLastDiv2 = div ((length . last) triangleOfStrings) 2
putStrLn ""
mapM_ (s -> let spaces = [' ' | x <- [1 .. lengthOfLastDiv2 - div (length s) 2]]
in (putStrLn $ spaces s) >> threadDelay 200000) triangleOfStrings
putStrLn ""
flatten :: [[a]] -> [a]
flatten xs =
[xss | ys <- xs, xss <- ys]
pad :: Int -> Int -> String
pad i k =
[' ' | _ <- [1..n]] m
where m = show k
n = i - length m
Хоть убей, я не понимаю, почему pascalFast не РАБОТАЕТ БЫСТРО!!! Он проверяет тип и математически он корректен, но моя функция «pascalFast» работает так же медленно, как и моя функция «pascal». Есть идеи? И нет, это не домашнее задание. Это то, что я просто хочу попробовать для себя. Спасибо за отзыв.
Лучший, Дуглас Левит
Ответ №1:
На самом деле вы main
вообще не вызываете pascalFast
, поэтому мне непонятно, что именно вы делали, что заставило вас сделать вывод, что это медленно — с некоторым усилием я могу сказать, что это медленно, глядя на это, но некоторые доказательства в вопросе были бы хороши.
Что касается того, почему, у меня возникают две проблемы. Мне кажется, что, поскольку вы передаете словарь «вверх» в базовый регистр, но никогда не передаете его вниз или вбок, вы только кэшируете результаты, на которые вы никогда больше не посмотрите. Попробуйте выполнить оценку pascalFast [] 2 1
вручную, на бумаге, и посмотрите, попадете ли вы когда-нибудь в кэш.
Во-вторых, даже если вы правильно кэшировали, использование lookup
займет время, линейное по размеру списка, поэтому время выполнения по крайней мере квадратично по количеству сгенерированных записей: для каждого генерируемого элемента вы просматриваете все остальные элементы хотя бы один раз. Для эффективного кэширования вам нужна реальная структура данных, например, из Data.Map
.
Но отдельно от вопроса о том, как эффективно запоминать, часто лучше вообще не запоминать, начиная с базовых случаев и наращивая, а не переходя к конечному результату. Что-то вроде этого довольно классическое для треугольника Паскаля:
triangle :: [[Int]]
triangle = iterate nextRow [1]
where nextRow xs = 1 : zipWith ( ) xs (tail xs) [1]
main :: IO ()
main = print $ take 5 triangle
Комментарии:
1. О, я знаю, что я не вызываю pascalFast в main. Я протестировал ее с помощью GHCi, и именно поэтому я знаю, что производительность во время выполнения ничуть не лучше, чем у незамеченной функции «pascal». Но спасибо за помощь. Вы дали мне хорошую пищу для размышлений.