Cách tốt để xử lý các tác vụ yêu cầu mảng sử dụng Haskell là gì?


11

Thông thường một nhiệm vụ đòi hỏi mảng thực sự. Lấy ví dụ nhiệm vụ để thực hiện Befunge hoặc> <>. Tôi đã cố gắng sử dụng Arraymô-đun cho việc này, nhưng nó thực sự cồng kềnh, vì cảm giác như tôi đang mã hóa quá dài dòng. Ai đó có thể giúp tôi làm thế nào để giải quyết các nhiệm vụ golf-code như vậy ít dài dòng và nhiều chức năng hơn không?


AFAIK, trang web này chỉ dành cho mã golf, không dành cho các câu hỏi liên quan. Tôi đoán rằng cái này thuộc về Stackoverflow.
Jesse Millikan

@Jlie Millikan: Xin vui lòng xem câu hỏi này và đọc faq. Nó không nói rõ rằng bạn không được phép hỏi bất kỳ câu hỏi nào về cách chơi gôn. Loại câu hỏi này cũng là một phần lớn của câu hỏi "về chủ đề" trong giai đoạn định nghĩa của trang web này. Vui lòng suy nghĩ kỹ về downvote của bạn và xóa nó nếu bạn hiểu lý do tại sao tôi hỏi điều này ở đây.
FUZxxl

Hmm, tôi đoán là xấu.
Jesse Millikan

@Jlie Millikan: Errare humanum est.
FUZxxl

Câu hỏi thường gặp là không rõ ràng về nó, mặc dù.
Jesse Millikan

Câu trả lời:


5

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.

ArrayVectorlý 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.Mapcó 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, insertchỉ mất thời gian và không gian O (log n) và giữ nguyên bản gốc. Do đó, Mapkhô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.


9

Câu trả lời glib là: Đừng sử dụng mảng. Câu trả lời không dễ hiểu là: Cố gắng suy nghĩ lại vấn đề của bạn để nó không cần mảng.

Thông thường, một vấn đề với một số suy nghĩ có thể được thực hiện mà không có bất kỳ mảng như cấu trúc nào cả. Ví dụ: đây là câu trả lời của tôi cho Euler 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]

Những gì được thể hiện trong mã ở đây là mô hình của chuỗi số khi chúng phát triển xung quanh hình xoắn ốc hình chữ nhật. Không cần phải thực sự đại diện cho ma trận số.

Chìa khóa để suy nghĩ vượt ra ngoài mảng là suy nghĩ về vấn đề thực sự có nghĩa là gì, chứ không phải cách bạn có thể biểu thị nó dưới dạng byte trong RAM. Điều này chỉ đi kèm với thực hành (có lẽ là một lý do tôi mã hóa golf rất nhiều!)

Một ví dụ khác là cách tôi giải quyết việc tìmđường dẫn tối đa - golf. Ở đó, phương pháp truyền các giải pháp từng phần dưới dạng sóng qua ma trận, từng hàng, được thể hiện trực tiếp bằng thao tác gấp. Hãy nhớ rằng: Trên hầu hết các CPU, bạn không thể xử lý toàn bộ mảng cùng một lúc: Chương trình sẽ phải hoạt động theo thời gian. Nó có thể không cần toàn bộ mảng cùng một lúc bất cứ lúc nào.

Tất nhiên, một số vấn đề được nêu theo cách mà chúng vốn dựa trên mảng. Các ngôn ngữ như> <>, Befunge hoặc Brainfuck có các mảng trong tim. Tuy nhiên, ngay cả ở đó, mảng thường có thể được phân phối. Ví dụ, xem giải pháp của tôi để giải thích Brainfuck , cốt lõi thực sự của ngữ nghĩa của nó là một dây kéo . Để bắt đầu suy nghĩ theo cách này, hãy tập trung vào các mẫu truy cập và cấu trúc gần hơn với ý nghĩa của vấn đề. Thường thì điều này không cần phải bị ép buộc vào một mảng có thể thay đổi.

Khi tất cả các cách khác đều thất bại và bạn cần phải sử dụng một mảng - mẹo của @ Joey là một khởi đầu tốt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.