関数

8.5 本章のまとめ


脊髄反射的に」ポイントフリースタイル書き換えが身につくように頑張ります。

8.6 練習問題


いつものように実験しながら自力で解いていきます。

lstrip関数について

dropWhile関数はこんな感じの動き。

Prelude> dropWhile (== 1) [1, 1, 2, 1, 3]
[2,1,3]
Prelude> dropWhile (== '$') "$$$$###$$$"
"###$$$"
Prelude> dropWhile (== ' ') "            12345        67890"
"12345        67890"


まずはベタで(ポイントフリースタイルを使わずに)解いてみる。
いつぞや話があったイディオム(unlines → 行ごとの処理 → lines)を使って実装してみよう。
こんな感じ。

  • lstrip.hs
main = do cs <- getContents
          putStr $ lstrip cs

lstrip :: String -> String
lstrip cs = unlines $ map myDropWhile $ lines cs

myDropWhile :: String -> String
myDropWhile cs = dropWhile (== ' ') cs


そして動作させてみる。

% ghc -W -o lstrip lstrip.hs 
% cat test.txt
   abc abc
  abc  abc
 bcd   bcd
  bcd  bcd
   abc abc
    cdecde
% ./lstrip < test.txt
abc abc
abc  abc
bcd   bcd
bcd  bcd
abc abc
cdecde


でも設問はポイントフリースタイルを使いなさいとのこと。
上のコードをポイントフリースタイルで書き直してみる。こんな感じ。

  • lstrip2.hs
main = do cs <- getContents
          putStr $ lstrip cs

{--
  lstrip
  各行の先頭にあるスペースを取り除く。
--}
lstrip :: String -> String
lstrip = unlines . map (dropWhile (== ' ')) . lines


うん、いい感じに縮まりましたね。
いきなりこのスタイルを捻り出すのは難しいですけどね。

rstrip関数について

さて次の設問へ進みます。lstripの逆(末尾のスペースを取り除く)ですね。
う〜ん、何といっても文字列を逆順に並べ替えたいですよね。
ということで実験してみます。

Prelude> reverse "abcdefg"
"gfedcba"
Prelude> reverse $ reverse "abcdefg"
"abcdefg"


よしよし、reverse関数いけそうだ。lstripを使ってもよいのでこんな感じ。

  • rstrip.hs
main = do cs <- getContents
          putStr $ reverse $ rstrip cs

{--
  rstrip
  各行の末尾にあるスペースを取り除く。
--}
rstrip :: String -> String
rstrip = reverse . lstrip . reverse

{--
  lstrip
  各行の先頭にあるスペースを取り除く。
--}
lstrip :: String -> String
lstrip = unlines . map (dropWhile (== ' ')) . lines


そのまま出力しても分かりにくいので、末尾のスペースをとった後、
逆順にして出力しています。結果はこんな感じ。

% ghc -W -o rstrip rstrip.hs
% cat test2.txt             
   abc abc     
  abc  abc     
 bcd   bcd     
  bcd  bcd     
   abc abc     
    cdecde     
% ./rstrip < test2.txt      

edcedc    
cba cba   
dcb  dcb  
dcb   dcb 
cba  cba  
cba cba 


あれれ、先頭に改行が出力されてる。
unlinesしたとき、各行に改行コードが付加されるけど、必要のない改行コードも付加されちゃってる模様。
入力を一気にreverseするから問題なわけです。
一行ずつreverseするように変えてみます。なお、やっぱりスペースだと分かりにくいので
@を消すように変えてます。

main = do cs <- getContents
          putStr $ unlines $ map (rstrip '@') $ lines cs

{--
  rstrip
  末尾にある指定文字を取り除く。
--}
rstrip :: Char -> String -> String
rstrip c = reverse . (lstrip c) . reverse

{--
  lstrip
  先頭にある指定文字を取り除く。
--}
lstrip :: Char -> String -> String
lstrip c = dropWhile (== c)


結果はこんな感じ。

% ghc -W -o rstrip rstrip.hs
% cat test3.txt             
@@@@abc abc@@@@@@
@@@@@@@@@@@@abc@@ @ abc@@@
@bcd @ @ @ @ bcd @ @ @ @ @
@@@@bcd  bcd    @@ @@ @
@ @ @@@   abc abc @@@@@@@@
@ @ cdecde @@@@@@@@@@@@@@@@@@@@@@@
% ./rstrip < test3.txt      
@@@@abc abc
@@@@@@@@@@@@abc@@ @ abc
@bcd @ @ @ @ bcd @ @ @ @ 
@@@@bcd  bcd    @@ @@ 
@ @ @@@   abc abc 
@ @ cdecde 

いい感じです♪

strip関数について

次は文字列の先頭と末尾のスペースを全て取り除くstrip関数。
単純に考えると、lstripを呼んでからrstripを呼べばよさげですね。こんな感じ。

  • strip.hs
main = do cs <- getContents
          putStr $ unlines $ map (strip '@') $ lines cs

{--
  strip
  各行の先頭・末尾にある指定された文字を取り除く。
--}
strip :: Char -> String -> String
strip c = (rstrip c) . (lstrip c)

{--
  rstrip
  各行の末尾にある指定文字を取り除く。
--}
rstrip :: Char -> String -> String
rstrip c = reverse . (lstrip c) . reverse

{--
  lstrip
  各行の先頭にある指定文字を取り除く。
--}
lstrip :: Char -> String -> String
lstrip c = dropWhile (== c)

結果はこんな感じ。

% ghc -W -o strip strip.hs
% cat test3.txt 
@@@@abc abc@@@@@@
@@@@@@@@@@@@abc@@ @ abc@@@
@bcd @ @ @ @ bcd @ @ @ @ @
@@@@bcd  bcd    @@ @@ @
@ @ @@@   abc abc @@@@@@@@
@ @ cdecde @@@@@@@@@@@@@@@@@@@@@@@
[normal_haskell/]% ./strip < test3.txt
abc abc
abc@@ @ abc
bcd @ @ @ @ bcd @ @ @ @ 
bcd  bcd    @@ @@ 
 @ @@@   abc abc 
 @ cdecde 


うん、いい感じですね。

tail.hsについて

さて次は以前作成したtail.hsをポイントフリースタイルに書き換える問題。
こんな感じ。

main = do cs <- getContents
          putStr $ lastNLines 10 cs

-- lastNLines n cs = unlines $ takeLast n $ lines cs
lastNLines n = unlines . takeLast n . lines

-- takeLast n ss = reverse $ take n $ reverse ss
takeLast n = reverse . take n . reverse


コメントが修正前のコードです。どちらも引数が一つ減ってます。

fgrep.hsについて

そして最後の問題。以前作成したfgrep.hsをポイントフリースタイルに書き換える。
って、これ8.4の中でやってましたよね?
とりあえず修正後のコードはこんな感じです。

import System
import List

main = do args <- getArgs
          cs   <- getContents
          putStr $ fgrep (head args) cs

fgrep :: String -> String -> String
fgrep pattern = unlines . filter (match pattern) . lines

match :: String -> String -> Bool
match pattern = any (pattern `isPrefixOf`) . tails