ก่อนอื่นผมขอแนะนำให้ดูที่Data.Vectorทางเลือกที่ดีกว่าData.Arrayในบางกรณี
Array
และVector
เหมาะอย่างยิ่งสำหรับกรณี memoization บางส่วนที่แสดงให้เห็นในคำตอบของฉัน "หาเส้นทางสูงสุด" อย่างไรก็ตามปัญหาบางอย่างนั้นไม่ใช่เรื่องง่ายที่จะแสดงในสไตล์การใช้งาน ตัวอย่างเช่นปัญหา 28ในProject Eulerเรียกหาการสรุปตัวเลขบนเส้นทแยงมุมของเกลียว แน่นอนว่ามันค่อนข้างง่ายที่จะหาสูตรสำหรับตัวเลขเหล่านี้ แต่การสร้างเกลียวนั้นท้าทายกว่า
Data.Array.STจัดเตรียมประเภทอาร์เรย์ที่ไม่แน่นอน อย่างไรก็ตามสถานการณ์ชนิดเป็นระเบียบ: จะใช้Marray ระดับเกินทุกหนึ่งเดียวของวิธีการยกเว้นrunSTArray ดังนั้นถ้าคุณวางแผนที่จะส่งคืนอาเรย์ที่ไม่เปลี่ยนรูปจากการกระทำของอาเรย์ที่ไม่แน่นอนคุณจะต้องเพิ่มลายเซ็นประเภทหนึ่งหรือมากกว่า
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
อย่างไรก็ตามวิธีการแก้ปัญหาของฉันไปที่ออยเลอร์ 28runSTArray
เปิดออกมาสวยอย่างดีและไม่จำเป็นต้องมีลายเซ็นของประเภทว่าเป็นเพราะผมใช้
ใช้ Data.Map เป็น "อาร์เรย์ที่ไม่แน่นอน"
หากคุณกำลังมองที่จะใช้ขั้นตอนวิธีการอาร์เรย์ไม่แน่นอนอีกทางเลือกหนึ่งคือการใช้Data.Map เมื่อคุณใช้อาเรย์คุณอยากได้ฟังก์ชั่นแบบนี้ซึ่งเปลี่ยนองค์ประกอบเดียวของอาเรย์:
writeArray :: Ix i => i -> e -> Array i e -> Array i e
น่าเสียดายที่สิ่งนี้จะต้องมีการคัดลอกอาร์เรย์ทั้งหมดยกเว้นว่าการใช้งานจะใช้กลยุทธ์การคัดลอกเมื่อเขียนเพื่อหลีกเลี่ยงมันเมื่อเป็นไปได้
ข่าวดีก็คือData.Map
มีฟังก์ชั่นเช่นนี้แทรก :
insert :: Ord k => k -> a -> Map k a -> Map k a
เนื่องจากMap
มีการนำมาใช้ภายในเป็นต้นไม้ไบนารีที่สมดุลinsert
ใช้เวลา O (บันทึก n) และพื้นที่และเก็บสำเนาต้นฉบับไว้เท่านั้น ดังนั้นMap
ไม่เพียงให้ "อาร์เรย์ที่ไม่แน่นอน" ที่ค่อนข้างมีประสิทธิภาพซึ่งเข้ากันได้กับรูปแบบการเขียนโปรแกรมที่ใช้งานได้ แต่ยังช่วยให้คุณ "ย้อนเวลากลับไป" ถ้าคุณโปรด
นี่เป็นวิธีแก้ปัญหาสำหรับออยเลอร์ 28 โดยใช้ 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
รูปแบบการปังป้องกันการล้นสแต็คที่เกิดจากรายการสะสม (เคอร์เซอร์หมายเลขและแผนที่) ไม่ได้ถูกใช้จนกว่าจะสิ้นสุดมาก สำหรับรหัสกอล์ฟส่วนใหญ่กรณีการป้อนข้อมูลไม่ควรใหญ่พอที่จะใช้ข้อกำหนดนี้