4

I'm a newbie on Groovy and have a question about replaceFirst with closure.

The groovy-jdk API doc gives me examples of...

assert "hellO world" == "hello world".replaceFirst("(o)") { it[0].toUpperCase() } // first match
assert "hellO wOrld" == "hello world".replaceAll("(o)") { it[0].toUpperCase() }   // all matches

assert '1-FISH, two fish' == "one fish, two fish".replaceFirst(/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }
assert '1-FISH, 2-FISH' == "one fish, two fish".replaceAll(/([a-z]{3})\s([a-z]{4})/) { [one:1, two:2][it[1]] + '-' + it[2].toUpperCase() }

The first two examples are quite straightforward, but I can't understand the remaining ones.

First, what does [one:1, two:2] mean? I even don't know the name of it to search.

Second, why is there a list of "it"? The doc says replaceFirst()

Replaces the first occurrence of a captured group by the result of a closure call on that text.

Doesn't "it" refer to "the first occurrence of a captured group"?

I would appreciate any tips and comments!

jalopaba
  • 8,039
  • 2
  • 44
  • 57
sgil
  • 45
  • 1
  • 4

1 Answers1

2

First, [one:1, two:2] is a Map:

assert [one:1, two:2] instanceof java.util.Map
assert 1 == [one:1, two:2]['one']
assert 2 == [one:1, two:2]['two']
assert 1 == [one:1, two:2].get('one')
assert 2 == [one:1, two:2].get('two')

So, basically, the code inside the closure uses that map as a lookup table to replace one with 1 and two with 2.

Second, let's see how the regex matcher works:

To find out how many groups are present in the expression, call the groupCount method on a matcher object. The groupCount method returns an int showing the number of capturing groups present in the matcher's pattern. In this example, groupCount would return the number 4, showing that the pattern contains 4 capturing groups.

There is also a special group, group 0, which always represents the entire expression. This group is not included in the total reported by groupCount. Groups beginning with (? are pure, non-capturing groups that do not capture text and do not count towards the group total.

Drilling down to the regex stuff:

def m = 'one fish, two fish' =~ /([a-z]{3})\s([a-z]{4})/
assert m instanceof java.util.regex.Matcher
m.each { group ->
    println group
}

This yields:

[one fish, one, fish] // only this first match for "replaceFirst"
[two fish, two, fish]

So we can rewrite the code in a clearer way renaming it by group (it is simply the default name of the argument in a single argument closure):

assert '1-FISH, two fish' == "one fish, two fish".replaceFirst(/([a-z]{3})\s([a-z]{4})/) { group ->
    [one:1, two:2][group[1]] + '-' + group[2].toUpperCase() 
}
assert '1-FISH, 2-FISH' == "one fish, two fish".replaceAll(/([a-z]{3})\s([a-z]{4})/) { group ->
    [one:1, two:2][group[1]] + '-' + group[2].toUpperCase()
}
jalopaba
  • 8,039
  • 2
  • 44
  • 57