必要に迫られて、タブを空白に展開する機能が必要になり、自分でコードを書く。行に分割すれば、改行を考えなくてよいので、単純に一行分の文字列を展開するものです。
考えた手順はこんな感じ。
- 文字列から一文字ずつ読み込み、タブ文字でなかったらそのまま。
- タブ文字だったら、必要な空白文字列に置き換える。
- 必要な空白文字列の長さは、タブの桁数から1までの間のいずれか。
- 空白文字列の長さは、一文字読み込むたびに1ずつ減らし、0になる場合にはタブの桁数にする。
- タブ文字を置き換えたときは、空白文字の長さはタブの桁数にする。
- 読み込む文字がなくなったらおしまい。
この考えをもとに、C++で書いたのが次のコード。わかりやすさ優先、効率が悪いところもあえてそのまま。それ以前のデキの善し悪しはちょっと脇において(ご覧になっている方がいらしたら、ツッコミ歓迎です)。
#include <string> std::string tabToSpace(int tabSize, const std::string& s) { std::string result; int spacingSize = tabSize; for(std::string::const_iterator i = s.begin(); i != s.end(); ++i) { if(*i == '?t') { result += std::string(spacingSize, ' '); spacingSize = tabSize; } else { result += *i; --spacingSize; if(spacingSize == 0) { spacingSize = tabSize; } } } return result; }
勉強がてら。これをHaskellでも書いてみました。
tabToSpace tabSize s = tabToSpace' tabSize s where tabToSpace' _ "" = "" tabToSpace' 0 s = tabToSpace' tabSize s tabToSpace' i ('?t':cs) = (take i $ repeat ' ') ++ (tabToSpace' tabSize cs) tabToSpace' i (c:cs) = c:(tabToSpace' (i-1) cs)
書き上げてしばし固まりました。なにこの簡潔さ、単純さ、短さ。きっと何かを忘れているに違いないと確かめたんですが…上のC++のコードと同じ内容のようです。定義を当てはめてみると、どちらのコードにも1から6が入ってます。同じ内容のようです。
Haskell向きのお題だったとはいえ、この結果に関数型言語の力を見た気がします。「仕事もHaskellで書けたら…」と妄想。3秒で醒めました。さすがにそれは今は無理。