5

正規表現に一致するすべての (重複している可能性がある) 部分文字列を返す API メソッドはありますか?

たとえば、テキスト string:があり、2 つ以上の文字の文字列に一致String t = 04/31 412-555-1235;する pattern:があります。Pattern p = new Pattern("\\d\\d+");

私が取得した一致は、04、31、412、555、1235 です。

重複する一致を取得するにはどうすればよいですか?

コードが返されるようにしたい: 04, 31, 41, 412, 12, 55, 555, 55, 12, 123, 1235, 23, 235, 35.

理論的には可能O(n^2)です。パターンに対してすべての部分文字列を列挙してチェックする明らかなアルゴリズムがあります。

編集

すべての部分文字列を列挙するよりも、 のregion(int start, int end)メソッドを使用する方が安全Matcherです。別の抽出された部分文字列に対してパターンをチェックすると、一致の結果が変わる可能性があります (たとえば、パターンの開始/終了に非キャプチャ グループまたは単語境界チェックがある場合)。

編集2

実際、region()ゼロ幅の一致に期待することができるかどうかは不明です。仕様は曖昧であり、実験では期待外れの結果が得られます。

例えば:

String line = "xx90xx";
String pat = "\\b90\\b";
System.out.println(Pattern.compile(pat).matcher(line).find()); // prints false
for (int i = 0; i < line.length(); ++i) {
  for (int j = i + 1; j <= line.length(); ++j) {
    Matcher m = Pattern.compile(pat).matcher(line).region(i, j);
    if (m.find() && m.group().size == (j - i)) {
      System.out.println(m.group() + " (" + i + ", " + j + ")"); // prints 90 (2, 4)
    }
  }
}

最もエレガントなソリューションが何であるかはわかりません。1 つのアプローチは、一致lineするかどうかを確認する前に、の部分文字列を取得し、適切な境界文字を埋め込むことです。pat

編集3

これが私が思いついた完全な解決策です。オリジナルの正規表現でゼロ幅のパターンや境界などを扱うことができます。テキスト文字列のすべての部分文字列を調べ、最初と最後に適切な数のワイルドカードをパターンに埋め込むことによって、正規表現が特定の位置でのみ一致するかどうかを確認します。私が試したケースではうまくいくようですが、広範なテストは行っていません。それは間違いなく、それができるよりも効率が悪いです。

  public static void allMatches(String text, String regex)
  {
    for (int i = 0; i < text.length(); ++i) {
      for (int j = i + 1; j <= text.length(); ++j) {
        String positionSpecificPattern = "((?<=^.{"+i+"})("+regex+")(?=.{"+(text.length() - j)+"}$))";
        Matcher m = Pattern.compile(positionSpecificPattern).matcher(text);

        if (m.find()) 
        {   
          System.out.println("Match found: \"" + (m.group()) + "\" at position [" + i + ", " + j + ")");
        }   
      }   
    }   
  }

編集4

これを行うより良い方法は次のとおりです。https://stackoverflow.com/a/11372670/244526

編集5

JRegexライブラリは、Java 正規表現に一致するすべての重複部分文字列の検索をサポートしています (ただし、しばらく更新されていないようです)。具体的には、非互換検索に関するドキュメントでは次のように指定されています。

非破壊検索を使用すると、交差またはネストされたパターンを含む、パターンのすべての可能な発生を見つけることができます。これは、find() の代わりにMatcherのメソッドproceed()を使用することで実現されます

4

3 に答える 3

0

あなたが得ることができる最も近いものは、このようなものです。

"(?=((\\d*)\\d))(?=(\\d)\\d*)"

結果は、グループ 1、2、および 3 のキャプチャになります。

私の想像力の限りでは、文字列の同じ位置を再キャプチャする実行可能な方法として、長さゼロのアサーションでキャプチャすることしか考えられません。ゼロ長アサーションの外側のテキストをキャプチャすると、そのテキストが完全に消費されます (後読みは Java で固定長しかキャプチャできないため、アクセスできないと見なすことができます)。

この解決策は完璧ではありません: 繰り返し (同じ位置にあるテキスト!) と空の文字列の一致を除けば、考えられるすべての部分文字列をキャプチャするわけではありません。

可能なすべての部分文字列を取得する 1 つの方法は、1 から始まる n の値を使用して次の正規表現を作成することです。

"(?=(\\d{" + n + "}))"

そして、一致がなくなるまで n の値をインクリメントするために、これに対して文字列を一致させます。

もちろん、この方法は、すべての数字を "\d+" で一致させてすべての部分文字列を抽出する方法に比べて非効率的です。

于 2012-07-03T03:21:08.310 に答える
0

許容数の長さの範囲を指定する場合にのみ、O(n) として実行できます。

2〜4桁(数字 00〜9999 )から言ってみましょう:(?=(\\d{2}))(?=(\\1\\d)?)(?=(\\2\\d)?)

これは、肯定的な先読みによる長さゼロのアサーションであり、そのような先読みをグループに取り込みます。結果は、正規表現入力内で見つかるすべての 2 ~ 4 桁の文字列の配列であり、重複および空の文字列 (一致しないキャプチャの場合) も含まれます。

私は Java 開発者ではありませんが、Perl スクリプトも例として読むことができると思います。

#!/usr/bin/perl                                       # perl script
use List::MoreUtils qw/ uniq /;                       # uniq subroutine library
$_ = '04/31 412-555-1235';                            # input
my @n = uniq (/(?=(\d{2}))(?=(\1\d)?)(?=(\2\d)?)/g);  # regex (single slash in Perl)
print "$_\n" for grep(/\S/, @n);                      # print non-empty lines

トリックは、後方参照を使用することです。2 ~ 5 桁の文字列をキャプチャする場合は、正規表現でもう 1 つ正の先読みを使用する必要があります: (?=(\\d{2}))(?=(\\1\\d)?)(?=(\\2\\d)?)(?=(\\3\\d)?).

これがあなたができる最も近いアプローチだと思います。これがうまくいく場合は、コメントをドロップしてください。Java 開発者が上記のスクリプトの Java コードで私の回答を編集してくれることを願っています。

于 2012-07-03T11:14:23.167 に答える