タブをスペースで展開する

お題だけ拝借。

 cf. Gaucheクックブック – タブをスペースで展開する

1文字ずつ処理する。正規表現を使ったり日本語を考慮するのはパス。

untabify :: Int -> String -> String
untabify w = f "" 0
  where
    f r _ [] = r
    f r p (c:cs) | '\t' == c = f (r ++ replicate (ts p) ' ') (p + ts p) cs
                 | otherwise = f (r ++ [c]) (p + 1) cs
    ts p = w - p `mod` w

はじめは foldl を使おうと思ったけど f の引数が3つになる(展開後の文字列を位置を蓄積する必要がある)のであきらめた。あと,haskell らしくリストの引数は後ろに。
実行例。

*Main> untabify 8 "012\t012345\t01"
"012     012345  01"

タブ幅は2文字がすき。

*Main> untabify 2 "012\t012345\t01"
"012 012345  01"

あ,そうか。f の引数をタプル(ペア)にしてやれば foldl が使えるんだ。

untabify2 :: Int -> String -> String
untabify2 w = fst . foldl f ("", 0)
  where
    f (r, p) c | '\t' == c = ( r ++ replicate (ts p) ' ' , p + ts p )
               | otherwise = ( r ++ [c] , p + 1 )
    ts p = w - p `mod` w
*Main> untabify2 8 "012\t012345\t01"
"012     012345  01"
*Main> untabify2 2 "012\t012345\t01"
"012 012345  01"

String#underscore を Haskell で

cf. HaHaHa! – ハイフンで区切られた文字をキャピタライズ
cf. 趣味的にっき – ハイフンで区切られた文字をキャピタライズ

↑ここら辺を見て思い出したのが,Ruby on Rails (より正確には ActiveSupport)にある String#underscore。
String#underscore は大文字/小文字/数字からなる文字列を小文字/数字/アンダースコアからなる文字列に変換するメソッドで……つまりこんな感じ。

D:\>irb -rrubygems -ractive_support --simple-prompt
>> [ "Railes",
?> "ActiveSupport",
?> "Active1Support",
?> "Active1support",
?> "ToDoList",
?> "XML",
?> "XML2",
?> "XMLData",
?> "XMLdata",
?> "XML2Data",
?> "Iso2022jpMailer"
>> ].each do |s| p s.underscore end
"railes"
"active_support"
"active1_support"
"active1support"
"to_do_list"
"xml"
"xml2"
"xml_data"
"xm_ldata"
"xml2_data"
"iso2022jp_mailer"
=> ["Railes", "ActiveSupport", "Active1Support", "Active1support", "ToDoList", "
XML", "XML2", "XMLData", "XMLdata", "XML2Data", "Iso2022jpMailer"]

これを Haskell でやってみた。

‘_’ は大文字の直前に入るんだけど,前後の文字によって入ったり入らなかったりで,ちょっと複雑。なので State モナドを使ってみた。

import Control.Monad.State
import Data.Char
import System.Environment

data CharCase = UpperB4Upper | UpperB4Lower | UpperB4Number | UpperAtEnd | Lower | Number | None

cState :: Char -> CharCase
cState c | isUpper c = UpperB4Upper
         | isLower c = Lower
         | otherwise = Number

procChar :: Char -> State CharCase [Char]
procChar c = get >>= p
  where
    p UpperB4Upper = do put $ cState c
                        return [toLower c]
    p UpperB4Lower = do put $ cState c
                        return $ "_" ++ [toLower c]
    p UpperB4Number = do put $ cState c
                         if isUpper c then return [toLower c]
                         else return $ "_" ++ [c]
    p UpperAtEnd = do put $ cState c
                      if isUpper c then return [toLower c]
                      else return $ "_" ++ [c]
    p Lower | isUpper c = do put (UpperB4Lower)
                             return [toLower c]
            | otherwise = do put $ cState c
                             return [c]
    p Number | isUpper c = do put (UpperB4Number)
                              return [toLower c]
             | otherwise = do put $ cState c
                              return [c]
    p None = do put $ if isUpper c then UpperAtEnd else cState c
                return [toLower c]

underScoreR :: String -> State CharCase String
underScoreR s = do strs <- mapM procChar $ reverse s return $ reverse $ concat strs underScore :: String -> String

underScore s = evalState (underScoreR s) None

UpperAtEnd は UpperB4Number と扱いが同じだから無くてもいいかも。

チェック用関数。

samples = [ ( "Rails" , "rails" )
          , ( "ActiveSupport" , "active_support" )
          , ( "Active1Support" , "active1_support" )
          , ( "Active1support" , "active1support" )
          , ( "ToDoList" , "to_do_list" )
          , ( "XML" , "xml" )
          , ( "XML2" , "xml2" )
          , ( "XMLData" , "xml_data" )
          , ( "2XMLdata" , "xm_ldata" )
          , ( "XML2Data" , "xml2_data" )
          , ( "Iso2022jpMailer" , "iso2022jp_mailer" )
          ]

checkSamples = mapM_ (putStrLn . check) samples

check s = (show $ underScore org == uds) ++ " " ++ org ++ " => " ++ uds
  where
    org = fst s
    uds = snd s

実際にやってみると

*Main> checkSamples
Loading package mtl-1.0 ... linking ... done.
True    Rails => rails
True    ActiveSupport => active_support
True    Active1Support => active1_support
True    Active1support => active1support
True    ToDoList => to_do_list
True    XML => xml
True    XML2 => xml2
True    XMLData => xml_data
True    XMLdata => xm_ldata
True    XML2Data => xml2_data
True    Iso2022jpMailer => iso2022jp_mailer

OKみたい。

テキストでプログレスバー

cf. Ruby/ProgressBar: プログレスバーをテキストで表示する Ruby用のライブラリ

via 趣味的にっき – プログレスバーをテキストで表示する関数

インストールは progressbar.rb をライブラリパスの通ったところにおけばいいだけ。

ProgressBar のインスタンスをつくっておいて ProgressBar#inc か ProgressBar#set で進捗を表示する。

まずは記事にある使用例で試してみよう。

D:\>irb -rprogressbar
irb(main):001:0> pbar = ProgressBar.new("title", 100)
=> #<ProgressBar:0/100>                                        | ETA:  --:--:--
irb(main):002:0> (1..100).each{|i| sleep(0.1); pbar.set(i)}; pbar.finish
title:         100% |oooooooooooooooooooooooooooooooooooooooooo| Time: 00:00:35
=> Thu Dec 21 21:29:46 +0000 2006

ProgressBar#file_transfer_mode を使うと,ファイルの転送バイト数と転送速度を表示できる。

こんな感じ。

require 'progressbar'

fname1 = ARGV.shift
fname2 = ARGV.shift

f1 = File.open(fname1, "rb")
f2 = File.open(fname2, "wb")
size = File.size(fname1)

pbar = ProgressBarnew("transfer file", size)
pbar.file_transfer_mode

until f1.eof?
  f2.write(f1.read(1000))
  pbar.inc(1000)
end
pbar.finish

f1.close
f2.close

ファイルのコピーを低レベルでやらなきゃいけないけど。

実行例。

D:\>copy_pbar.rb projects.xls copy_projects.xls
transfer file: 100% |oooooooooooooooooooooooo|   5.0MB  35.5MB/s Time: 00:00:00

3引数のflip

久しぶりに時間があいたので Haskell をやってみよう,と思ったらこんなのを見つけた。

 haskellのある暮らし - 頭の体操:3引数flip

f :: a -> b -> c -> d なる関数 f があったとして,

flip31 f a c b = f a b c

みたいに引数の順番を入れ替える関数をポイントフリースタイルで定義せよ,ってのが今回の指令。ただし f は残っても良い。
さっそくやってみるさー。

といってもパッと答えがひらめいたりはしないので地道に変換する。

flip31 f a c b = f a b c
            -->  flip (f a) c b
            -->  (flip.f) a c b

となって,引数を消すと

flip31 f = flip.f

つぎ。

flip32 f b a c = f a b c
            -->  (f a b) c
            -->  (flip f b a) c
            -->  (flip f) b a c

結局

flip32 f = flip f

あー,つまり flip の引数が特殊な場合ってことか。

残りの3つは結果だけ書く。

flip33 f = flip.(flip f)

flip34 f = flip (flip.f)

flip35 f = flip (flip.(flip f))

これでOKのはず。
もと記事にある関数でチェックしてみる。

*Main> check
[True,True,True,True,True]

OKのようだ。

もう少し,見た目にわかりやすいようなチェックをしてみよう。

*Main> let f a b c = a:b:c:[]
*Main> flip31 f 'a' 'c' 'b'
"abc"
*Main> flip32 f 'b' 'a' 'c'
"abc"
*Main> flip33 f 'b' 'c' 'a'
"abc"
*Main> flip34 f 'c' 'a' 'b'
"abc"
*Main> flip35 f 'c' 'b' 'a'
"abc"

OK。
けど,せめて関数の型をちゃんとかいておかないとさっぱりわかんないよな,これ。

追記:
さらに f も無くしてみる。

flip31' :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31' = (flip.)

flip32' :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32' = flip

flip33' :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33' = (flip.).flip

flip34' :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34' = flip.(flip.)

flip35' :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35' = flip.((flip.).flip)