6

Aim: to insert a character every x characters in a string in Golang

Input: helloworldhelloworldhelloworld

Expected Output: hello-world-hello-world-hello-world

Attempts

Attempt one

package main

import (
    "fmt"
    "strings"
)

func main() {
    s := "helloworldhelloworldhelloworld"

    s = strings.Replace(s, "world", ",", -1)
    fmt.Println(s)
}

results in: hello,hello,hello,


Attempt two

  1. Count number of characters
  2. For loop
  3. If X=5 then insert a -

Attempt three

  1. Scan combined with join

Problem

The reason that attempts two and three do not contain code snippets at the moment is that I am still thinking what approach should be used to insert a character every X characters in a string in Golang.

030
  • 10,842
  • 12
  • 78
  • 123

7 Answers7

10

https://play.golang.org/p/HEGbe7radf

This function just inserts '-' each Nth element

func insertNth(s string,n int) string {
    var buffer bytes.Buffer
    var n_1 = n - 1
    var l_1 = len(s) - 1
    for i,rune := range s {
       buffer.WriteRune(rune)
       if i % n == n_1 && i != l_1  {
          buffer.WriteRune('-')
       }
    }
    return buffer.String()
}
Oleg
  • 3,080
  • 3
  • 40
  • 51
  • 2
    https://play.golang.org/p/IzcWx771wF Here's a slightly more general version, that takes arbitrary runes to use as the character to insert. – Dr. Ernie Apr 17 '17 at 17:49
4

I feel the following solution is worth mentioning:

package main

import "fmt"

var s = "helloworldhelloworldhelloworld"

func main() {
    for i := 5; i < len(s); i += 6 {
        s = s[:i] + "-" + s[i:]
    }
    fmt.Println(s)
}

https://play.golang.org/p/aMXOTgiNHf

Jérôme
  • 66
  • 3
2

According to the Go documentation strings are a read only Slice of bytes.. With that in mind there is an issue that comes up. What character set are you using? You can see some examples of where things get weird here and here.

Despite the complexity there still is a simple answer

s = strings.Replace(s, "hello", "hello-", -1)
s = strings.Replace(s, "world", "world-", -1)
Community
  • 1
  • 1
Diablojoe
  • 231
  • 1
  • 8
1

My take:


import (
    "fmt"
    "regexp"
)

const s = "helloworldhelloworldhelloworld"

func Attempt1(s string) string {
    var re = regexp.MustCompile(`(\Bhello|\Bworld)`)
    return re.ReplaceAllString(s, "-$1")
}

func Attempt2(s string) string {
    const chunkLen = len("hello")
    out := make([]rune, len(s)+len(s)/chunkLen)
    i, j := 1, 0
    for _, c := range s {
        out[j] = c
        if i == len(s) {
            break
        }
        j++
        if i%chunkLen == 0 {
            out[j] = '-'
            j++
        }
        i++
    }
    return string(out)
}

func main() {
    fmt.Println(Attempt1(s))
    fmt.Println(Attempt2(s))
}

Playground link

I should add that while it would be possible to implement "approach 3" — that "split the source string in chunks of five characters then join the chunks using the '-' character" one, — it would still require scanning the source string rune-by-rune as my Attempt2() does; so if you squint at it, you'll see that storing a list of chunks and then joining them is doing more operations for no real gain (and bigger memory footprint etc).

kostix
  • 51,517
  • 14
  • 93
  • 176
0

If you know your string is divisible by 5, then this could also be a solution. Definitely less efficient though than some of the other solutions posted.

package main

import (
    "fmt"
    "regexp"
    "strings"
)

func main() {
    fmt.Println(InsertEvery5("HelloWorld", "-"))
}

// Only works if len(str) mod 5 is 0
func InsertEvery5(str string, insert string) string {
    re := regexp.MustCompile(`.{5}`) // Every 5 chars
    parts := re.FindAllString(str, -1) // Split the string into 5 chars blocks.
    return strings.Join(parts, insert) // Put the string back together
}
Dylan
  • 1
  • 1
0

My take. I needed to add new lines (more than a single character) to long strings to wrap them.

func InsertNewLines(s string, n int) string {
    var r = regexp.MustCompile("(.{" + strconv.Itoa(n) + "})")
    return r.ReplaceAllString(s, "$1<wbr />")
}
472084
  • 17,666
  • 10
  • 63
  • 81
0

Pre-allocating memory and using copy is faster than iteration over every single character.

https://go.dev/play/p/NqVYnOJGRLP

func insertNthCopy(in, sep string, n int) string {
    count := len(in) / n // amount of lines to split
    split := make([]byte, len(in)+len(sep)*count)
    i, s := 0, 0

    for ; i < count*n; i, s = i+n, s+n+len(sep) {
        copy(split[s:], in[i:i+n])
        copy(split[s+n:], sep)
    }

    copy(split[s:], in[i:]) // copy remainder if any

    return string(split)
}

About six times faster than the accepted answer with provided sample string:

cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
Benchmark_insertNthCopy
Benchmark_insertNthCopy-8       12404311            85.11 ns/op
Benchmark_insertNth
Benchmark_insertNth-8            2269458           524.5 ns/op
debugger
  • 346
  • 2
  • 10