パターンを含まない部分否定の正規表現

パターンで始まらない文字かチェックする場合

否定先読み

あるパターンを含まない正規表現は、否定先読み?!を使う。

否定先読みは、パターンの開始位置から前を読んでパターンがマッチしなければtrueになる。

パターンで始まらない正規表現

パターンで始まらないかチェックする場合、否定先読みを使って^(?!パターン)と書く。

つまり、行頭(=パターンの開始位置の前)の次にパターンがマッチしなければtrueになる。

例えばa-zで始まらない文字をチェックする場合は以下の通り。

jshell> Pattern p = Pattern.compile("^(?![a-z]+)")
p ==> ^(?![a-z]+)

jshell> p.matcher("abc").find()
$2 ==> false

jshell> p.matcher("abc123").find()
$3 ==> false

jshell> p.matcher("123abc").find()
$4 ==> true

find vs matches

Javaのmatchesは全体がマッチしているかどうかをチェックする。^, $を書かなくても書いたのと同じ挙動になってしまうため、今回の例示にはわかりづらいので使用しない。

# findだと^を書いていないと正しい結果が得られない
jshell> Pattern.compile("(?![a-z]+)").matcher("abc").find()
$1 ==> true

# matchesだと^を書いていないのに正しく動作する
jshell> "123abc".matches("(?![a-z]+).*")
$2 ==> true

# matchesだと全体がマッチしているかを確認されるので、
# 逆に.*を末尾につけないと空文字にしかマッチしなくなってしまう
jshell> "123abc".matches("(?![a-z]+)")
$3ß ==> false

jshell> "".matches("(?![a-z]+)")
$4 ==> true

パターンを含まない文字かチェックする場合

パターンを含まないかチェックする場合は、パターンの前半に.*を含めばよく、^(?!.*パターン)となる。

jshell> Pattern p = Pattern.compile("^(?!.*[a-z]+)")
p ==> ^(?!.*[a-z]+)

jshell> p.matcher("abc").find()
$2 ==> false

jshell> p.matcher("abc123").find()
$3 ==> false

jshell> p.matcher("123abc").find()
$4 ==> false

jshell> p.matcher("123").find()
$5 ==> true

パターンで終わらない文字かチェックする場合

パターンで終わらないかチェックする場合は、否定先読みでもできるが、否定後読み?<!の方がシンプルになる。

否定後読み

否定先読みとの相違点は、パターンの前を読むか後を読むかの違いで、パターンの開始位置からを読んでパターンがマッチしなければtrueになる。

jshell> Pattern p = Pattern.compile("(?<![a-z]+)$")
p ==> (?<![a-z]+)$

jshell> p.matcher("abc").find()
$2 ==> false

jshell> p.matcher("abc123").find()
$3 ==> true

jshell> p.matcher("123abc").find()
$4 ==> false

パターンで終わらないかどうかをチェックするにはパターンの開始位置の後に$がくるかどうかがカギになるため、否定後読みで実装するのがシンプルになる。

否定先読みでの実装

完全否定の正規表現につながるので、あえて否定先読みでも実装してみる。カギになるポイントは同じくパターンと$が隣り合っているかどうかであるため、パターン自体に$を含めてしまう。

jshell> Pattern p = Pattern.compile("^(?!.*[a-z]+$)")
p ==> ^(?!.*[a-z]+$)

jshell> p.matcher("abc").find()
$2 ==> false

jshell> p.matcher("abc123").find()
$3 ==> true

jshell> p.matcher("123abc").find()
$4 ==> false

パターン$を含まない文字がtrueとなる。

パターンと一致しない完全否定の正規表現

完全否定の正規表現にする時も否定先読みを使用する。

パターンで始まらない場合とパターンで終わらない場合を合わせたもの(つまり、パターンで始まらないし終わらない)がパターンと一致しない完全否定になる。文中に含むだけなら問題ない。

jshell> Pattern p = Pattern.compile("^(?![a-z]+$)")
p ==> ^(?![a-z]+$)

jshell> p.matcher("abc").find()
$2 ==> false

jshell> p.matcher("abc123").find()
$3 ==> true

jshell> p.matcher("123abc").find()
$4 ==> true

行頭の次にパターンと行末がくることを否定している。