Her şeyden önce, ben bakarak tavsiye Data.Vector , daha hoş bir alternatif Data.Array bazı durumlarda.
Array
ve "Maksimum yolları bulma"Vector
yanıtımda gösterildiği gibi bazı not dosyaları için idealdir . Bununla birlikte, bazı problemlerin işlevsel bir tarzda ifade edilmesi kolay değildir. Örneğin, Problem 28 yılında Proje Euler spiral köşegenlerinin numaralar toplayarak gerektirir. Elbette, bu sayılar için bir formül bulmak oldukça kolay olmalı, ancak spiralin inşa edilmesi daha zordur.
Data.Array.ST değiştirilebilir bir dizi türü sağlar. Ancak, tip durum bir felaket: Bir kullanır sınıf Marray süre dışında yöntemlerin her birini aşırı runSTArray . Dolayısıyla, değiştirilebilir bir dizi işleminden değiştirilemez bir dizi döndürmeyi planlamıyorsanız, bir veya daha fazla tür imza eklemeniz gerekir:
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
Yine de, Euler 28'e çözümüm oldukça güzel çıktı ve kullandığım için bu tip imza gerektirmedi runSTArray
.
Data.Map'i "değiştirilebilir bir dizi" olarak kullanma
Değişken bir dizi algoritması uygulamak istiyorsanız, başka bir seçenek Data.Map kullanmaktır . Bir dizi kullandığınızda, bir dizinin tek bir öğesini değiştiren böyle bir işleve sahip olmanız dileğiyle:
writeArray :: Ix i => i -> e -> Array i e -> Array i e
Maalesef, uygulama mümkün olduğunda bunu önlemek için yazma üzerine kopyalama stratejisi kullanmadığı sürece, tüm dizinin kopyalanmasını gerektirir.
İyi haber şu, Data.Map
böyle bir işlevi var, ekle :
insert :: Ord k => k -> a -> Map k a -> Map k a
Çünkü Map
dengeli bir ikili ağacı olarak dahili olarak uygulanan, insert
sadece orijinal kopyası O (log n) zaman ve mekân ve korur sürer. Bu nedenle, Map
sadece fonksiyonel programlama modeliyle uyumlu, biraz verimli bir "değişebilir dizi" sağlamakla kalmaz, aynı zamanda lütfen "zamanda geriye gitmenizi" sağlar.
İşte Data.Map kullanarak Euler 28 için bir çözüm:
{-# 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
Patlama düzenleri, akümülatör öğelerinin (imleç, sayı ve Harita) sonuna kadar kullanılmamasından kaynaklanan bir yığın taşmasını önler. Çoğu kod golfü için, giriş durumları bu hükme ihtiyaç duyacak kadar büyük olmamalıdır.