HaskellでArray操作

Haskell使ってはじめてArray操作したのでメモ。
STモナドをつかったのもはじめてだけども。
使い方があってるのかは保証しない。

arrayパッケージのhackageのページ
http://hackage.haskell.org/package/array-0.4.0.0

  • インターフェイスとしてはImmutablな IArrayクラス とMutableな MArrayクラスがある
  • IArrayとしては Array と アンボックスされてる UArray がある
  • MArrayとして IOArray とか STArrayとかある。それそれアンボックスされてるVersionがある。(IOUArray,STUArray)
  • MArrayとIArrayを相互変換には freeze、thaw とかがある。
  • thawとかfreezeしたときにコピーが発生してるんでないかと思う。
    • freezeしたときはコピーするんだろか。
  • アクセスする際の添字としてはIxクラスであればつかえて、初期化する際に最小値と最大値を指定する。(多次元配列とか簡単につくれる)
    • array関数とかに *1 とか渡してやれば 12x12の配列ができる。

折角なので書いたコードも添付しとく

  • 書いたコードはプログラミングコンテストチャレンジブックのLake Coutingを元にhaskellに書き換えた。
  • 数えた数を保存するのにStateモナドつかってたらうまくいかなかった。どうせSTの中なので新しいSTRefつくった。どうするのがよいのかよくわかってない。
  • solveの中で破壊的な操作があるけれでも、その変更は外に出ないの参照透明性は維持できる。と信じている。
  • whenが連続するのをなんとかする方法をしりたいけど、今回は保留。
  • STモナドはIOモナドみたいに副作用がおこせて、STをはずすこともできるが、その変わり別の制限がある程度に認識している(つまりよくわかってない)
  • 入力はIOUArrayで構築して UArray に変換しておいて、問題をとく場合はSTUArrayにして処理したという感じ。

問題の内容は

大きさがN×Mの庭があります。そこに雨が振り、水溜りができました。
水溜りは8近傍で隣接している場合につながっているとみなします。
全部でいくつの水溜りがあるでしょうか?
(8近傍とは、次のWに対する*の部分を指します)

***
*W*
***

制約 N,M <= 100

入力データは以下のように想定

  • 1行目がN
  • 2行目がM
  • それ移行が庭
10
12
W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.
module Main where

import Control.Monad (forM_,when)
import Control.Monad.ST (ST,runST)
import Data.Array.ST (STUArray)
import Data.Array.IO (freeze,thaw,writeArray,getBounds,readArray,newArray,IOUArray)
import Data.Array.Unboxed (UArray,bounds,array)
import Data.Ix (inRange,range)
import Data.STRef (newSTRef,readSTRef,writeSTRef)

type Point = (Int,Int)
type Field = UArray Point Char
type STField s = STUArray s Point Char
type IOField = IOUArray Point Char

main = do
  n <- getInt
  m <- getInt
  let r = ((1,1),(n,m))
  field <- newArray r ' ' :: IO IOField
  forM_ [1..n] $ \x -> do
    forM_ [1..m] $ \y -> do
      c <- getChar
      writeArray field (x,y) c
    getChar
  f <- freeze field
  print . solve $ f
  where
    getInt :: IO Int
    getInt = fmap read $ getLine

dfs :: STField s -> Point -> ST s ()
dfs f p@(x,y) = do
  writeArray f p '.'
  b <- getBounds f
  forM_ r $ \p1 -> do
    when (p /= p1 && inRange b p1) $ do
      c <- readArray f p1
      when (isWater c) $ do
        dfs f p1
  where
    r = range ((x-1,y-1),(x+1,y+1))

solve :: Field -> Int
solve field = runST $ do
      f <- thaw field :: ST s (STField s)
      solve' f
        where
          solve' :: STField s -> ST s Int
          solve' f = do
            ref <- newSTRef 0
            forM_ r $ \p -> do
              c <- readArray f p
              when (isWater c) $ do
                dfs f p
                x <- readSTRef ref
                writeSTRef ref (x+1)
            readSTRef ref
          r = range $ bounds $ field

isWater :: Char -> Bool
isWater = (=='W')

-- サンプルデータ
ar :: Field
ar = array ((1,1),(2,2)) [((1,1), 'W'),((2,2), 'W')]

*1:1,1),(12,12