Regular expressions - replacing occurrences in Go

The Go standard library provides several methods for matching and replacing text with regular expressions via its regexp package. Here’s an example that matches and replaces all the occurrences of the string “abc” in a text with “xyz”:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "abc abc abc"
	regex := regexp.MustCompile("abc")
	fmt.Println(regex.ReplaceAllString(text, "xyz")) // xyz xyz xyz
}

Replace one or more occurrences

While the above method works well, it doesn’t help when it is desired that not all the matches in the source text should be replaced. The regexp.Regexp type does not offer a Replace method similar to strings.Replace() where you can specify the exact number of matches that should be replaced, but we can still achieve the necessary effect through the ReplaceAllStringFunc() method as shown below:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "abc abc abc"
	regex := regexp.MustCompile("abc")
	counter := 0
	output := regex.ReplaceAllStringFunc(text, func(value string) string {
		if counter == 1 {
			return value
		}

		counter++
		return regex.ReplaceAllString(value, "xyz")
	})

	fmt.Println(output) // xyz abc abc
}

The ReplaceAllStringFunc() method takes the source text as its first argument and a function as its second. The function is called once for each time a match is found in the text (3 times in this case) and its return value will replace value in the source string.

The counter variable is incremented each time the function runs, so we can use it to keep track of how many replacements have been made. If the counter is 1, we don’t want to make any more replacements so the value is returned as is. This yields the desired output as shown in the snippet. You can replace 1 with the number of occurrences that you want replaced in the source text.

Replacing occurrences from the end of the text

The ReplaceAllStringFunc() method is also handy if you want to start replacing the occurrences of matched text from the end of the string instead of from the beginning. Here’s how:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	text := "abc abc abc"
	regex := regexp.MustCompile("abc")

	counter := 0
	matches := regex.FindAllString(text, -1)
	l := len(matches) - 1
	output := regex.ReplaceAllStringFunc(text, func(value string) string {
		if counter >= l {
			return regex.ReplaceAllString(value, "xyz")
		}

		counter++
		return value
	})

	fmt.Println(output) // abc abc xyz
}

In above snippet, the FindAllString() method is used to find all the matches in the source text. The desired number of replacements is subsequently subtracted from the length of the matches and compared to counter inside the ReplaceAllStringFunc() function argument. And that’s how the replacement of occurrences is made to begin from the end of the source text.

Thanks for reading, and happy coding!