challenge 文字列からの情報抽出

与えられた文字列から特定の条件を満たす文字列を抽出するコードを書いてください。 状況としてはテキスト形式で渡された原稿の中から、画像のファイル名を抽出するようなものをイメージしてください。

サンプル入力

aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge

サンプル出力

name:'abc', ext:'png', size: normal hidden: True
name:'hoge', ext:'jpeg', size: big hidden: False
name:'foo', ext:'gif', size: small hidden: True
name:'a', ext:'bmp', size: normal hidden: False
name:'hoge', ext:'png', size: normal hidden: False
name:'small', ext:'jpg', size: normal hidden: False
name:'small', ext:'hoge', size: big hidden: False

探すべき文字列は下の条件を満たします

  • アルファベットと1個のピリオド、ハイフンで構成される
  • 前後にはアルファベットではない文字がある(abcd.jpgがaaaabcd.jpghogeなどと書かれていることはない)
  • ピリオドの後ろは拡張子で、アルファベットだけで構成されている
  • ピリオドの直前に-hidden, -small, -bigがある場合には特殊な意味がある。複数個ある場合(a-hidden-big.jpgなど)も同じ
  • ファイル名に-hiddenと-smallまたは-hiddenと-bigの両方が含まれる場合は-hiddenの方が先にある
  • 特殊な意味の-hidden, -small, -big以外でハイフンが使われることはない
  • 特殊な意味の-smallと-bigの両方が付くことはない

出力は以下の条件を満たす必要があります

  • ファイル名が出現した順に表示される
  • ファイル名に-hiddenが含まれるかどうかを真偽値で表示する
  • ファイル名に-smallまたは-bigが含まれる場合はsmallまたはbigと、含まれない場合はnormalと表示する
  • -hidden, -small, -bigを取り除いたファイル名部分と、拡張子を表示する

このお題は、正規表現のグループに名前をつけて連想配列として取得できるPythonからの挑戦状です。

Posted feedbacks - Haskell

良く分かりませんでした。
取り合えずparsecの練習で。
 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 System
import Char
import Text.ParserCombinators.Parsec hiding (spaces)
import Monad
import Control.Monad.Error
import IO hiding (try)

main = readFile "a.txt" >>= pp >>= print >> return ()
pp s = case parse (many $try(wordx)<|>other) "lisp" s of
         (Right x) -> return $filter okparse x
         (Left x)  -> error "error"
okparse ("",_,_,_) = False
okparse  (_,_,_,_)= True
w = many1 letter
hi = do  _ <- char '-'
         x <- string "hidden"
         return x
sz = do  _ <- char '-'
         x <- try(string "big") <|> string "small"
         return x

wordx = do x <- w
           (y, z) <- (do yy <-try (hi)
                         zz <-option "" sz
                         return (yy,zz))
                     <|>
                     (do zz<- option "" sz
                         return ("", zz))
           char '.'
           ext <- w
           return (x,y,z, ext)

other = do x <- noneOf ""
           return ("", "","","")

個人的はProgramming Policyにより、正規表現は封印中
でもParsec は使い方を思い出すのがちょっと面倒ね。
ということで。。。
 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
import Data.Char
import Text.ParserCombinators.ReadP

type Name = String
type Size = String
type Hidden = Bool

type Info = (Name,Name,Size,Hidden)

pName :: ReadP Name
pName = munch1 isAlpha

pSize :: ReadP Size
pSize = (string "-big"   >> return "big")
    +++ (string "-small" >> return "small")
    +++ (return "normal")

pHidden :: ReadP Hidden
pHidden = (string "-hidden" >> return True)
      +++ (return False)

pExt :: ReadP Name
pExt =  char '.' >> pName

pInfo :: ReadP Info 
pInfo = do { name <- pName
	   ; size <- pSize
	   ; hidden <- pHidden
	   ; ext <- pExt
	   ; return (name,ext,size,hidden)
	   }

showInfo :: Info -> String
showInfo (name,ext,size,hidden)
 = "name:'"++name++"', ext:'"++ext++", size: "++size++" hidden: "++show hidden

readInfo :: ReadS Info
readInfo = readP_to_S pInfo

infoReader :: String -> [Info]
infoReader [] = []
infoReader ccs@(c:cs) = case readInfo ccs of
  []          -> infoReader cs
  (info,rs):_ -> info : infoReader rs

main :: IO ()
main = test

test :: IO ()
test = putStr $ unlines $ map showInfo $ infoReader $ testdata

testdata = unlines
 ["aaa abc-hidden.png>hoge-big.jpeg"
 ,"---foo-hidden-small.gif|^_^a.bmp"
 ,"--hiddena-hoge.png<=not hidden~~"
 ,"--small.jpg<=not small(^_^)"
 ,"normal-small-big.hoge"]

{- 実行例
*Main> :main
name:'abc', ext:'png, size: normal hidden: True
name:'hoge', ext:'jpeg, size: big hidden: False
name:'hidden', ext:'gif, size: small hidden: False
name:'a', ext:'bmp, size: normal hidden: False
name:'hoge', ext:'png, size: normal hidden: False
name:'small', ext:'jpg, size: normal hidden: False
name:'small', ext:'hoge, size: big hidden: False
-}

文字列からパターンにマッチする表現のデータをとりだしたり,
特定のパターンにマッチする表現を別の表現で置換したり,

というのが正規表現の使いどころなんだね.
(という自明なことに今さら気づきました.^^;)

とするとパターンを指定するのは正規表現ではなくて,たとえばもうすこし
拡張してパーザで指定してもいいわけですよね.(これも自明か^^;)

たとえば,ReadP a が一般化されたパターンを表すと思えばいいわけでよね...

そうすると性能の問題はあるにせよ、
一般化したパターンをもらって,(雑音のある)文字列からデータを取り出す関数を
生成する関数 reader とか,パターン(とデータを文字列に変換する関数)をもらって
文字列のなかの部分列を置換する関数を生成する関数 replacer とかがあれば汎用性
があるはずですよねぇ.(正規表現はまさにこのように使うのでしょうから)

そうすると先に投稿したコードでは infoReader の定義は

infoReader = reader pInfo

となるし,たとえば入力文字列からデータ部分をとりのぞいて雑音部分だけに
してしまうにような infoNoise は

infoNoise = replacer pInfo (const "") 

なんてことができますね.

*Main> let infoNoise = replacer pInfo (const "")
*Main> infoNoise testdata
"aaa >\n---foo-|^_^\n--hiddena-<=not hidden~~\n--<=not small(^_^)\nnormal-\n"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
reader :: ReadP a -> (String -> [a])
reader parser string = case string of
 "" -> []
 _  -> case readP_to_S parser string of
   []            -> reader parser (tail string)
   (a,string'):_ -> a : reader parser string'

replacer :: ReadP a -> (a -> String) -> String -> String
replacer p s str = case str of
  "" -> ""
  _  -> case readP_to_S p str of
    []         -> head str : replacer p s (tail str)
    (a,str'):_ -> s a ++ replacer p s str'

とりあえずサンプルはできた感じです。多分。

*Main> :!cat sample.txt
aaa abc-hidden.png>hoge-big.jpeg
---foo-hidden-small.gif|^_^a.bmp
--hiddena-hoge.png<=not hidden~~
--small.jpg<=not small(^_^)
normal-small-big.hoge
*Main> 
*Main> :main sample.txt
name:'abc', ext:'png', size: normal hidden: True
name:'hoge', ext:'jpeg', size: big hidden: False
name:'foo', ext:'gif', size: small hidden: True
name:'a', ext:'bmp', size: normal hidden: False
name:'hoge', ext:'png', size: normal hidden: False
name:'small', ext:'jpg', size: normal hidden: False
name:'small', ext:'hoge', size: big hidden: False
 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
import System (getArgs)
import Control.Monad (liftM)
import Text.ParserCombinators.Parsec

data Val = V {name :: String, ext :: String, hidden :: String, size :: String} | None deriving Eq

instance Show Val where
  show (V n e h s) = "name:'" ++ n ++ "', ext:'" ++ e ++ "', size: " ++ s ++ " hidden: " ++ h
  show None = ""

alpha = ['a'..'z'] ++ ['A'..'Z']

nameParser :: GenParser Char () String
nameParser = many1 (oneOf alpha)

extParser :: GenParser Char () String
extParser = char '.' >> many1 (oneOf alpha)

hiddenParser :: GenParser Char () String
hiddenParser = try ((string "-hidden") >> return "True") <|> return "False"

sizeParser :: GenParser Char () String
sizeParser = try (string "-small" >> return "small")
   <|> try (string "-big" >> return "big")
   <|> return "normal"

expression :: GenParser Char () Val
expression = do
     n <- nameParser
     h <- hiddenParser
     s <- sizeParser
     e <- extParser
     return $ V n e h s

skip :: GenParser Char () Val
skip = manyTill (many1 (oneOf alpha)) (many1 (noneOf alpha)) >> return None
        
expressionParser :: GenParser Char () [Val]
expressionParser = do
        exp  <- try expression <|> skip
        exps <- try expressionParser <|> return []
        return $ exp:exps

run :: String -> Either ParseError [Val]
run = parse expressionParser ""

main = do
     s <- liftM run . readFile . head =<< getArgs
     case s of
       Left  e -> print e
       Right x -> mapM_ print $ filter (/= None) x

汚い。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import Control.Monad
import Data.Char
import System.Environment
import Text.ParserCombinators.Parsec

main = getArgs >>= parseFromFile allInfo . head >>= \(Right i) -> mapM_ (putStrLn . showInfo) i
showInfo [n,h,s,e] = "name:'" ++ n ++ "', ext:'"++ e ++"', size: "++ s ++" hidden: " ++ h
allInfo = liftM (filter (not.null)) (manyTill (try info <|> skip) eof)
skip = many letter >> many (satisfy (not.isAlpha)) >> return []
info = sequence [many1 letter, hidden, size, char '.' >> many1 letter]
hidden = liftM (show.not.null) (option "" (try (string "-hidden")))
size = (char '-' >> (string "big" <|> string "small")) <|> return "normal"

Index

Feed

Other

Link

Pathtraq

loading...