2016年4月1日金曜日

数字の並びを部分的に置換したいとき

数字の並びを部分的に置換することを考える。

たとえば、あるinteger vectorの中に存在する「3, 4, 5」を「5, 4, 3」に置き換えたいとき。
ここでは、文字列用の関数であるpaste()とgsub()を使ったトリックを紹介する。

適当なinteger vectorを作る。
int <- 0:9
int.vec <- c(int, int, int)

> int.vec
 [1] 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9


【for()ループでやる場合】

int.vec.new <- int.vec
for(i in 1:length(int.vec)){
if(int.vec[i] == 3 & int.vec[i+1] == 4 & int.vec[i+2] == 5){
int.vec.new[i:(i+2)] <- c(5, 4, 3)
i <- i + 2
}

> int.vec.new
 [1] 0 1 2 5 4 3 6 7 8 9 0 1 2 5 4 3 6 7 8 9 0 1 2 5 4 3 6 7 8 9


int.vecの中の「3, 4, 5」を「5, 4, 3」に置き換える仕事をfor()ループでやろうとすると、
このように一応はできるのだけれども、for()ループは遅い。
とくにvectorが長いほど遅さを実感する。

そこで、paste()とgsub()を使ったトリックの登場。


【paste()とgsub()でやる場合】

chr.vec <- paste(int.vec, sep = "", collapse = "")
int.vec.new <- as.integer(strsplit(gsub("345", "543", chr.vec), split = "")[[1]])

> int.vec.new
 [1] 0 1 2 5 4 3 6 7 8 9 0 1 2 5 4 3 6 7 8 9 0 1 2 5 4 3 6 7 8 9


まず、int.vecの値を文字列として繋げてしまう。
paste() はintegerを与えられたとしても、それをcharacterとして扱う。
collapseをうまく使えばすべての文字が繋がって、値がひとつのcharacter vectorになる。

chr.vec <- paste(int.vec, sep = "", collapse = "")

> chr.vec
[1] "012345678901234567890123456789"

こうしてひとつなぎの文字列にしてしまえば、gsub()が使える。
sub()は1つ目のヒットだけを置換するが、gsub()はすべてのヒットを置換する。

gsub("345", "543", chr.vec)

> gsub("345", "543", chr.vec)
[1] "012543678901254367890125436789"

あとは、strsplit()で1文字ずつ切り出し、integerに戻す。
strsplit() はリストで返すので、その1つ目を採用する。

> strsplit(gsub("345", "543", chr.vec), split = "")
[[1]]
 [1] "0" "1" "2" "5" "4" "3" "6" "7" "8" "9" "0" "1" "2" "5" "4" "3" "6" "7" "8"
[20] "9" "0" "1" "2" "5" "4" "3" "6" "7" "8" "9"


回りくどい仕事のように見えるが、対象が大きくなるほど早さを実感する。

0 件のコメント:

コメントを投稿