Haskell:IOArray在读写时速度太慢

时间:2018-12-26 13:38:35

标签: haskell multidimensional-array

我在Haskell中编写了一个小代码。您能否提出改善性能的解决方案?

  • 输入:HeightWidth的网格。点的位置显示为'#'
4 4
##..
#...
...#
....
  • 输出:显示到每个最近点的“曼哈顿距离”的数字网格。
0 0 1 2
0 1 2 1
1 2 1 0
2 3 2 1

要在网格中设置大量数字,我使用了二维IOArray来减少修改成本。处理500x500的网格需要花费一分钟多的时间,而我在Python中编写的替代代码仅需5秒。

不到16秒将不胜感激!

import Data.List
import Data.Array.IO
import Control.Monad

type Point = (Int, Int)
type Grid = IOArray Point Int

main :: IO ()
main = do
  [height, width] <- (map read . take 2 . words) <$> getLine
  points <- zeroPoints width <$> getContents
  grid <- newArray ((1,1), (height, width)) (-1)
  wfs grid points 0
  printArray grid

zeroPoints :: Int -> String -> [Point]
zeroPoints width str = [(i `div` width + 1, i `mod` width + 1) | (i, c) <- chars, c == '#']
  where chars = zip [0..] (concat $ words str)

wfs :: Grid -> [Point] -> Int -> IO [Point]
wfs grid [] _ = return []
wfs grid points distance = do
  mapM_ (\p -> writeArray grid p distance) points
  (_, (height, width)) <- getBounds grid
  newPoints <- neighbors grid height width points
  wfs grid newPoints (distance + 1)

neighbors :: Grid -> Int -> Int -> [Point] -> IO [Point]
neighbors grid height width points =
  filterM (isEmpty grid) $ nub $ filter inArea $ [up, down, left, right] <*> points
  where
    up    (x, y) = (x - 1, y)
    down  (x, y) = (x + 1, y)
    left  (x, y) = (x, y - 1)
    right (x, y) = (x, y + 1)
    inArea (x, y) = x > 0 && x <= height && y > 0 && y <= width
    isEmpty grid p = (< 0) <$> readArray grid p

printArray :: Grid -> IO ()
printArray grid = do
  (_, (height, width)) <- getBounds grid
  forM_ [(h, w) | h <- [1..height], w <- [1..width]] $ \(h, w) -> do
    i <- readArray grid (h, w)
    if w == width then putStrLn $ show i else putStr $ show i ++ " "

1 个答案:

答案 0 :(得分:4)

我没有进行任何分析,但这是我跳出来的那一行:

  filterM (isEmpty grid) $ nub $ filter inArea $ [up, down, left, right] <*> points

特别地,nub是O(n ^ 2)运算。您可能首先尝试使用ordNub中的Data.Containers.ListUtils

我看到的另一件事是您正在使用装箱的数组。盒装阵列有间接成本,也有额外的GC成本。他们可以很容易地使使用它们的代码比应有的速度慢几倍。通常,仅在需要盒装数组的多态或懒惰时才应使用盒装数组。我在这里没有立即看到任何东西。

最后,进行微优化:如果您需要商和余数,请使用divModquotRem仅用一个硬件部门来完成这项工作。这不是您的代码运行缓慢的原因,但这是一个好习惯。如果数字为正,请使用quotRem来提高速度。如果不是,请确保阅读文档以弄清楚应使用哪个。