この正規表現はどのようにして三角数を見つけるのでしょうか? 質問する

この正規表現はどのようにして三角数を見つけるのでしょうか? 質問する

教育的な正規表現記事シリーズの一部であるこの記事は、ネストされた参照の概念をわかりやすく紹介しています。

最初の数三角数は:

 1 = 1
 3 = 1 + 2
 6 = 1 + 2 + 3
10 = 1 + 2 + 3 + 4
15 = 1 + 2 + 3 + 4 + 5

数字が三角であるかどうかを確認する方法はたくさんあります。次のように正規表現を使用する興味深い手法があります。

  • 与えられたまず長さの文字列を作成します同じ文字で埋められた
  • 次にこの文字列をパターンと照合します^(\1.|^.)+$
    • このパターンが文字列に一致する場合のみ三角形となる

これが複数の言語で機能することを示すスニペットをいくつか示します。

PHP (ideone.com 上)

$r = '/^(\1.|^.)+$/';

foreach (range(0,50) as $n) {
  if (preg_match($r, str_repeat('o', $n))) {
     print("$n ");
  }
}

Java (ideone.com 上)

for (int n = 0; n <= 50; n++) {
    String s = new String(new char[n]);
    if (s.matches("(\\1.|^.)+")) {
        System.out.print(n + " ");
    }
}

C# (ideone.com 上)

Regex r = new Regex(@"^(\1.|^.)+$");

for (int n = 0; n <= 50; n++) {
    if (r.IsMatch("".PadLeft(n))) {
       Console.Write("{0} ", n);
    }
}

この正規表現は機能しているようですが、どのように機能するのかを誰か説明できますか?

類似の質問

ベストアンサー1

説明

パターンの概略は次のとおりです。

from beginning…
|         …to end
|         |
^(\1.|^.)+$
 \______/|___match
  group 1    one-or-more times

(…) 括弧キャプチャグループ1を定義し、このグループは繰り返しマッチした+。このサブパターンは固定されたと使用して^$文字列全体が一致するかどうかを確認します。

グループ1はマッチを試みるthis|that 交代:

  • \1.つまり、グループ1が一致したもの(自己参照!)に、「任意の」文字
  • または^.、先頭の「任意の」1文字だけ

グループ1には、グループ1が一致したものへの参照があることに注目してください。これはネストされた/自己参照であり、この例で紹介されている主なアイデアです。キャプチャグループが繰り返される場合、一般的に最後のキャプチャのみ保存されますしたがって、この場合の自己参照は本質的に次のことを意味しています。

「前回マッチしたものに、さらにもう 1 つ加えてマッチさせてください。それが今回マッチさせるものです。」

再帰と同様に、自己参照を持つ「基本ケース」が必要です。 の最初の反復では+、グループ1はまだ何もキャプチャしていませんでした(これはないこれは、空の文字列で始まるということと同じです。したがって、グループ 1 を「初期化」する方法として、2 番目の代替が導入され、文字列の先頭にある 1 つの文字をキャプチャできるようになります。

したがって、これを繰り返すと+、グループ 1 は最初に 1 文字を一致させようとし、次に 2 文字、3 文字、4 文字というように一致させようとします。これらの数字の合計は三角数です。


さらなる探究

簡略化のため、入力と同じ繰り返し文字で構成される文字列を使用していることに注意してください。このパターンの仕組みがわかったので、このパターンは"1121231234"、、"aababc"などの文字列にも一致することがわかります。

また、は三角数である。n = 1 + 2 + … + k、グループ1が最後にキャプチャした文字列の長さは

これらの両方のポイントは、次の C# スニペットに示されています (ideone.comでも見られます):

Regex r = new Regex(@"^(\1.|^.)+$");

Console.WriteLine(r.IsMatch("aababc"));     // True
Console.WriteLine(r.IsMatch("1121231234")); // True
Console.WriteLine(r.IsMatch("iLoveRegEx")); // False

for (int n = 0; n <= 50; n++) {
    Match m = r.Match("".PadLeft(n));
    if (m.Success) {
       Console.WriteLine("{0} = sum(1..{1})", n, m.Groups[1].Length);
    }
}
// 1 = sum(1..1)
// 3 = sum(1..2)
// 6 = sum(1..3)
// 10 = sum(1..4)
// 15 = sum(1..5)
// 21 = sum(1..6)
// 28 = sum(1..7)
// 36 = sum(1..8)
// 45 = sum(1..9)

フレーバーノート

すべてのフレーバーがネストされた参照をサポートしているわけではありません。味の癖作業している正規表現の種類 (したがって、正規表現関連の質問をするときは常にこの情報を提供すると役立ちます)。

ほとんどのフレーバーでは、標準的な正規表現のマッチングメカニズムは、パターンが一致するかどうかを確認しようとします。一部入力文字列の (場合によっては入力全体) です。つまり、必要な場合は常にパターンを と でアンカーすることを忘れないで^ください$

Javaは少し違うString.matchesPattern.matchesそしてMatcher.matchesパターンを一致させようとする全体入力文字列。これが、上記のスニペットでアンカーを省略できる理由です。

\A他のコンテキストでは、代わりにとアンカーを使用する必要があることに注意してください\Z。たとえば、複数行モードの始まりと終わりを一致させ^ます$各行入力時に。

最後に、.NET正規表現では、できる実際には、繰り返しキャプチャ グループによって行われたすべての中間キャプチャを取得します。ほとんどのフレーバーでは、これはできません。すべての中間キャプチャは失われ、最後のキャプチャのみが保持されます。

関連する質問


ボーナス資料: 正規表現を使用して 2 の累乗を見つける!!!

ほんの少し変更するだけで、ここで紹介したのと同じ手法を使用して 2 の累乗を求めることができます。

活用したい基本的な数学的特性は次のとおりです。

  • 1 = 1
  • 2 = (1) + 1
  • 4 = (1+2) + 1
  • 8 = (1+2+4) + 1
  • 16 = (1+2+4+8) + 1
  • 32 = (1+2+4+8+16) + 1

解決策は以下に記載されています (ただし、まずは自分で解決してみてください!!!!)

(ideone.comで参照)PHP のジャワ、 そしてC#):^(\1\1|^.)*.$

おすすめ記事