ぷろぐらみんぐ帳

C#とかJavaScriptとか

任意の文字の繰り返し回数を検出する

特定の文字列の繰り返しを検出したいなら正規表現を使うのが王道だが、繰り返す文字列が任意だと一筋縄にはいかないことがある。例えば、

hogehogeeeeehoggggggge

という文字列があったとして、2文字以上連続した箇所とその文字と繰り返し回数を検出したい場合(この例では「eeeee」と「ggggggg」)。また、繰り返される文字列はわからない(ここでは簡略化のためにa-zとするが)ものとする。

ダメな例

まずダメな例から。

var text = "hogehogeeeeehoggggggge";
var matches = text.match(/([a-z]){2,}/g);

document.write(matches);//hogehogeeeeehoggggggge 

一見うまくいきそうだが、[a-z]にテキストの箇所以外の文字列が含まれているため全体がマッチしてしまう。これではダメ。

うまくいく一例

後方参照の「\1」を使うとうまくいく。

\n n には正の整数が入ります。正規表現内で (左括弧を数えて) n 番目の括弧でくくられた部分に該当する部分文字列を後方参照します。

例えば、/apple(,)\sorange\1/ は “apple, orange, cherry, peach” の ‘apple, orange,’ にマッチします。具体的なサンプルをこの表の後に掲載しています

RegExp - JavaScript | MDN

var text = "hogehogeeeeehoggggggge";
var matches = text.match(/([a-z])\1+/g);

document.write(matches);//eeeee,ggggggg  

2回以上繰り返しなので「\1+」、3回以上繰り返しなら「\1\1+」とする。ひたすら\1を繰り返すのは頭悪いので、後方参照と繰り返しのと{}を組み合わせてもOK。例えば、「([a-z])\1{5,}」なら6回以上繰り返しとなり、「eeeee」は引っかからなくなる。

上限が必要な場合は、例えば{1,5}で繰り返し2回以上6回以下のみになり、結果は「eeeee, gggggg」となる。厳密にg×7は除外されないので、繰り返しの上限が必要な場合はもうひと工夫いりそうだ。

replaceの引数に関数指定

繰り返し回数はどう判定すべきだろうか?この例だとマッチした箇所の文字数を数えれば終わりだが、キャプチャする文字数が可変の場合は困ったことになる。一例ではあるが、string.replaceの引数に関数を指定する例が使えそうだ。

String.prototype.replace() - JavaScript | MDN

var text = "hogehogeeeeehoggggggge";
var replace = text.replace(/([a-z])\1+/g, function (match, p1, offset, string) {
    document.writeln("match : " + match + ", p1 : " + p1 + ", offset : " + offset + ", string : " + string);
    return match;//何もしない置き換え
})
document.writeln(replace);
//match : eeeee, p1 : e, offset : 7, string : hogehogeeeeehoggggggge 
//match : ggggggg, p1 : g, offset : 14, string : hogehogeeeeehoggggggge 
//hogehogeeeeehoggggggge

メソッドの引数の仕様は上記リンクを参照。「return match」とすることで、置き換えメソッドであるが、実質何もしないメソッドとすることが可能である。

replaceを使用した簡単な文字列圧縮・解凍

限定した用途であるが、aaa→a3のように繰り返し回数を数字で置き換えれば、文字列の簡易圧縮・解凍メソッドを作ることができる。

var originalStr = "hogehogeeeeehoggggggge";

//圧縮
function deflate(str) {
    return str.replace(/([a-z])\1+/g, function (match, p1) {
        var cnt = match.length / p1.length;
        if (match.length % 1 == 0) return p1 + cnt;
        else return match;
    })
}
//解凍
function inflate(str) {
    return str.replace(/([a-z])(\d+)/g, function (match, p1, p2) {
        return Array(Number(p2) + 1).join(p1);
    })
}

var str1 = deflate(originalStr);
document.writeln(str1);//hogehoge5hog7e
var str2 = inflate(str1);
document.writeln(str2);//hogehogeeeeehoggggggge 

もしかしたらどこかで使えるかもしれない…?