Java正規表現のグループを置き換えることはできますか?


95

私はこのコードを持っています。Java正規表現でグループのみ(すべてのパターンではない)を置き換えることができるかどうかを知りたいです。コード:

 //...
 Pattern p = Pattern.compile("(\\d).*(\\d)");
    String input = "6 example input 4";
    Matcher m = p.matcher(input);
    if (m.find()) {

        //Now I want replace group one ( (\\d) ) with number 
       //and group two (too (\\d) ) with 1, but I don't know how.

    }

6
質問を明確にできますか?たとえば、その入力に対して期待される出力を与えることができますか?
マイケルマイヤーズ

回答:


125

を使用して$n(nは数字)、でキャプチャされたサブシーケンスを参照しreplaceFirst(...)ます。最初のグループをリテラル文字列「number」で置き換え、2番目のグループを最初のグループの値で置き換えることを想定しています。

Pattern p = Pattern.compile("(\\d)(.*)(\\d)");
String input = "6 example input 4";
Matcher m = p.matcher(input);
if (m.find()) {
    // replace first number with "number" and second number with the first
    String output = m.replaceFirst("number $3$1");  // number 46
}

(\D+)代わりに2番目のグループを検討してください(.*)*貪欲なマッチャーであり、最初は最後の桁を消費します。マッチャーは(\d)、最後の数字に一致する前に、一致するものが何もないことに気付いたときにバックトラックする必要があります。


7
出力例を投稿したとしたら良かったでしょう
winklerrr

6
これは最初のマッチで機能しますが、グループが多く、while(m.find())でグループを反復している場合は機能しません
Hugo Zaragoza

1
私はHugoに同意します。これはソリューションを実装するためのひどい方法です...なぜこれが受け入れられた答えであり、acdcjuniorの答えではないのですか?これは完璧なソリューションです:少量のコード、高い凝集度、低い結合、はるかに少ないチャンス(可能性がない場合)不要な副作用の... ため息 ...
FireLight

この回答は現在有効ではありません。するm.replaceFirst("number $2$1");必要がありますm.replaceFirst("number $3$1");
ダニエルアイゼンライヒ

52

Matcher#start(group)Matcher#end(group)を使用して、一般的な置換方法を構築できます。

public static String replaceGroup(String regex, String source, int groupToReplace, String replacement) {
    return replaceGroup(regex, source, groupToReplace, 1, replacement);
}

public static String replaceGroup(String regex, String source, int groupToReplace, int groupOccurrence, String replacement) {
    Matcher m = Pattern.compile(regex).matcher(source);
    for (int i = 0; i < groupOccurrence; i++)
        if (!m.find()) return source; // pattern not met, may also throw an exception here
    return new StringBuilder(source).replace(m.start(groupToReplace), m.end(groupToReplace), replacement).toString();
}

public static void main(String[] args) {
    // replace with "%" what was matched by group 1 
    // input: aaa123ccc
    // output: %123ccc
    System.out.println(replaceGroup("([a-z]+)([0-9]+)([a-z]+)", "aaa123ccc", 1, "%"));

    // replace with "!!!" what was matched the 4th time by the group 2
    // input: a1b2c3d4e5
    // output: a1b2c3d!!!e5
    System.out.println(replaceGroup("([a-z])(\\d)", "a1b2c3d4e5", 2, 4, "!!!"));
}

ここでオンラインデモを確認してください。


1
これは実際に受け入れられる答えであるはずです。これは、付随するコードにある程度のカップリングを導入することなく、最も完全で「すぐに使える」ソリューションです。そのうちの1つのメソッド名を変更することをお勧めしますが。一見すると、最初のメソッドの再帰呼び出しのように見えます。
FireLight 2017年

逃した編集の機会。コードを適切に分析しなかったため、再帰呼び出しに関する部分を取り戻します。オーバーロードは一緒に機能します
FireLight 2017年

23

死んだ馬を打ち負かして申し訳ありませんが、だれもこれを指摘しなかったのはちょっと奇妙なことです。

Regexを本来の方法で使用する場合、解決策は次のように簡単です。

"6 example input 4".replaceAll("(?:\\d)(.*)(?:\\d)", "number$11");

または、以下のshmoselが正しく指摘したように、

"6 example input 4".replaceAll("\d(.*)\d", "number$11");

...あなたの正規表現では小数をグループ化する理由はまったくありません。

あなたが通常使用しないキャプチャあなたがしたい文字列の部分にグループを破棄し、あなたがしたい文字列の一部にそれらを使用し続けます

置き換えたいグループが本当に必要な場合は、おそらく代わりにテンプレートエンジン(たとえばmoustache、ejs、StringTemplateなど)が必要です。


興味深いことに、正規表現の非キャプチャグループでさえ、正規表現エンジンが可変テキストを認識してスキップする必要がある場合のために存在しています。たとえば、

(?:abc)*(capture me)(?:bcd)*

入力が「abcabc capture me bcdbcd」、「abc capture me bcd」、または単に「capture me」のように見える場合に必要です。

または、逆に言えば、テキストが常に同じで、キャプチャしない場合は、グループを使用する理由はまったくありません。


1
非キャプチャグループは不要です。\d(.*)\d十分であろう。
shmosel 2018

1
$11ここがわかりません。なぜ11なのか?
Alexis

1
@Alexis -これは、Javaの正規表現癖:グループ11セット、Javaの解釈$ 11 $ 1は1続くとされていない場合
やろ

9

周り.*に括弧を追加して3番目のグループを追加し、サブシーケンスをに置き換え"number" + m.group(2) + "1"ます。例えば:

String output = m.replaceFirst("number" + m.group(2) + "1");

4
実際には、Matcherは$ 2スタイルの参照をサポートしているため、m.replaceFirst( "number $ 21")も同じことを行います。
マイケル・マイヤーズ

実際、彼らは同じことをしていません。 "number$21"動作し"number" + m.group(2) + "1"ません。
アランムーア

2
number$21グループ2 +文字列 "1"ではなく、グループ21を置き換えるように見えます。
フェルナンドM.ピニェイロ

これは単純な文字列連結ですよね?なぜreplaceFirstを呼び出す必要があるのですか?
Zxcv Mnb 2015

2

matcher.start()およびmatcher.end()メソッドを使用して、グループの位置を取得できます。したがって、この位置を使用すると、テキストを簡単に置き換えることができます。


1

入力のパスワードフィールドを置き換えます。

{"_csrf":["9d90c85f-ac73-4b15-ad08-ebaa3fa4a005"],"originPassword":["uaas"],"newPassword":["uaas"],"confirmPassword":["uaas"]}



  private static final Pattern PATTERN = Pattern.compile(".*?password.*?\":\\[\"(.*?)\"\\](,\"|}$)", Pattern.CASE_INSENSITIVE);

  private static String replacePassword(String input, String replacement) {
    Matcher m = PATTERN.matcher(input);
    StringBuffer sb = new StringBuffer();
    while (m.find()) {
      Matcher m2 = PATTERN.matcher(m.group(0));
      if (m2.find()) {
        StringBuilder stringBuilder = new StringBuilder(m2.group(0));
        String result = stringBuilder.replace(m2.start(1), m2.end(1), replacement).toString();
        m.appendReplacement(sb, result);
      }
    }
    m.appendTail(sb);
    return sb.toString();
  }

  @Test
  public void test1() {
    String input = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"123\"],\"newPassword\":[\"456\"],\"confirmPassword\":[\"456\"]}";
    String expected = "{\"_csrf\":[\"9d90c85f-ac73-4b15-ad08-ebaa3fa4a005\"],\"originPassword\":[\"**\"],\"newPassword\":[\"**\"],\"confirmPassword\":[\"**\"]}";
    Assert.assertEquals(expected, replacePassword(input, "**"));
  }

0

これは別の解決策であり、複数の一致で単一のグループを置き換えることもできます。スタックを使用して実行順序を逆にするため、文字列操作を安全に実行できます。

private static void demo () {

    final String sourceString = "hello world!";

    final String regex = "(hello) (world)(!)";
    final Pattern pattern = Pattern.compile(regex);

    String result = replaceTextOfMatchGroup(sourceString, pattern, 2, world -> world.toUpperCase());
    System.out.println(result);  // output: hello WORLD!
}

public static String replaceTextOfMatchGroup(String sourceString, Pattern pattern, int groupToReplace, Function<String,String> replaceStrategy) {
    Stack<Integer> startPositions = new Stack<>();
    Stack<Integer> endPositions = new Stack<>();
    Matcher matcher = pattern.matcher(sourceString);

    while (matcher.find()) {
        startPositions.push(matcher.start(groupToReplace));
        endPositions.push(matcher.end(groupToReplace));
    }
    StringBuilder sb = new StringBuilder(sourceString);
    while (! startPositions.isEmpty()) {
        int start = startPositions.pop();
        int end = endPositions.pop();
        if (start >= 0 && end >= 0) {
            sb.replace(start, end, replaceStrategy.apply(sourceString.substring(start, end)));
        }
    }
    return sb.toString();       
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.