Pertama-tama, saya sarankan melihat Data.Vector , alternatif yang lebih bagus untuk Data.Array dalam beberapa kasus.
Array
dan Vector
sangat ideal untuk beberapa kasus memoisasi, seperti yang ditunjukkan dalam jawaban saya untuk "Menemukan jalur maksimum" . Namun, beberapa masalah tidak mudah diungkapkan dalam gaya fungsional. Misalnya, Masalah 28 dalam Project Euler menyerukan penjumlahan angka pada diagonal spiral. Tentu, cukup mudah untuk menemukan formula untuk angka-angka ini, tetapi membuat spiral lebih menantang.
Data.Array.ST menyediakan tipe array yang bisa berubah. Namun, tipe situasi berantakan: ia menggunakan kelas MArray untuk membebani setiap metode kecuali untuk runSTArray . Jadi, kecuali jika Anda berencana mengembalikan array yang tidak dapat diubah dari aksi array yang bisa berubah, Anda harus menambahkan satu atau lebih jenis tanda tangan:
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
Namun demikian, solusi saya untuk Euler 28 ternyata cukup bagus, dan tidak memerlukan tanda tangan jenis itu karena saya gunakan runSTArray
.
Menggunakan Data.Map sebagai "array yang bisa berubah-ubah"
Jika Anda ingin menerapkan algoritme array yang dapat diubah, opsi lain adalah menggunakan Data.Map . Saat Anda menggunakan array, Anda berharap memiliki fungsi seperti ini, yang mengubah satu elemen array:
writeArray :: Ix i => i -> e -> Array i e -> Array i e
Sayangnya, ini akan memerlukan menyalin seluruh array, kecuali jika implementasi menggunakan strategi copy-on-write untuk menghindarinya jika memungkinkan.
Berita baiknya adalah, Data.Map
memiliki fungsi seperti ini, masukkan :
insert :: Ord k => k -> a -> Map k a -> Map k a
Karena Map
diimplementasikan secara internal sebagai pohon biner seimbang, insert
hanya membutuhkan O (log n) waktu dan ruang, dan mempertahankan salinan asli. Oleh karena itu, Map
tidak hanya menyediakan "array yang dapat diubah" yang agak efisien yang kompatibel dengan model pemrograman fungsional, tetapi bahkan memungkinkan Anda "kembali ke masa lalu" jika Anda mau.
Berikut adalah solusi untuk Euler 28 menggunakan 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
Pola bang mencegah stack overflow yang disebabkan oleh item akumulator (kursor, angka, dan Peta) tidak digunakan sampai akhir. Untuk sebagian besar kode golf, kotak masukan tidak boleh cukup besar untuk memerlukan ketentuan ini.