Processing で正規表現を使った文字列抽出を行ってみる。
Processing プログラミング
正規表現を初めて使ってみようと思った人が感覚を掴みやすそうな技術ブログを見つけられなかったので、Processing 言語を使って正規表現を使ってみる方法を要所だけにざっくりと絞って、順を追って紹介してみます。
そのライブコーディングで使われていたプログラミング言語が何かははっきりとはわからないのですけれど、 配信の中で Java という言葉が出てきたのと、もしかすると Processing でコーディングしていそうな様子だったので、 今回は Processing を使って正規表現を使った文字列処理を順を追ってざっくり行ってみます。
Processing での正規表現
正規表現を使うことで、文字列が特定のパターンにマッチするかを判定したり、特定のパターンに該当する文字列を自由に切り出したりできます。
Java 言語の場合は Pattern
クラスで正規表現を使っていくことになるようなのですけれど、
Processing では match
関数や matchAll
関数を使って正規表現を行えるようになっているようです。
扱う文字列データの準備
今回は次のような文字列を、正規表現で取り扱ってみようと思います。
この文字列の構造としては、行ごとにパラメーターが記載されています。
そして各行は、アルファベットで構成されたキー名と、空白を含まない任意の文字で構成された値とが :
で区切られています。:
と値の間には、1文字以上の空白文字が挟まれます。
また、空行を挟むことで複数のデータブロックを表現します。
id: 1000
name: star
option: none
count: 500
id: 1001
name: moon
option: full
count: 1
このテキストデータを、今回は次のようにして変数 text
に格納しておくことにします。
本題とは関係ないですけれど、今回はコードを見たときにデータブロックが多少は区別しやすいように +=
演算子を使ってブロック単位で追加する形で記述しておくことにします。
String text = "";
text += "id: 1000\nname: star\noption: none\ncount: 500\n";
text += "\n";
text += "id: 1001\nname: moon\noption: full\ncount: 1\n";
text += "\n";
match
関数を使う
Processing で正規表現を行うときには match
関数を使います。
この関数は、第1引数で指定した文字列の中に、第2引数で指定したパターンが含まれているかを判定します。
そして、含まれていればそのパターンにマッチした文字列を String[]
型で返し、
含まれていなかった場合は null
を返します。
なお、パターンにマッチした場合は String
型の配列で結果が返りますけれど、
配列の先頭要素に「パターンにマッチした文字列全体」が、それ以降には「サブパターンにマッチした文字列」が順番に格納されるようになっています。
まずはこれを使って "name"
という文字列が変数 text
に含まれているかを判定してみます。指定した文字列が含まれていることは、戻り値が null
ではないことを
調べればわかります。
if (match(text, "name") != null) {
println("含まれています。");
}
else {
println("含まれていません。");
}
パターンにマッチした文字列を取り出す
パターンにマッチした文字列は、戻り値の最初の要素を参照することで取得できます。
String[] matchedTexts = match(text, "name");
if (matchedTexts != null) {
println(matchedTexts[0]);
}
今回の例であれば、変数 text
の中に "name"
という文字列が含まれているので、
match
関数のパターンとして指定した "name"
はマッチします。そしてそのパターンにマッチした部分が、戻り値で受け取った配列の最初の要素、すなわち matchedTexts[0]
で得られます。
今回の場合はパターンが純粋な文字列なので、得られる文字列は次の通り、指定したパターンそのものです。
name
name
キーが現れる行を切り出す
正規表現が威力を発揮するのは、特殊文字を使ってパターンを記述する場面です。
たとえば、キーに name
が現れる行全体にマッチさせたかったとします。
Processing では、文字列を改行で区切られた行単位でパターンを判定するので、パターン "^name:.*?$"
で判定できます。ここで使われている特殊文字は次の通りです。
特殊文字 | 該当するもの |
---|---|
^ |
行の先頭 |
. |
任意の文字 |
*? |
直前で指定した文字が 0 個以上で、パターンを満たす中で最も短いもの |
$ |
行の末尾 |
つまり、今回のパターンは「行の先頭から "name:"
で始まり、任意の文字が 0 個以上続いて、行末まで」という正規表現になります。
このパターンで match
関数を使うと、該当する行全体が matchedTexts[0]
で得られます。
String[] matchedTexts = match(text, "^name:.*?$");
if (matchedTexts != null) {
println(matchedTexts[0]);
}
つまり、今回の例では次の結果が得られます。
name: star
name
キーに対応する値を切り出す
サブパターンを使うと、パターンにマッチした文字列のうちの任意の部分を取得できます。
たとえば "name:"
の先から行末までを取得したい場合は、該当する部分を特殊文字 "("
と ")"
で括ったサブパターンを使います。
ただ、今回の文字列では :
の直後に空白文字が入るので、特殊文字 \s
を使ってそれを表現し、
それを除いた部分をサブパターンで指定することにします。
そうして作った正規表現のパターンは "^name:\\s*(.*?
$") です。ここで "\s"
のところが "\\s"
と表現されているのは、Processing では文字列内の "\"
が特別な意味として解釈されてしまうので、"\\"
として単なる文字の "\"
として扱われるようにしています。
今回の正規表現で新しく出てきた特殊文字は次の通りです。
特殊文字 | 該当するもの |
---|---|
\s |
空白文字(空白やタブ、改行) |
* |
直前で指定した文字が 0 個以上で、パターンを満たす中で最も長いもの |
( |
サブパターンの開始位置 |
) |
サブパターンの終了位置 |
つまり、今回のパターンは「行の先頭から "name:"
で始まり、空白文字が 0 個以上続いた後からサブパターンが始まって行末の直前で終わり、サブパターン内は、任意の文字が 0 個以上続く」という正規表現になります。
このパターンで match
関数を使うと、該当する行全体が matchedTexts[0]
で得られると同時に、サブパターンに該当する部分が matchedTexts[1]
で得られます。
String[] matchedTexts = match(text, "^name:\\s*(.*?)$");
if (matchedTexts != null) {
println(matchedTexts[0]);
println(matchedTexts[1]);
}
つまり、今回の例では次の結果が得られます。
name: star star
複数箇所のマッチする部分を取得する
これまでの例では、最初にマッチした部分だけが取得できていました。ただ、今回の例ではもう1箇所、パターンにマッチする部分があります。
これらも含めて取得するには matchAll
関数を使います。
この関数では、戻り値が二次元配列の String[][]
になっていて、
複数のマッチした部分の文字列を取得できるようになっています。
ひとつもマッチしなかった場合は null
を返します。
マッチした場合は、マッチした部分の文字列が match
関数と同じ String[]
型で得られ、それがさらに見つかった順番で配列に格納された二次元配列 String[][]
として得られます。
つまり、matchAll
関数で得られた結果を for
構文で繰り返し処理してあげることで、マッチした全ての部分を処理できます。
String[][] matches = matchAll(text, "^name:\\s*(.*?)$");
if (matches != null) {
for (String[] matchedTexts : matches) {
println(matchedTexts[1]);
}
}
このようなコードを書くと、今回の例では次の結果が得られます。
star moon
さまざまな正規表現が可能
今回の例でも特殊文字がいくつか登場しましたけれど、正規表現にはこれ以外にもさまざまな特殊文字が用意されています。 用意されている特殊文字はプログラミング言語によって僅かに違っていたりしますけれど、Processing で使われる Java 言語であれば標準的な特殊文字が使えると思うので「正規表現 特殊文字」などで検索すると見つかると思います。
たとえば、次のような特殊文字がよく使われて、これらをうまく組み合わせていくとけっこう複雑なパターンを表現できたりします。
特殊文字 | 該当するもの |
---|---|
^ |
行の先頭 |
$ |
行の末尾 |
. |
任意の文字 |
\. |
ピリオドそのもの(特殊文字の手前に \ を付けるとその文字そのものを表現) |
\s |
空白文字(空白やタブ、改行) |
\d |
数字 |
\w |
英数字 |
[abc] |
文字 a , b , c のどれか |
[a-z] |
文字 a から z までのどれか |
[a-zA-Z] |
大文字と小文字を含むアルファベットのどれか |
[^abc] |
文字 a , b , c 以外のどれか |
* |
直前で指定した文字が 0 個以上で、パターンを満たす中で最も長いもの |
*? |
直前で指定した文字が 0 個以上で、パターンを満たす中で最も短いもの |
+ |
直前で指定した文字が 1 個以上で、パターンを満たす中で最も長いもの |
+? |
直前で指定した文字が 1 個以上で、パターンを満たす中で最も短いもの |
? |
直前で指定した文字が 0 個または 1 個 |
( |
サブパターンの開始位置 |
) |
サブパターンの終了位置 |
| |
この記号の左側で指定したのパターンまたは右側で指定したパターンのどちらか |
他にも「指定した文字列を含まないもの」みたいなパターンを表現する「先読み」という正規表現を表現する特殊文字を使ったりすることもできます。 正規表現は高度な反面難しいところもあるので、特に慣れないうちは正規表現だけに頼ろうとしないで、正規表現で抽出しやすいようにプログラムで文字列を予め整形してから使ったり、正規表現でざっくり取得してから直接的な文字列処理で目的の文字列を抽出していくみたいにしても良いかもしれません。