R の文字列結合が面倒くさいので色々考えてみた

1. はじめに

みんな大好き R ですが、おそらく共通して不満を持っていることがあります。
それは「文字列結合が面倒くさい」ということです。

デフォルトの R での文字列結合を見てみましょう。

paste("hoge", "fuga", "piyo", sep = "")
## [1] "hogefugapiyo"

非常に面倒くさいです。
R は他言語にあるような文字列結合のシンタックスシュガーを持たないため、いちいち paste() を呼び出すはめになるのです。

2. 自分で定義する

ところで、 R では、デフォルトで使用されている演算子をユーザ定義関数で上書きすることができます。
この裏技を使って、+ 演算子を上書きして文字列連結に使うことはできないでしょうか?
つまり、次のような文字列連結の糖衣構文を作るのです。

"hoge" + "fuga" + "piyo"
# => この結果が 'hogefugapiyo' になるように + を再定義したい

+ 演算子を再定義するとき、+ 演算子のデフォルトの挙動を変えないように注意する必要があります。
まず、+ 演算子が文字列に対応していないことを確認します。

"hoge" + "fuga"
## Error: 二項演算子の引数が数値ではありません

エラーが出ました。+ 演算子は文字列入力に対応していないようです。
つまり、文字列が入力されたときは文字列連結を行い、他の入力が与えられた時はデフォルトの挙動をするように + を再定義すればよいことになります。

"+" <- function(e1, e2) {
    if (is.character(c(e1, e2))) {
        paste(e1, e2, sep = "")
    } else {
        base::"+"(e1, e2)
    }
}

これが思った通りに動作することを確認してみてください。

"hoge" + "fuga" + "piyo"
## [1] "hogefugapiyo"

3. Excel をリスペクトする

上記ですべてがうまくいったようかに思えますが、問題があります。 下記のコードを実行してみてください。

x <- +1
## Error: 'e2' が見つかりません

x という変数に +1 を代入しようとしていますが、エラーが出ています。
この原因は、+ 記号には単項演算子としての機能があり、それが上記変更により動作しなくなったためと考えられます。
上記で行った変更を取り消すと、エラーが出なくなります。

rm("+")
x <- +1
print(x)
## [1] 1

以上により、+ 演算子は文字列連結には使えないようです。

じゃあどうするかというと、みんな大好き Excel さんをリスペクトして、& 演算子で文字列連結を行ってみてはどうでしょうか?

"hoge" & "fuga"
## Error: 演算が可能なのは数値、論理値、そして複素数型のみです

エラーが出るので、& 演算子は文字列入力に対応していません。
そこで、& 演算子を次のように再定義します。

"&" <- function(e1, e2) {
    if (is.character(c(e1, e2))) {
        paste(e1, e2, sep = "")
    } else {
        base::"&"(e1, e2)
    }
}

これにより、次のような記述が可能になります。

"hoge" & "fuga" & "piyo"
## [1] "hogefugapiyo"

うまくいきました。

4. paste を短い名前にする

さて、上記の方法では、既存の演算子を上書きするということをやっていますが、これは結構不安です。
なぜなら、既存の演算子を上書きすると、思いがけないところで思いがけないバグが発生するかもしれないからです。

というわけで、別の方法も模索してみます。
デフォルトの文字列連結の問題点は、単に、いちいち paste と書くのが億劫なだけかもしれません。
なので、paste を短い名前に置き換えてみてはどうでしょうか?

L <- paste

ちなみに、L は私が適当に選んだ関数名なので P でも何でも自分で好きに決めてください。
(個人的には cat と書くのは億劫に思わないので、3文字までは許容できるのではないかと思います)

また、pastesep 引数のデフォルトが " " なのも面倒くさい原因なので、デフォルトを sep="" に書き換えてしまいましょう。

L <- function(..., sep = "", collapse = NULL) {
    paste(..., sep = sep, collapse = collapse)
}

これにより、

L("hoge", "fuga", "piyo")
## [1] "hogefugapiyo"

と書くことができるようになりました。 やったね!

5. sprintf を組み込む

ところで、欲深い私はこれではまだ満足しません。
R には sprintf という非常に便利な関数があります。(参照:Use C-style String Formatting Commands - R help)

sprintf("tax is %.2f", 0.05)
## [1] "tax is 0.05"

このように、フォーマットに従った文字列を返してくれるナイスガイです。

この sprintf を使って、文字列のみを入力されたときは文字列連結を、書式を入力されたときは sprintf となるような関数を定義してみます。

L <- function(..., f) {
    if (missing(f)) 
        f <- paste(rep("%s", length(c(...))), collapse = "")
    sprintf(fmt = f, ...)
}

これを使えば、文字列の出力がかなり楽になります。

L("hoge", "fuga", "piyo")
## [1] "hogefugapiyo"
L(f = "tax is %.2f", 0.05)
## [1] "tax is 0.05"

どうでしょうか?

6. まとめ

今日は文字列連結について色々と考えてみました。
結論はありません。 みなさん独自に好きなようにすればいいと思います。
私のケースが参考になればうれしいです。

俺たちの戦いは始まったばかりだ!

<未完>