22

I would like to replace a match with the number/index of the match.

Is there way in java to know which match number the current match is, so I can use only replaceAll(regex, replacement) to inject the count of each match into the result?

Example: Replace [A-Z] with itself and its index:

Input:  fooXbarYfooZ
Output: fooX1barY2fooZ3

eg this:

"fooXbarXfooX".replaceAll("[A-Z]", "$0<some reference to the match count>");

should return "fooX1barY2fooZ3"


Note: I seek a replacement String within a single invocation of replaceAll() (or similar method) that does the entire job.

Answers that use more than just a single method call, eg wrapping replacement operations in a for loop, do not answer the question.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 2
    There isn't a simple replacement string that can do this; strings can't store state and the regex engine doesn't have a built-in way to do so either. – Amber Jun 05 '12 at 06:04
  • 1
    I think that you would need some stack enabled regex, which I do not think that the Java regex has... – npinti Jun 05 '12 at 06:06
  • 2
    You want something like this: [Java equivalent to PHP's preg_replace_callback](http://stackoverflow.com/questions/375420/java-equivalent-to-phps-preg-replace-callback) . Generally, I don't know any regex flavor that allows replacing like that "natively", without a calllback: http://www.regular-expressions.info/refreplace.html – Kobi Jun 05 '12 at 06:18
  • 2
    Where does the "no loops" requirement come from? I don't understand that: pretty much everything involves loops, *including* interpreting regexes. So what do you really want to achieve? Do you want to avoid *seeing* the loops? – Joachim Sauer Jun 05 '12 at 06:55
  • @JoachimSauer Yes... I would like an elegant one-liner using just the JDK. I hit some code today that needed it, so I thought I'd ask if it could be done. I solved it more or less with a loop, but it cluttered my code without adding much value. If it can't be done, then it can't be done... fine. – Bohemian Jun 05 '12 at 07:14
  • 1
    @Bohemian, no, the standard JDK does not provide such functionality. Your code should not look cluttered if you factor out your replacement code in a method. – aioobe Jun 05 '12 at 07:27

4 Answers4

7

Iterating over the input string is required so looping one way or the other is inevitable. The standard API does not implement a method implementing that loop so the loop either have to be in client code or in a third party library.


Here is how the code would look like btw:

public abstract class MatchReplacer {

    private final Pattern pattern;

    public MatchReplacer(Pattern pattern) {
        this.pattern = pattern;
    }

    public abstract String replacement(MatchResult matchResult);

    public String replace(String input) {

        Matcher m = pattern.matcher(input);

        StringBuffer sb = new StringBuffer();

        while (m.find())
            m.appendReplacement(sb, replacement(m.toMatchResult()));

        m.appendTail(sb);

        return sb.toString();
    }
}

Usage:

public static void main(String... args) {
    MatchReplacer replacer = new MatchReplacer(Pattern.compile("[A-Z]")) {
        int i = 1;
        @Override public String replacement(MatchResult m) { 
            return "$0" + i++;
        }
    };
    System.out.println(replacer.replace("fooXbarXfooX"));
}

Output:

fooX1barX2fooX3
dacwe
  • 43,066
  • 12
  • 116
  • 140
  • 4
    From the question: **Please do not provide answers involving loops or similar code.** – npinti Jun 05 '12 at 06:06
  • 1
    Even though it's a loop, quite nice. However please note edit to question refining the example to demonstrate both match and replacement must be a regex, which would break your implementation as it stands, because the result of `replacement()` must use a back reference. – Bohemian Jun 05 '12 at 18:41
  • 1
    I don't see how it would break the current solution. The `appendReplacement` method can (just like `replaceAll`) handle back-references like `$0`. Updated the `main` method to show how it is done. – dacwe Jun 06 '12 at 20:24
3

Not possible without loops in Java...

dda
  • 6,030
  • 2
  • 25
  • 34
3

Java regex does not support a reference to the count of the match, so it is impossible.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
2

Well I don't think its possible without any loop in Java.

String x = "fooXbarXfooX";
        int count = 0;
        while(x.contains("X"))
        x = x.replaceFirst("X", Integer.toString(count++));
        System.out.println(x);
Chandra Sekhar
  • 18,914
  • 16
  • 84
  • 125
  • 3
    Some feedback: Although it's less code than the other solutions, because you're restarting the regex match every loop, there is a risk that the regex will match the replacement, causing an infinite loop. Consider what would happen when replacing `"X"` with `"Xi"` where `i` is the index - the first `X` would get re-matched forever. You could avoid this by starting your match *after* the position of the last match, but then your code would morph into something like dacwe's code. Also, his code is more efficient, because it only does one pass. Just something to think about. – Bohemian Jun 05 '12 at 07:24
  • Please note edit to question refining the example to demonstrate both match and replacement must be a regex. – Bohemian Jun 05 '12 at 18:40