challenge 固定長データ

固定長のデータが記載されたファイルを読み込むプログラムを作成してください。読み込んだデータは、複数の値を格納できるデータ型に格納してください。

ファイルには、すべて ascii 文字で以下のデータが格納されています。デリミタはなく、固定長で格納されています。レコードとレコードのあいだも改行はありません。

  1. 姓 (12文字) 文字数が足りない場合は、後ろを空白で埋めてあります。
  2. 名 (12文字) 文字数が足りない場合は、後ろを空白で埋めてあります。
  3. 性別 (F,M,Uの3種類、1文字)
  4. 年齢 (3桁の数字、桁数が足りない場合は、ゼロで埋めず、頭を空白で埋めてあります。
  5. 年 2008 固定
  6. 月 03 固定
  7. さらに以下のデータが日付分くりかえされます。
    1. 日付 (01 〜 31) 2文字
    2. 朝食のメニュー (500文字)
    3. 昼食のメニュー (500文字)
    4. 夕食のメニュー (500文字)

以上の形式のデータ500人分を読みこんで、データを複数の値を格納できるデータ型に格納してください。データに大して何か処理を行う必要はなく、すぐに破棄してかまいません。

この問題は、このようなファイルをどのように扱うかを知りたくて作成しました。

Posted feedbacks - Haskell

敢えて冗長な書き方でやってみました。 メンテナンス性を考慮したつもりなんですが、見やすいかどうかは微妙です。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
--
--    固定長データを参照する
--

module Main where

import System

--
--    base length
--
firstNameLen = 12
lastNameLen = 12
sexLen = 1
ageLen = 3
yearLen = 4
monthLen = 2
dateLen = 2
breakFastLen = 500
lunchLen = 500
dinnerLen = 500

--
--    base position
--
firstNamePos = 0
lastNamePos = firstNamePos + firstNameLen
sexPos = lastNamePos + lastNameLen
agePos = sexPos + sexLen
yearPos = agePos + ageLen
monthPos = yearPos + yearLen
datePos = 0
breakFastPos = datePos + dateLen
lunchPos = breakFastPos + breakFastLen
dinnerPos = lunchPos + lunchLen

--
--    length
--
userHeaderLen = firstNameLen + lastNameLen + sexLen + ageLen + yearLen + monthLen
userDataLen = dateLen + breakFastLen + lunchLen + dinnerLen
userLen = userHeaderLen + userDataLen * 31

--
--    position
--
userPos n = userLen * n
userDataPos n = userHeaderLen + userDataLen * n

--
--    access utility
--
cut d (pos, len) = take len $ drop pos d

getUser d n = cut d (userPos n, userLen)
getUserData user n = cut user (userDataPos n, userDataLen)

getFirstName user = cut user (firstNamePos, firstNameLen)
getLastName user = cut user (lastNamePos, lastNameLen)
getSex user = cut user (sexPos, sexLen)
getAge user = cut user (agePos, ageLen)
getYear user = cut user (yearPos, yearLen)
getMonth user = cut user (monthPos, monthLen)
getDate user n = cut (getUserData user n) (datePos, dateLen)
getBreakFast user n = cut (getUserData user n) (breakFastPos, breakFastLen)
getLunch user n = cut (getUserData user n) (lunchPos, lunchLen)
getDinner user n = cut (getUserData user n) (dinnerPos, dinnerLen)

--
--    example
--
main = do
    args <- getArgs
    contents <- if (not.null) args
        then readFile $ head args
        else getContents
    let user = getUser contents 0
    putStrLn $ show $ getLastName user
    putStrLn $ show $ getFirstName user
--    where
--        user n = "Jyunichiro  " ++ "Koizumi     " ++ "F" ++ "66" ++
--                "2008" ++ "03" ++ days
--        days = show $ map (\x -> (dayNo x) ++ foods) [1..31]
--        dayNo n = reverse $ take 2 $ reverse $ "0" ++ (show n)
--        foods = concat $ replicate 3 (replicate 500 ' ')

こんなのはどうでしょう?Parsecとか、Genericsを使って、もっとかっこよくレコード定義を書くとそれがそのまま読み込み関数にならないかと思ったのですが、結局割りと基本的なHaskellのコードになってしまったのですが、ほかの言語のバージョンと張り合うために簡潔さを追求してみました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
module Main where

rdStr len = sequence $ replicate len getChar

chomp = reverse.((dropWhile (== ' ')).reverse)

rdMenu = do
    [day, b, l, d] <- mapM (rdStr) [2, 500, 500, 500]
    return $ ((read day)::Int, chomp b, chomp l, chomp d)

rdRecord = do
    [lnm, fnm, [gen], ag, yr, mo] <- mapM (rdStr) [12, 12, 1, 3, 4, 2]
    ms <- sequence $ replicate 31 (rdMenu)
    return $ (chomp lnm, chomp fnm, gen, (read ag)::Int, (read yr)::Int, (read mo)::Int, ms)

main = (sequence $ replicate 500 (rdRecord)) >>= print

あと、テストデータ生成コード...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module Main where

import Text.Printf

putDayMenu :: Int -> IO()
putDayMenu d = do
    printf "%2d" d
    putMenu
    putMenu
    putMenu
    where
        menu = "Menu entry"
        putMenu = putStr $ menu ++ (take (500 - length menu) $ repeat ' ')

putRecord = do
    putStr $ take 12 $ drop 1 $ cycle ['0'..'9']
    putStr $ take 12 $ drop 1 $ cycle ['0'..'9']
    putStr "M"
    putStr " 40"
    putStr "2008"
    putStr "03"
    mapM_ (putDayMenu) [1..31]

main = sequence_ $ replicate 500 (putRecord)

Data.Genericsの勉強もかねて、Generics版を作ってみました。最初のイメージどおり、データの定義をfieldとして一箇所にまとめることができました...結果的に、大きくなっちゃいましたけど...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module Main where

import Data.Generics

rdStr len = sequence $ replicate len getChar

chomp = reverse.((dropWhile (== ' ')).reverse)

data Field = Integer Int | Str String | Ch Char
    deriving (Data, Typeable, Show)

field = ((12::Int, Str ""), -- last name 
    (12::Int, Str ""), -- first name
    (1::Int, Ch ' '), -- gender
    (3::Int, Integer 0), -- age
    (4::Int, Integer 0), -- year
    (2::Int, Integer 0), -- month
    replicate 31 ((2::Int, Integer 0), --day
        (500::Int, Str ""), --breakfast
        (500::Int, Str ""), --lulnch
        (500::Int, Str ""))) -- dinner

readDB = everywhereM (mkM strReader) $ replicate 500 field
    where
        strReader :: (Int, Field) -> IO (Int, Field)
        strReader (cch, Str _) = do 
            str <- rdStr cch
            return (cch, Str $ chomp str)
        strReader (cch, Integer _) = do
            str <- rdStr cch
            return (cch, Integer $ read str)
        strReader (cch, Ch _) = do
            (ch: _) <- rdStr cch
            return (cch, Ch ch)

main = readDB >>= print

Index

Feed

Other

Link

Pathtraq

loading...