uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

君ならどう書く? s///ge

Perlとかでは、文字列x中の正規表現にマッチする部分を置換するときには

 x =~ s/正規表現パターン/置換後の文字列/g;

の様に書きます。Javaでは同様のことを

 x = x.replaceAll("正規表現パターン", "置換後の文字列");

と書けます。このとき、置換後の文字列中で、$1, $2といった記法により、パターン中の部分一致した文字列を参照できるので、それなりに強力で便利です。しかし、$1,$2に対してなんらかの加工を行い、その処理結果で置換したい、という場合にはJavaでは相当に面倒になります。たとえばPerl

 x =~ s/正規表現パターン/置換後文字列として任意の式をここに書ける/eg;

の様に簡潔にかけるのに、Javaでは

 Pattern p = Pattern.compile("正規表現パターン");
 Matcher m = p.matcher(x);
 StringBuffer sb = new StringBuffer();
 while (m.find()) {
     m.appendReplacement(sb, 置換後文字列として任意の式をここに書ける)
 }
 m.appendTail(sb);
 x = sb.toString();

のようになります。ちょっとどころではなくうんざりです。上をそのままGroovyで書き下したとしても相当面倒です。

しかし、Groovy JDKで拡張されたString#replaceAll(String, Closure)メソッドを使用すれば以下の様に簡単に書けます。

 x = x.replaceAll(/正規表現パターン/) { 置換後文字列として任意の式をここに書ける }

部分マッチを参照するには以下の様にクロージャが引数をとるようにします。

 "one cat two cats in the yard".replaceAll(/c(a)t/) { m0, m1 -> "d${m1}ck" }

m1が1番目の部分マッチを参照しており($1相当)、m0はマッチした文字列全体です($0相当)。

  x = x.replaceAll(..);

のようにxに改めて置換後の結果を代入しているのは、Stringはイミュータブルだからですね。

便利!

ちなみにRubyだと

  x.gsub!(/正規表現パターン/) { | m0 | 置換後文字列として任意の式をここに書ける }

こうかな(試していません)。GroovyのreplaceAllはRubyのString#gsubとほぼ同様ですが、$0以外の部分マッチ文字列である$1,$2をクロージャ引数で取るところが違います。もともとGroovyでは$1,$2のような「暗黙マッチ結果」の参照の方法が無いわけじゃないですけど、長い(Matcher.getLastMatcher().group(n))ですからクロージャ引数を使う上のような方法がすっきりしますね。

参考:
http://groovy.codehaus.org/groovy-jdk.html#meth311