Trước hết, tôi khuyên bạn nên xem Data.Vector , một sự thay thế đẹp hơn cho Data.Array trong một số trường hợp.
Array
và Vector
lý tưởng cho một số trường hợp ghi nhớ, như thể hiện trong câu trả lời của tôi về "Tìm đường dẫn tối đa" . Tuy nhiên, một số vấn đề đơn giản là không dễ thể hiện theo kiểu chức năng. Ví dụ, Bài toán 28 trong Project Euler gọi để tính tổng các số trên các đường chéo của hình xoắn ốc. Chắc chắn, sẽ khá dễ dàng để tìm ra một công thức cho những con số này, nhưng việc xây dựng đường xoắn ốc khó khăn hơn.
Data.Array.ST cung cấp một kiểu mảng có thể thay đổi. Tuy nhiên, tình huống kiểu là một mớ hỗn độn: nó sử dụng một lớp MArray để quá tải mọi phương thức của nó ngoại trừ runSTArray . Vì vậy, trừ khi bạn có kế hoạch trả về một mảng bất biến từ một hành động mảng có thể thay đổi, bạn sẽ phải thêm một hoặc nhiều chữ ký loại:
import Control.Monad.ST
import Data.Array.ST
foo :: Int -> [Int]
foo n = runST $ do
a <- newArray (1,n) 123 :: ST s (STArray s Int Int) -- this type signature is required
sequence [readArray a i | i <- [1..n]]
main = print $ foo 5
Tuy nhiên, giải pháp của tôi cho Euler 28 hóa ra khá độc đáo và không yêu cầu chữ ký loại đó vì tôi đã sử dụng runSTArray
.
Sử dụng Data.Map như một "mảng có thể thay đổi"
Nếu bạn đang tìm cách thực hiện thuật toán mảng có thể thay đổi, một tùy chọn khác là sử dụng Data.Map . Khi bạn sử dụng một mảng, bạn muốn có một hàm như thế này, nó thay đổi một thành phần duy nhất của một mảng:
writeArray :: Ix i => i -> e -> Array i e -> Array i e
Thật không may, điều này sẽ yêu cầu sao chép toàn bộ mảng, trừ khi việc triển khai sử dụng chiến lược sao chép trên ghi để tránh điều đó khi có thể.
Tin tốt là, Data.Map
có một chức năng như thế này, chèn :
insert :: Ord k => k -> a -> Map k a -> Map k a
Bởi vì Map
được triển khai bên trong như một cây nhị phân cân bằng, insert
chỉ mất thời gian và không gian O (log n) và giữ nguyên bản gốc. Do đó, Map
không chỉ cung cấp một "mảng có thể thay đổi" có hiệu quả tương thích với mô hình lập trình chức năng, mà thậm chí còn cho phép bạn "quay ngược thời gian" nếu bạn vui lòng.
Đây là một giải pháp cho Euler 28 bằng Data.Map:
{-# LANGUAGE BangPatterns #-}
import Data.Map hiding (map)
import Data.List (intercalate, foldl')
data Spiral = Spiral Int (Map (Int,Int) Int)
build :: Int -> [(Int,Int)] -> Map (Int,Int) Int
build size = snd . foldl' move ((start,start,1), empty) where
start = (size-1) `div` 2
move ((!x,!y,!n), !m) (dx,dy) = ((x+dx,y+dy,n+1), insert (x,y) n m)
spiral :: Int -> Spiral
spiral size
| size < 1 = error "spiral: size < 1"
| otherwise = Spiral size (build size moves) where
right = (1,0)
down = (0,1)
left = (-1,0)
up = (0,-1)
over n = replicate n up ++ replicate (n+1) right
under n = replicate n down ++ replicate (n+1) left
moves = concat $ take size $ zipWith ($) (cycle [over, under]) [0..]
spiralSize :: Spiral -> Int
spiralSize (Spiral s m) = s
printSpiral :: Spiral -> IO ()
printSpiral (Spiral s m) = do
let items = [[m ! (i,j) | j <- [0..s-1]] | i <- [0..s-1]]
mapM_ (putStrLn . intercalate "\t" . map show) items
sumDiagonals :: Spiral -> Int
sumDiagonals (Spiral s m) =
let total = sum [m ! (i,i) + m ! (s-i-1, i) | i <- [0..s-1]]
in total-1 -- subtract 1 to undo counting the middle twice
main = print $ sumDiagonals $ spiral 1001
Các mẫu bang ngăn chặn tràn ngăn xếp gây ra bởi các mục tích lũy (con trỏ, số và Bản đồ) không được sử dụng cho đến khi kết thúc. Đối với hầu hết các golf mã, các trường hợp đầu vào không đủ lớn để cần điều khoản này.