Haskellを使用して配列を必要とするタスクを処理する良い方法は何ですか?


11

多くの場合、タスクには実際の配列が必要です。たとえば、Befungeまたは> <>を実装するタスクを取ります。私はArrayこのためにモジュールを使用しようとしましたが、非常に冗長でコーディングしているように感じるので、本当に面倒です。そのようなコードゴルフのタスクをより冗長で機能的にする方法を誰かが私に助けてもらえますか?


私の知る限り、このサイトはコードゴルフ自体専用であり、関連する質問ではありません。これはStackoverflowに属していると思います。
ジェシーミリカン

@Jesse Millikan:この質問も参照して、よくある質問を読んでください。それはあなたがゴルフの仕方について質問することを許されていないことを述べていません。この種の質問は、このサイトの定義段階での「トピックに関する」質問の大きな部分でもありました。ここで私がこれを求めている理由を理解しているなら、あなたのダウン票を二重に考えて削除してください。
FUZxxl

うーん、私は悪いと思う。
ジェシーミリカン

@ジェシーミリカン:エラーヒューマナムエスト
FUZxxl

ただし、FAQについてはあまり明確ではありません。
ジェシーミリカン

回答:


5

まず第一に、場合によってはData.Arrayのより良い代替手段であるData.Vectorを調べることをお勧めします。

ArrayVectorに示されているように、いくつかのメモ化の例のための理想的な「最大のパスの検索」に私の答え。ただし、いくつかの問題は機能的なスタイルで表現するのは簡単ではありません。たとえば、プロジェクトオイラーの問題28では、スパイラルの対角線上の数値を合計する必要があります。確かに、これらの数値の式を見つけるのは非常に簡単なはずですが、スパイラルの構築はより困難です。

Data.Array.STは可変配列タイプを提供します。ただし、型の状況は混乱です。runSTArrayを除くメソッドのすべてをオーバーロードするためにクラスMArrayを使用します。したがって、可変配列アクションから不変配列を返す予定がない限り、1つ以上の型シグネチャを追加する必要があります。

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

それでも、Euler 28に対する私の解決策はかなりうまくいき、私はを使用しrunSTArrayたため、そのタイプシグネチャを必要としませんでした。

Data.Mapを「可変配列」として使用する

可変配列アルゴリズムの実装を検討している場合、別のオプションはData.Mapを使用することです。配列を使用する場合、配列の単一の要素を変更する次のような関数があればいいのに、と思っています。

writeArray :: Ix i => i -> e -> Array i e -> Array i e

残念ながら、実装でコピーオンライト戦略を使用して可能な限り回避しない限り、アレイ全体をコピーする必要があります。

良いニュースは、次のData.Mapような機能があり、insertです:

insert :: Ord k => k -> a -> Map k a -> Map k a

Mapバランスの取れたバイナリツリーとして内部的に実装されているため、insertO(log n)の時間とスペースのみを取り、元のコピーを保持します。したがって、Map関数型プログラミングモデルと互換性のあるある程度効率的な「可変配列」を提供するだけでなく、必要に応じて「過去に戻る」こともできます。

Data.Mapを使用したオイラー28のソリューションを次に示します。

{-# 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

glibの答えは次のとおりです。配列を使用しないでください。not-so-glibの答えは、配列を必要としないように問題を再考してみてください。

多くの場合、構造のような配列がまったくなくても、何らかの問題が発生する可能性があります。たとえば、オイラー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のバイトとして表現する方法ではなく、問題が実際に何を意味するのかを考えることです。これには練習が必要です(おそらく、私がコードゴルフをたくさんする理由でしょう!)

もう1つの例は、最大パスのコードゴルフの発見をどのように解決したかです。そこでは、行列を行ごとに部分解を波として渡す方法は、フォールド演算によって直接表現されます。要確認:ほとんどのCPUでは、アレイ全体を一度に処理することはできません。プログラムは、時間の経過とともにアレイ全体を操作する必要があります。配列全体を一度に必要としない場合があります。

もちろん、本質的に配列ベースであるような方法でいくつかの問題が述べられています。> <>、Befunge、Brainfuckなどの言語には、配列が中心にあります。ただし、そこであっても、アレイは多くの場合不要です。たとえば、Brainfuck解釈するための私のソリューションを参照してください、そのセマンティクスの本当の核心はzipperです。このように考え始めるには、アクセスのパターンと、問題の意味により近い構造に焦点を合わせます。多くの場合、これを可変配列に強制する必要はありません。

他のすべてが失敗し、配列を使用する必要がある場合-@Joeyのヒントは良い出発点です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.