メモ:正規表現でreplacement functionを使いたいときはbaseの関数が便利っぽい

formatRのコードを読んでいると、こんな箇所がありました。

    m = gregexpr(mat.comment, text.mask)
    regmatches(text.mask, m) = lapply(regmatches(text.mask, m), restore_bs)

(https://github.com/yihui/formatR/blob/022da8c1be2c04c8374d19907c41bacd5d0ecfcc/R/tidy.R#L107-L108)

stringrに慣れきってしまって何をしてるのかさっぱりわからなかったんですが、regmatches<-というreplacement functionがあるらしいです。

gregexpr()は、パターンとマッチするインデックスをリスト形式で返す関数です。

hoxos <- c("hoxo_m", "hozo_m", "hoxo_b")
gregexpr("(hoxo)_(m|w|b|x)", hoxos)
#> [[1]]
#> [1] 1
#> attr(,"match.length")
#> [1] 6
#> attr(,"useBytes")
#> [1] TRUE
#> 
#> [[2]]
#> [1] -1
#> attr(,"match.length")
#> [1] -1
#> attr(,"useBytes")
#> [1] TRUE
#> 
#> [[3]]
#> [1] 1
#> attr(,"match.length")
#> [1] 6
#> attr(,"useBytes")
#> [1] TRUE

regexpr()は同じ結果をベクトル形式で返します。

regexpr("(hoxo)_(m|w|b|x)", hoxos)
#> [1]  1 -1  1
#> attr(,"match.length")
#> [1]  6 -1  6
#> attr(,"useBytes")
#> [1] TRUE

このインデックスを元の文字列とともにregmatches()に渡すと、マッチした部分をそれぞれリスト形式、ベクトル形式で取り出してくれます。

m <- gregexpr("(hoxo)_(m|w|b|x)", hoxos)
regmatches(hoxos, m)
#> [[1]]
#> [1] "hoxo_m"
#> 
#> [[2]]
#> character(0)
#> 
#> [[3]]
#> [1] "hoxo_b"
#> 
m <- regexpr("(hoxo)_(m|w|b|x)", hoxos)
regmatches(hoxos, m)
#> [1] "hoxo_m" "hoxo_b"

で、regmatches<-ですが、これはマッチした文字列を置き換えてくれます。

hoxos <- c("hoxo_m", "hozo_m", "hoxo_b")
m <- regexpr("(hoxo)_(m|w|b|x)", hoxos)
regmatches(hoxos, m) <- c("<anonymous>", "<anonymous>")
hoxos
#> [1] "<anonymous>" "hozo_m"      "<anonymous>"

これは、正規表現にマッチした文字列になにか処理を加えて元の文字列に戻したい、みたいなときに便利です。たとえば、マッチしたやつだけを大文字に置き換えてみるにはこんな風にします。これがformatRの中でやっていたことです。

hoxos <- c("This is hoxo_m", "This is hozo_m", "This is hoxo_b")
m <- regexpr("(hoxo)_(m|w|b|x)", hoxos)
regmatches(hoxos, m) <- purrr::map_chr(regmatches(hoxos, m), toupper)
hoxos
#> [1] "This is HOXO_M" "This is hozo_m" "This is HOXO_B"

ちなみに、regexec()というのもあります。これはグループも含めたマッチの結果を出してくれます。

hoxos <- c("This is hoxo_m", "This is hozo_m", "This is hoxo_b")
m <- regexec("(hoxo)_(m|w|b|x)", hoxos)
m
#> [[1]]
#> [1]  9  9 14
#> attr(,"match.length")
#> [1] 6 4 1
#> attr(,"useBytes")
#> [1] TRUE
#> 
#> [[2]]
#> [1] -1
#> attr(,"match.length")
#> [1] -1
#> attr(,"useBytes")
#> [1] TRUE
#> 
#> [[3]]
#> [1]  9  9 14
#> attr(,"match.length")
#> [1] 6 4 1
#> attr(,"useBytes")
#> [1] TRUE
regmatches(hoxos, m)
#> [[1]]
#> [1] "hoxo_m" "hoxo"   "m"     
#> 
#> [[2]]
#> character(0)
#> 
#> [[3]]
#> [1] "hoxo_b" "hoxo"   "b"

ということでめっちゃ便利っぽいんですが、regmatches<-には使えません。なので、マッチ全体ではなく一部のグループだけ置き換える、みたいなことはできません。

regmatches(hoxos, m) <- regmatches(hoxos, m)
#> Error in (function (u, so, ml)  : 
#>    ‘invert = TRUE’ に対して重複のないマッチが必要です

こういうのstringrとかstringiでやる方法あるんでしょうか? ご存知の方は教えてください…(str_sub<-はあるけど、こういう「何か処理を加えてから戻す」ということはできない)