Home > Software design >  Replace all characters except for the last occurrence
Replace all characters except for the last occurrence

Time:09-02

I am performing a replace of characters on a string like this:

result = strings.ReplaceAll(result, ".", "_")

This works as expected, but I want to preserve the last occurence of the . and not replace it, just leave it alone.

Is there a fancy way doing this?

CodePudding user response:

The most obvious way to handle this would be to combine Replace and Count:

func ReplaceAllExceptLast(d string, o string, n string) string {
    strings.Replace(d, o, n, strings.Count(d, o) - 1)
}

I don't think this would be the most optimal solution, however. For me, the best option would be to do this:

func ReplaceAllExceptLast(d string, o string, n string) string {
    ln := strings.LastIndex(d, o)
    if ln == -1 {
        return d
    }

    return strings.ReplaceAll(d[:ln], o, n)   d[ln:]
}

This works by getting the index of the last occurrence of the value you want to replace, and then doing a replace-all on the string up to that point. For example:

println(ReplaceAllExceptLast("a", ".", "_"))
println(ReplaceAllExceptLast("a.b", ".", "_"))
println(ReplaceAllExceptLast("a.b.c", ".", "_"))
println(ReplaceAllExceptLast("a.b.c.d", ".", "_"))

will produce:

a
a.b
a_b.c
a_b_c.d

CodePudding user response:

Split into []string, then Join all but last ;)

func ReplaceAllButLast(data, old, new string) string {
    split := strings.Split(data, old)
    if len(split) < 3 {
        return data
    }
    last := len(split) - 1
    return strings.Join(split[:last], new)   old   split[last]
}

https://go.dev/play/p/j8JJP-p_Abk

func main() {
    println(ReplaceAllButLast("a", ".", "_"))
    println(ReplaceAllButLast("a.b", ".", "_"))
    println(ReplaceAllButLast("a.b.c", ".", "_"))
    println(ReplaceAllButLast("a.b.c.d", ".", "_"))
}

produced

a
a.b
a_b.c
a_b_c.d

UPDATE

That was a joke, just to be fancy

Best way is to count the number of matches and replace Count()-1, the second it to ReplaceAll until last match position and using Join to be the slowest

func ReplaceAllButLast_Count(data, old, new string) string {
    cnt := strings.Count(data, old)
    if cnt < 2 {
        return data
    }
    return strings.Replace(data, old, new, cnt-1)
}

func ReplaceAllButLast_Replace(data, old, new string) string {
    idx := strings.LastIndex(data, old)
    if idx <= 0 {
        return data
    }

    return strings.ReplaceAll(data[:idx], old, new)   data[idx:]
}

func ReplaceAllButLast_Join(data, old, new string) string {
    split := strings.Split(data, old)
    if len(split) < 3 {
        return data
    }
    last := len(split) - 1
    return strings.Join(split[:last], new)   old   split[last]
}

Benchmark using a.b.c.d -> a_b_c.d

goos: windows
goarch: amd64
pkg: example.org
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
Benchmark_Count-8       16375098            70.05 ns/op        8 B/op          1 allocs/op
Benchmark_Replace-8     11213830           108.5 ns/op        16 B/op          2 allocs/op
Benchmark_Slice-8        5460445           217.6 ns/op        80 B/op          3 allocs/op

CodePudding user response:

The first solution comes to my mind is Positive Lookahead of regular expression

[.](?=.*[.])

However, the (?=re) is not supported in Golang re2


We can use Non-capturing group to implement it in this way

  • First use (?:[.])( Match a single character present in the list below [.]) to find all dot indexes.

  • Then replace dot with '_' except the last one.

Sample

func replaceStringByIndex(str string, replacement string, index int) string {
    return str[:index]   replacement   str[index 1:]
}

func replaceDotIgnoreLast(str string, replacement string) string {
    pattern, err := regexp.Compile("(?:[.])")
    if err != nil {
        return ""
    }

    submatches := pattern.FindAllStringSubmatchIndex(str, -1)
    for _, submatch := range submatches[:len(submatches)-1] {
        str = replaceStringByIndex(str, replacement, submatch[0])
    }

    return str
}

func main() {
    str := "1.2"
    fmt.Println(replaceDotIgnoreLast(str, "_"))
    str = "1.2.3"
    fmt.Println(replaceDotIgnoreLast(str, "_"))
    str = "1.2.3.4"
    fmt.Println(replaceDotIgnoreLast(str, "_"))
}

Result

1.2
1_2.3
1_2_3.4
  •  Tags:  
  • go
  • Related