Regular expressions

正規表現は、文字列のパターンを記述するための簡潔かつ柔軟なツールです。この vignette は、stringi によって実装されたstringrの正規表現の主要な機能を説明しています。チュートリアルではないので、もし正規表現に馴染みがなければ、 http://r4ds.had.co.nz/strings.html から始めることをお勧めします。もし、詳細をマスターしたいのであれば、Jeffrey E. F. Friedl による古典的な Mastering Regular Expressions を読むことをお勧めします。

正規表現は stringr のデフォルトのパターン・エンジンです。つまり、パターンマッチの関数をそのままの文字列で使う場合、regex() の呼び出しでラップするのと同じです。

# The regular call:
str_extract(fruit, "nana")
# Is shorthand for
str_extract(fruit, regex("nana"))

以下の例で見るように、デフォルトのオプションを上書きしたい場合は、 regex() を明示的に使用する必要があります。

基本的なマッチ

最も単純なパターンは、正確な文字列にマッチします。

x <- c("apple", "banana", "pear")
str_extract(x, "an")
#> [1] NA   "an" NA

ignore_case = TRUE を指定すると、大文字と小文字を区別しないマッチングを行うことができます。

bananas <- c("banana", "Banana", "BANANA")
str_detect(bananas, "banana")
#> [1]  TRUE FALSE FALSE
str_detect(bananas, regex("banana", ignore_case = TRUE))
#> [1] TRUE TRUE TRUE

次に複雑なのは . で、これは改行以外のあらゆる文字にマッチします。

str_extract(x, ".a.")
#> [1] NA    "ban" "ear"

dotall = TRUE と設定することで、.\n を含むすべてにマッチするようにすることができます。

str_detect("\nX\n", ".X.")
#> [1] FALSE
str_detect("\nX\n", regex(".X.", dotall = TRUE))
#> [1] TRUE

エスケープ

もし「.」が任意の文字にマッチするなら、「.」そのものにはどうしたらマッチするのでしょうか?「エスケープ」を使って、正規表現の特殊な振る舞いを使わず、正確にマッチさせたいことを伝える必要があります。文字列と同様に、正規表現もバックスラッシュ \ を使って特殊な挙動をエスケープします。つまり、.にマッチするには、正規表現 \. が必要です。残念ながら、これは問題を引き起こします。正規表現を表現するために文字列を使いますが、文字列の中で \ はエスケープシンボルとしても使われます。だから、正規表現 \. を作るには、文字列 "\\." が必要となります。

# To create the regular expression, we need \\
dot <- "\\."

# But the expression itself only contains one:
writeLines(dot)
#> \.
# And this tells R to look for an explicit .
str_extract(c("abc", "a.c", "bef"), "a\\.c")
#> [1] NA    "a.c" NA

正規表現で \ がエスケープ文字として使われる場合、\ そのもの にはどのようにマッチングするのでしょうか?正規表現を作るためにエスケープする必要がある。その正規表現を作るには、文字列を使用する必要があり、その文字列もまた \ をエスケープする必要がある。つまり、\ そのものにマッチするためには、"\\\\"と書く必要があります。バックスラッシュが4つとなります。

x <- "a\\b"
writeLines(x)
#> a\b
str_extract(x, "\\\\")
#> [1] "\\"

この vignette では、正規表現を表す文字列に \. を、正規表現を表す文字列に "\\." を使用します。

別の引用符の付け方は \Q...\E で、... に含まれるすべての文字が完全一致として扱われます。これは、正規表現の一部としてユーザー入力に完全に一致させたい場合に便利です。

x <- c("a.b.c.d", "aeb")
starts_with <- "a.b"

str_detect(x, paste0("^", starts_with))
#> [1] TRUE TRUE
str_detect(x, paste0("^\\Q", starts_with, "\\E"))
#> [1]  TRUE FALSE

特殊文字

また、エスケープを使うことで、入力しにくい文字を個別に指定することができます。個々の Unicode 文字を指定するには、16進数で指定する方法(4桁が最も一般的)と、名前で指定する方法があります。

同様に、多くの一般的な制御文字を指定することができます。

(これらの多くは歴史的な興味しかないため、ここでは完全性を期すためにのみ記載しています。)

複数の文字にマッチする

複数の文字にマッチするパターンも数多く存在します。すでに見たことがあるかもしれませんが、 . は任意の文字にマッチします (改行は除く)。これは grapheme cluster 、つまり、1つのシンボルを形成する個々の要素の集合にマッチします。例えば、“á” は “a” とアクセント記号で表現されます。. は “a” にマッチし、\X は記号全体にマッチします。

x <- "a\u0301"
str_extract(x, ".")
#> [1] "a"
str_extract(x, "\\X")
#> [1] "á"

その他に、より狭いクラスの文字にマッチする5つのエスケープペアがあります。

また、[] を使って独自の 文字クラス を作成することもできます。

[] 内で使うことができるビルド済みクラスもたくさんあります。

これらはすべて、文字クラスを表す [] の中に入ります。例えば、 [[:digit:]AX] は、すべての数字、A、Xにマッチします。

また、Unicode のプロパティ、例えば [\p{Letter}] や集合演算、例えば [\p{Letter}--p{script=latin}] も使用できます。詳しくは ?"stringi-search-charclass" を参照してください。

オルタネート

|alternation 演算子で、1 つ以上のマッチの中から選択します。例えば、 abc|defabc または def にマッチします。

str_detect(c("abc", "def", "ghi"), "abc|def")
#> [1]  TRUE  TRUE FALSE

abc|defabcdef にマッチしますが、 abcyzabxyz にはマッチしないことに注意してください。

(訳注: abcyz にはマッチします。開発者用最新版では、この部分が削除されているようです。)

グループ化

括弧を使用すると、デフォルトの優先順位の規則を上書きすることができます。

str_extract(c("grey", "gray"), "gre|ay")
#> [1] "gre" "ay"
str_extract(c("grey", "gray"), "gr(e|a)y")
#> [1] "grey" "gray"

また、カッコは backreferences で参照できる「グループ」を定義し、\1\2 など、str_match() で抽出することができます。例えば、以下の正規表現は、繰り返される文字の組を持つすべての果物を見つけます。

pattern <- "(..)\\1"
fruit %>% 
  str_subset(pattern)
#> [1] "banana"      "coconut"     "cucumber"    "jujube"      "papaya"     
#> [6] "salal berry"
fruit %>% 
  str_subset(pattern) %>% 
  str_match(pattern)
#>      [,1]   [,2]
#> [1,] "anan" "an"
#> [2,] "coco" "co"
#> [3,] "cucu" "cu"
#> [4,] "juju" "ju"
#> [5,] "papa" "pa"
#> [6,] "alal" "al"

グループ化しない括弧である (?:...) を使用すると、優先順位を制御することはできますが、グループ内のマッチを捕まえることはできません。これはキャプチャする括弧よりも若干効率的です。

str_match(c("grey", "gray"), "gr(e|a)y")
#>      [,1]   [,2]
#> [1,] "grey" "e" 
#> [2,] "gray" "a"
str_match(c("grey", "gray"), "gr(?:e|a)y")
#>      [,1]  
#> [1,] "grey"
#> [2,] "gray"

これは、マッチの捕捉と優先順位の制御を個別に行う必要がある、より複雑なケースで最も有用です。

アンカー

デフォルトでは、正規表現は文字列の任意の部分にマッチします。文字列の先頭か末尾にマッチするように正規表現を anchor するのが便利なことがよくあります。

x <- c("apple", "banana", "pear")
str_extract(x, "^a")
#> [1] "a" NA  NA
str_extract(x, "a$")
#> [1] NA  "a" NA

“$” や “^” そのものにマッチするには、それらをエスケープする必要があり、 \$\^ となります。

複数行の文字列には、 regex(multiline = TRUE) を使用することができます。これは、 ^$ の動作を変更し、3つの新しい演算子を導入しています。

x <- "Line 1\nLine 2\nLine 3\n"
str_extract_all(x, "^Line..")[[1]]
#> [1] "Line 1"
str_extract_all(x, regex("^Line..", multiline = TRUE))[[1]]
#> [1] "Line 1" "Line 2" "Line 3"
str_extract_all(x, regex("\\ALine..", multiline = TRUE))[[1]]
#> [1] "Line 1"

繰り返し

繰り返し演算子を使って、パターンにマッチする回数を制御することができます。

x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
str_extract(x, "CC?")
#> [1] "CC"
str_extract(x, "CC+")
#> [1] "CCC"
str_extract(x, 'C[LX]+')
#> [1] "CLXXX"

なお、これらの演算子の優先順位は高いので、次のように書くことができます。colou?r と書くと、アメリカでもイギリスでも同じ綴りになります。つまり、ほとんどの場合、 bana(na)+ のように括弧が必要になります。

また、マッチする数を正確に指定することもできます。

str_extract(x, "C{2}")
#> [1] "CC"
str_extract(x, "C{2,}")
#> [1] "CCC"
str_extract(x, "C{2,3}")
#> [1] "CCC"

デフォルトでは、これらのマッチは「欲張り (greedy)」で、可能な限り長い文字列にマッチします。これらのマッチの後に ? をつけると、可能な限り短い文字列にマッチする「怠惰な (lazy, 訳注 reluctant とも言う)」マッチになります。

str_extract(x, c("C{2,3}", "C{2,3}?"))
#> [1] "CCC" "CC"
str_extract(x, c("C[LX]+", "C[LX]+?"))
#> [1] "CLXXX" "CL"

また、マッチの後に + を付けることで「独占的 (possessive, 絶対などとも訳されますが、訳注 良い訳がないようです)」にすることができます。これは、マッチの後の部分が失敗しても、その繰り返しがより少ない文字数で再試行されないことを意味します。これは、最悪のシナリオ(「壊滅的バックトラック」と呼ばれる)においてパフォーマンスを向上させるために使われる高度な機能です。

関連する概念として、 atomic-match の括弧、 (?>...) があります。後のマッチが失敗し、エンジンがバックトラックを必要とする場合、アトミックマッチはそのまま維持されます:全体として成功または失敗します。次の二つの正規表現を比べてみてください。

str_detect("ABC", "(?>A|.B)C")
#> [1] FALSE
str_detect("ABC", "(?:A|.B)C")
#> [1] TRUE

アトミックマッチは A にマッチして失敗し、次の文字が C なので失敗します。正規のマッチは A にマッチするので成功しますが、C にマッチしないので、バックトラックして代わりに B を試行します。

ルックアラウンド

これらのアサーションは、文字を「消費」することなく、現在のマッチの前方または後方を検索します (つまり、入力位置を変更します)。

これらは、あるパターンが存在することを確認したいが、そのパターンを結果に含めたくない場合に便利です。

x <- c("1 piece", "2 pieces", "3")
str_extract(x, "\\d+(?= pieces?)")
#> [1] "1" "2" NA
y <- c("100", "$400")
str_extract(y, "(?<=\\$)\\d+")
#> [1] NA    "400"

コメント

正規表現にコメントを含めるには、2つの方法があります。一つ目は (?#...) を使う方法です。

str_detect("xyz", "x(?#this is a comment)")
#> [1] TRUE

もう一つは、regex(comments = TRUE) を使用する方法です。この形式では、スペースや改行は無視され、 # 以降はすべて無視されます。スペースにマッチさせるには、 "\\ " とエスケープする必要があります。これは複雑な正規表現を記述するのに便利な方法です。

phone <- regex("
  \\(?     # 最初のカッコ
  (\\d{3}) # 市外局番
  [)- ]?   # カッコ閉じ、あるいはハイフンまたは空白
  (\\d{3}) # 数字3つ
  [ -]?    # 空白またはハイフン
  (\\d{3}) # 数字3つ
  ", comments = TRUE)

str_match("514-791-8141", phone)
#>      [,1]          [,2]  [,3]  [,4] 
#> [1,] "514-791-814" "514" "791" "814"