

漠然とした質問で申しわけないのですが  
お力を貸していただけるとうれしいです

『Ruby初心者必見！？「ビンゴカード作成問題」のリファクタリング風景をお見せします #codeiq』  
<http://blog.jnito.com/entry/2015/03/06/090106>  
にあった『ビンゴカード作成問題』をHaskellで挑戦してみました

＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝  
■ルール■  
①各列の値は以下の条件を満たすこと  
B：1～15のどれか  
I：16～30のどれか  
N：31～45のどれか  
G：46～60のどれか  
O：61～75のどれか  
②毎回異なるカードを生成すること  
③どの数値も重複しないこと  
④各列はパイプ（|）で区切ること  
⑤数字や"BINGO"の文字は右寄せで出力すること  
⑥真ん中（FREEになる場所）はスペースを出力すること  
＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝＝

[code]

    {-# OPTOINS -Wall -Werror #-}
    {-# LANGUAGE OverloadedStrings, ViewPatterns #-}
    
    import qualified Data.List as L
    import qualified Data.Text as T
    import qualified System.Random as R
    
    type RangeUnit = (Int, Int)
    
    header = "BINGO"
    bingoSize = T.length header
    numrange = 15
    rangeUnit = take bingoSize $ zip [1,numrange+1..] [numrange, numrange*2..]
    
    body = do
        nums <- mapM (createRondomRange bingoSize) rangeUnit
        return $ formatCell 2 2 nums
    
    viewBingo = do
        b <- body
        return $ map formatline $ map appendSeparatedWord $ headerList : b
        where
            headerList = map (\x -> justifyRightSpace $ T.pack [x]) $ T.unpack header
            appendSeparatedWord = L.intersperse $ T.pack " | "
            formatline = foldl T.append T.empty
    
    createRondomRange :: Int -> RangeUnit -> IO [Int]
    createRondomRange range (start, end) = do
        gen <- R.newStdGen
        return $ take range . L.nub $ R.randomRs (start, end) gen
    
    formatCell :: Int -> Int -> [[Int]] -> [[T.Text]]
    formatCell rowIndex colIndex vals = 
        L.transpose $ mmap (justifyRightSpace . centerReplaced) vals
        where
            centerReplaced x = if centerVal == x then " " else T.pack $ show x
            centerVal = vals !! rowIndex !! colIndex
    
    justifyRightSpace = T.justifyRight 2 ' ' 
    
    mmap f = map (map f)
    
    main = do
        b <- viewBingo
        mapM_ print b
    
[/code]

■質問内容  
・Haskellらしく書くにはこうしたほうがいい  
・この便利関数を使えばこんな回りくどいことしなくていい  
・そもそも書く上での考え方が悪(ry

などなど、ソースを改善するお力を貸していただきたいです

また①の数値の範囲について  
import qualified Data.List.Split as S  
ranges = S.splitEvery 15 [1..75]  
で作成したrangesの格リストの値をシャッフルすることで  
作りたかったのですが方法がわかりませんでした。  
リストの要素をランダムに入れ替える関数はあるのでしょうか


CC BY-SA 3.0
2015-04-05T10:34:30.673
8789
2015-04-05T23:21:18.077
7793
question
haskell
『ビンゴカード作成問題』で書いたソースの改善方法について


リストの要素をランダムに入れ替えるには、[random-
shuffleパッケージ](https://hackage.haskell.org/package/random-shuffle)を使うと楽です。

ビンゴそのものを作成する過程と、それを表示する過程が混じってしまっているために、全体的に読みづらく感じます。特に、各セルをフォーマットするときに、毎回真ん中かどうかをチェックしている辺りなどです。

若干手を抜いているところがありますが、以下のような感じではどうでしょうか。

ビンゴ自体は`[[Maybe
Int]]`で表し、`bingo`関数で新しいビンゴを生成します。真ん中の部分は値がないということで`Nothing`を置きます。インデックスでリストの要素を置き換える部分は、[lensパッケージ](http://hackage.haskell.org/package/lens)を使っています。

作成したビンゴは、`showBingo`関数で文字列に変換します。

[code]

    {-# LANGUAGE FlexibleContexts, TypeFamilies #-}
    
    import Control.Lens ((.~), (&), ix)
    import Control.Monad.Random.Class (MonadRandom)
    import Data.List (intercalate, transpose)
    import Data.List.Split (chunksOf)
    import System.Random.Shuffle (shuffleM)
    
    type Bingo = [[Maybe Int]]
    
    bingo :: MonadRandom m => m Bingo
    bingo = replaceCenter <$> mapM (fmap (map Just . take 5) . shuffleM) (chunksOf 15 [1 .. 75])
      where
        replaceCenter l = l & ix 2 . ix 2 .~ Nothing
    
    showBingo :: Bingo -> String
    showBingo bingo = unlines $ " B |  I |  N |  G |  O" : map showRow (transpose bingo)
      where
        showRow = intercalate " | " . map showCell
        showCell (Just n) | n < 10 = ' ' : show n
                          | otherwise = show n
        showCell Nothing = "  "
    
    main = bingo >>= putStr . showBingo
    
[/code]

GHC-7.10.1でチェックしてありますが、それ以前のバージョンではいくつか`import`を追加する必要があるかもしれません。


CC BY-SA 3.0
2015-04-05T14:10:08.607
8795
2015-04-05T23:21:18.077
2015-04-05T23:21:18.077
3066
3066
8789
answer