เป็นวิธีที่ดีในการจัดการกับงานที่ต้องใช้อาร์เรย์โดยใช้ Haskell อะไร


11

บ่อยครั้งที่งานต้องใช้อาร์เรย์จริง รับอินสแตนซ์งานเพื่อใช้ Befunge หรือ> <> ฉันพยายามใช้Arrayโมดูลสำหรับสิ่งนี้ แต่มันยุ่งยากจริงๆเพราะมันรู้สึกว่าฉันเขียนโค้ดมากเกินไป ใครสามารถช่วยฉันได้บ้างในการแก้ปัญหาโค้ดกอล์ฟดังกล่าวให้น้อยลงและทำงานได้มากขึ้น?


AFAIK เว็บไซต์นี้มีไว้สำหรับโค้ดกอล์ฟเท่านั้นไม่ใช่สำหรับคำถามที่เกี่ยวข้อง ฉันเดาว่านี่เป็นของ Stackoverflow
Jesse Millikan

@Jesse Millikan: โปรดดูคำถามนี้และอ่านคำถามที่พบบ่อย มันไม่ได้ระบุว่าคุณไม่ได้รับอนุญาตให้ถามคำถามเกี่ยวกับการเล่นกอล์ฟ คำถามประเภทนี้เป็นส่วนหนึ่งของคำถาม "ตามหัวข้อ" ในขั้นตอนการกำหนดของเว็บไซต์นี้ โปรดคิดสองเท่าของ downvote ของคุณและลบมันถ้าคุณเข้าใจว่าทำไมฉันถึงถามที่นี่
FUZxxl

อืมข้าคิดว่าไม่ดี
Jesse Millikan

@Jesse Millikan: Errare humanum est.
FUZxxl

แม้ว่าคำถามที่พบบ่อยยังไม่ชัดเจนมากนัก
Jesse Millikan

คำตอบ:


5

ก่อนอื่นผมขอแนะนำให้ดูที่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

รูปแบบการปังป้องกันการล้นสแต็คที่เกิดจากรายการสะสม (เคอร์เซอร์หมายเลขและแผนที่) ไม่ได้ถูกใช้จนกว่าจะสิ้นสุดมาก สำหรับรหัสกอล์ฟส่วนใหญ่กรณีการป้อนข้อมูลไม่ควรใหญ่พอที่จะใช้ข้อกำหนดนี้


9

คำตอบที่กะล่อนคือ: อย่าใช้อาร์เรย์ คำตอบที่ไม่ดังกะล่อนคือ: พยายามคิดใหม่ปัญหาของคุณเพื่อที่ไม่จำเป็นต้องใช้อาร์เรย์

บ่อยครั้งที่ปัญหาที่เกิดขึ้นกับความคิดบางอย่างสามารถทำได้โดยไม่ต้องมีโครงสร้างแบบอาร์เรย์ใด ๆ เลย เช่นนี่คือคำตอบของฉันที่ออยเลอร์ 28:

-- | What is the sum of both diagonals in a 1001 by 1001 spiral?
euler28 = spiralDiagonalSum 1001

spiralDiagonalSum n
    | n < 0 || even n = error "spiralDiagonalSum needs a positive, odd number"
    | otherwise = sum $ scanl (+) 1 $ concatMap (replicate 4) [2,4..n]

สิ่งที่ได้รับในรหัสที่นี่คือรูปแบบของลำดับของตัวเลขเมื่อพวกมันเติบโตรอบเกลียวก้นหอย ไม่จำเป็นต้องเป็นตัวแทนของเมทริกซ์ของตัวเลขจริงๆ

กุญแจสำคัญในการคิดนอกเหนือจากอาร์เรย์คือการคิดเกี่ยวกับปัญหาที่เกิดขึ้นจริงหมายถึงไม่ใช่วิธีที่คุณอาจแสดงว่าเป็นไบต์ใน RAM นี่มาพร้อมกับการฝึกฝน (อาจเป็นเหตุผลที่ฉันเขียนโค้ดกอล์ฟมาก!)

อีกตัวอย่างหนึ่งคือวิธีที่ฉันแก้ไขการหาเส้นทางสูงสุดของการเขียนโค้ดกอล์ฟ ที่นั่นวิธีการผ่านการแก้ปัญหาบางส่วนเป็นคลื่นผ่านเมทริกซ์ทีละแถวจะแสดงโดยตรงโดยการดำเนินการพับ ข้อควรจำ: สำหรับซีพียูส่วนใหญ่คุณไม่สามารถจัดการกับอาเรย์ทั้งหมดได้ในครั้งเดียว: โปรแกรมจะต้องทำงานผ่านช่วงเวลาหนึ่ง มันอาจไม่จำเป็นต้องมีอาร์เรย์ทั้งหมดพร้อมกันในเวลาใดก็ได้

แน่นอนปัญหาบางอย่างระบุไว้ในลักษณะที่พวกเขาเรียงตามลำดับโดยกำเนิด ภาษาเช่น> <>, Befunge หรือ Brainfuck มีอาร์เรย์อยู่ในใจ อย่างไรก็ตามแม้จะมีอาร์เรย์สามารถจ่ายด้วย ตัวอย่างเช่นดูวิธีการแก้ปัญหาของฉันที่จะตีความ brainfuckแกนที่แท้จริงของความหมายของการเป็นซิป ในการเริ่มคิดด้วยวิธีนี้ให้เน้นที่รูปแบบการเข้าถึงและโครงสร้างที่ใกล้เคียงกับความหมายของปัญหามากขึ้น บ่อยครั้งที่สิ่งนี้ไม่จำเป็นต้องถูกบังคับให้อยู่ในอาร์เรย์ที่ไม่แน่นอน

เมื่อทุกอย่างล้มเหลวและคุณจำเป็นต้องใช้อาร์เรย์ - เคล็ดลับของ @ Joey เป็นการเริ่มต้นที่ดี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.