challenge 文字列の反転(括弧の対応を保存)

与えられた文字列を前後反転する関数 reverseString2 を書いてください。
ただし、reverseString2 は単純に文字列を反転するのではなく、括弧の対応
を保存するようにしてください。

以前のお題で作成した単純に与えられた文字列を単純に前後反転したもの返す
reverseString では

  reverseString("文字列(もじれつ)の反転(はんてん)") 
    → ")んてんは(転反の)つれじも(列字文"

のように括弧の対応は保存されませんが、reverseString2 では

  reverseString2("文字列(もじれつ)の反転(はんてん)")
    → "(んてんは)転反の(つれじも)列字文"

のように括弧の対応が保存されます。
括弧文字は、'('と')'、'{'と'}'、'['と']'で、それぞれASCII文字と仮定し
てください。

  reverseString2("対応[の{とれている(さまざまな)括弧}の(例)]です。")
    → "。すで[(例)の{弧括(なまざまさ)るいてれと}の]応対"

入力文字列では対応の取れている括弧の内側には対応の取れない括弧文字はな
いと解釈してください。たとえば、

  reverseString2("これ(は(対応のとれていない)括弧がある例です。")
    → "。すで例るあが弧括(いないてれとの応対)は(れこ"

次のような場合は対応のとれている括弧はないという解釈になります。

  reverseString2("これ(も{対応の)とれていない}括弧の例です")
    → "。すで例の弧括}いないてれと)の応対{も(れこ"

日本語対応にする場合の文字のエンコーディングは実装側で都合のよいように
仮定してください。日本語対応であることは望ましいですが、必須ではありま
せん。 

---
このお題はnobsunさんに投稿いただきました。ご協力ありがとうございます。

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
reverseString2 :: String -> String
reverseString2 str = let (e, s) = foldl f ([],[]) str in foldl (\s1 (s2,c) -> s2++c:s1) s e
    where f (e, s) c
              | c `elem` "({[" = (([], c):e, s)
              | c `elem` ")}]" = case e of
                                   (es1, p1):(es2, p2):rest
                                        | c == opposite p1 -> ((p1:es1++c:es2, p2):rest, s)
                                        | otherwise -> ((c:es1++p1:es2, p2):rest, s)
                                   [(es, p)]
                                       | c == opposite p -> ([], p:es++c:s)
                                       | otherwise -> ([], c:es++p:s)
                                   [] -> (e, c:s)
              | otherwise = case e of
                              (es, p):rest -> ((c:es, p):rest, s)
                              otherwise    -> (e, c:s)
          opposite '(' = ')'
          opposite ')' = '('
          opposite '{' = '}'
          opposite '}' = '{'
          opposite '[' = ']'
          opposite ']' = '['
          opposite c = c

Haskell練習中。
あまり美しくないなあ。
ghciで日本語はどう使うのかなあ。

実行例
*Main> reverseString2 "mojiretsu(MOJIRETSU) no hanten(HANTEN)."
".(NETNAH)netnah on (USTERIJOM)usterijom"
*Main> reverseString2 "taiou[no{toreteiru(samazamana)kakko}no(rei)]desu."
".used[(ier)on{okkak(anamazamas)urieterot}on]uoiat"
*Main> reverseString2 "kore(ha(taiounotoreteinai)kakkonoarureidesu."
".usedieruraonokkak(ianieterotonuoiat)ah(erok"
*Main> reverseString2 "kore(mo{taiouno)toreteinai}kakkonoreidesu."
".usedieronokkak}ianieterot)onuoiat{om(erok"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
parens = [('(',')'), ('[',']'), ('{','}')]

isOpener c = elem c $ map fst parens
isCloser c = elem c $ map snd parens

closer p = case lookup p parens of
             Just q -> q
             Nothing -> error "huh?"

revs [] rs zs = ([],rs++zs)
revs (x:xs) rs zs
  | isOpener x = case revs xs [] [x] of
                   (xs',rs') -> revs xs' (rs'++rs) zs
  | isCloser x = case zs of
                   [z] | x == closer z -> (xs, [z]++rs++[x])
                   _                   -> (xs, [x]++rs++zs)
  | otherwise  = revs xs (x:rs) zs

reverseString2 s = snd $ revs s [] []

ghcで日本語を正しく扱うには,いまのところ入出力の際に UTF8 <-> 内部コード
という変換をする必要があります.この変換を行うモジュールは以前自前で書いた
ことがあるのですが,今は Hackage DB に登録されています.

http://hackage.haskell.org/cgi-bin/hackage-scripts/package/utf8-string-0.2

インストール手順は以下のとおりです.

$ wget http://hackage.haskell.org/packages/archive/utf8-string/0.2/utf8-string-0.2.tar.gz
$ tar xf utf8-string-0.2.tar.gz
$ cd utf8-string-0.2
$ runhaskell Setup.lhs configure
$ runhaskell Setup.lhs build
$ sudo runhaskell Setup.lhs install

これで Codec.Binary.UTF8.String と System.IO.UTF8 モジュールが使えるように
なります.

$ ghc-pkg -l | grep utf8 

とやるとこのパッケージがインストールされたかどうか確認できます.
ghci には直接 UTF8 の文字列リテラルを入力できない(orz)ので,ソースコードに
定数として仕込んでおく必要があります.ソースコードのエンコーディングを
UTF8 にして

sample0 = "文字列(もじれつ)の反転(はんてん)"
sample1 = "対応[の{とれている(さまざまな)括弧}の(例)]です。"
sample2 = "これ(は(対応のとれてない)括弧がある例です。"
sample3 = "これ(も{対応の)とれていない}括弧の例です。"

というのをソースコード(3511.hs)にいれておきますそうしておいて,
- ghci を起動.
- System.IO.UTF8 を追加.
- 出力用 wrapper を追加.
- wrapper をつかって結果を表示

$ ghci -v0 3511.hs
*Main> :m + System.IO.UTF8
*Main System.IO.UTF8> let wrapper = (System.IO.UTF8.putStrLn .)
*Main System.IO.UTF8> wrapper reverseString2 sample0
(んてんは)転反の(つれじも)列字文
*Main System.IO.UTF8> wrapper reverseString2 sample1
。すで[(例)の{弧括(なまざまさ)るいてれと}の]応対
*Main System.IO.UTF8> wrapper reverseString2 sample2
。すで例るあが弧括(いなてれとの応対)は(れこ
*Main System.IO.UTF8> wrapper reverseString2 sample3
。すで例の弧括}いないてれと)の応対{も(れこ

ああ面倒 ^^;

「鶏を割くに牛刀をもってす」の感はありますが,
パーザを構成してしまうという手もあります.
 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
import qualified System.IO.UTF8 as U
import Text.ParserCombinators.ReadP

data Kakko = Maru | Nami | Kaku

data Inline = Plain String
            | Paren Kakko [Inline]

pPlain  = do { s <- many1 $ satisfy (flip notElem "(){}[]"); return (Plain s)}

pMaru = do { char '('; is <- many pInline; char ')'; return (Paren Maru is) }
pNami = do { char '{'; is <- many pInline; char '}'; return (Paren Nami is) }
pKaku = do { char '['; is <- many pInline; char ']'; return (Paren Kaku is) }

pParen = pMaru +++ pNami +++ pKaku
pInline = pPlain +++ pParen

pInlines =   many1 (pPlain +++ pParen)
         +++ do { c  <- satisfy (const True)
                ; is <- pInlines
                ; return (Plain [c] : is)
                }

revInline (Plain s) = Plain (reverse s)
revInline (Paren k is) = Paren k (foldl ((. revInline) . flip (:)) [] is)

instance Show Inline where
  show (Plain s) = s
  show (Paren k is) = case k of
                         Maru -> '(':(concatMap show is)++")"
                         Nami -> '{':(concatMap show is)++"}"
                         Kaku -> '[':(concatMap show is)++"]"

reverseString2 = concatMap show 
               . reverse 
               . map revInline 
               . fst 
               . head 
               . filter (null . snd) 
               . readP_to_S pInlines

sample0 = "文字列(もじれつ)の反転(はんてん)"
sample1 = "対応[の{とれている(さまざまな)括弧}の(例)]です。"
sample2 = "これ(は(対応のとれてない)括弧がある例です。"
sample3 = "これ(も{対応の)とれていない}括弧の例です。"

{-
*Main> U.putStrLn $ reverseString2 $ sample0
(んてんは)転反の(つれじも)列字文
*Main> U.putStrLn $ reverseString2 $ sample1
。すで[(例)の{弧括(なまざまさ)るいてれと}の]応対
*Main> U.putStrLn $ reverseString2 $ sample2
。すで例るあが弧括(いなてれとの応対)は(れこ
*Main> U.putStrLn $ reverseString2 $ sample3
。すで例の弧括}いないてれと)の応対{も(れこ
-}

一本の再帰呼び出しのパスで処理を終わらせられるように考えてみました。行きで閉じ括弧、帰りでひらき括弧の処理をしています。
 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
type Paren = Char
type ParenStack = [Paren]

reverseString2 :: String -> String
reverseString2 str = fst $ reverseStrParen (str, [])

reverseStrParen :: (String, ParenStack) -> (String, ParenStack)
reverseStrParen ([],  _)        = ([], [])
reverseStrParen (x:[], stkOpen) = ([flipParenIfMatch x stkOpen], pushCloseParen x [])
reverseStrParen (x:xs, stkOpen) = 
            (reverseRest ++ [flipParen2Way x stkOpen stkClose], pushCloseParen x stkClose)
        where
        (reverseRest, stkClose) = reverseStrParen (xs ,pushOpenParen x stkOpen)

flipParen2Way :: Char -> ParenStack -> ParenStack -> Char
flipParen2Way x stkOpen stkClose
    | isOpening x = flipParenIfMatch x stkClose
    | isClosing x = flipParenIfMatch x stkOpen
    | otherwise   = x

flipParenIfMatch :: Char -> ParenStack -> Char
flipParenIfMatch x [] = x
flipParenIfMatch x (y:_)
    | isOpposit x y = y
    | otherwise = x

pushCloseParen :: Paren -> ParenStack -> ParenStack
pushCloseParen x xs = pushpopParen isClosing isOpening x xs

pushOpenParen :: Paren -> ParenStack -> ParenStack
pushOpenParen x xs = pushpopParen isOpening isClosing x xs

pushpopParen :: (Paren -> Bool) -> (Paren -> Bool) -> Paren -> ParenStack -> ParenStack
pushpopParen isOpenClose _ x []
    | isOpenClose x = [x]
    | otherwise     = []
pushpopParen isOpenClose isCloseOpen x xs
    | isOpenClose x = x : xs
    | isCloseOpen x = drop 1 xs
    | otherwise     = xs

isClosing :: Paren -> Bool
isClosing x = elem x ")}]"

isOpening :: Paren -> Bool
isOpening x = elem x "({["

isOpposit :: Char -> Paren -> Bool
isOpposit x y
    | x == flipParen y = True
    | otherwise   = False

flipParen :: Paren -> Paren
flipParen '(' = ')'
flipParen '{' = '}'
flipParen '[' = ']'
flipParen ')' = '('
flipParen '}' = '{'
flipParen ']' = '['

*Main> sample (んてんは)転反の(つれじも)列字文 。すで[(例)の{弧括(なまざまさ)るいてれと}の]応対 。すで例るあが弧括(いないてれとの応対)は(れこ 。すで例の弧括}いないてれと)の応対{も(れこ

System Message: WARNING/2 (<string>, line 1); backlink

Inline emphasis start-string without end-string.
 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
import Text.ParserCombinators.Parsec
import qualified System.IO.UTF8 as U

parenParser = do
            op <- oneOf "([{"
            st <- mkParser
            try (oneOf ")]}" >>= return . (f op st)) <|> return (op:st)
    where f op st cp | check op cp = [cp] ++ st ++ [op]
                     | otherwise   = [op] ++ st ++ [cp]
          check '(' ')' = True
          check '{' '}' = True
          check '[' ']' = True
          check _ _     = False                     

strParser = many1 $ noneOf "()[]{}"

mkParser = many1 (try parenParser <|> strParser) >>= return . concat

reverseString str = case parse mkParser "" str of
                         Left err -> show err
                         Right x  -> reverse x

exec = U.putStrLn . reverseString

sample0 = "文字列(もじれつ)の反転(はんてん)"
sample1 = "対応[の{とれている(さまざまな)括弧}の(例)]です。"
sample2 = "これ(は(対応のとれていない)括弧がある例です。"
sample3 = "これ(も{対応の)とれていない}括弧の例です。"

sample = mapM_ exec [sample0,sample1,sample2,sample3]

Index

Feed

Other

Link

Pathtraq

loading...