2
\$\begingroup\$

This is my custom function to join strings from an array into one string. We can provide normal separator and the last one. JoinStringsIntoArray(", ", " and ", words...)

func JoinStringsIntoArray(sep string, lastSep string, words ...string) string {
lastElementIndex := len(words) - 1
var joinedString string
for index, word := range words[0:lastElementIndex] {
 if index == lastElementIndex-1 {
 sep = lastSep
 }
 joinedString += word + sep
}
joinedString += words[lastElementIndex]
 return joinedString
}

Test for this function

func TestStringsJoinsWithCommas(t *testing.T) {
 var words []string
 words = append(words, "one", "two", "three")
 expectedResult := "one, two and three"
 result := JoinStringsIntoArray(", ", " and ", words...)
 if expectedResult != result {
 t.Errorf("Strings joiner is broken, it should be '%v', we got '%v'", expectedResult, result)
 }
}

What do you think about this, can this solution be improved? The function works fine but I'm not so sure it's written well.

Mast
13.8k12 gold badges56 silver badges127 bronze badges
asked Apr 29, 2021 at 16:30
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The Go standard library strings package has a Join function:

func Join(elems []string, sep string) string

Your function is a simple extension of strings.Join:

func JoinLast(elems []string, sep, lastSep string) string

However, for no obvious reason, you chose a different function parameter signature:

func JoinLast(sep string, lastSep string, words ...string) string

We need a comment to explain why this isn't an arbitrary or idiosyncratic decision.


Your testing is inadequate. Check boundary conditions. Your function fails on an empty words list.

words = []string{}

You have not provided any testing benchmarks. For example,

BenchmarkJoin-4 5534918 207.9 ns/op 104 B/op 5 allocs/op
func JoinStringsIntoArray(sep string, lastSep string, words ...string) string {
 lastElementIndex := len(words) - 1
 var joinedString string
 for index, word := range words[0:lastElementIndex] {
 if index == lastElementIndex-1 {
 sep = lastSep
 }
 joinedString += word + sep
 }
 joinedString += words[lastElementIndex]
 return joinedString
}
func BenchmarkJoin(b *testing.B) {
 words := []string{"one", "two", "three", "four", "five"}
 b.ReportAllocs()
 b.ResetTimer()
 for N := 0; N < b.N; N++ {
 JoinStringsIntoArray(", ", " and ", words...)
 }
}

You can do better. For example, minimize allocation,

BenchmarkJoin-4 16612879 70.95 ns/op 32 B/op 1 allocs/op
func JoinLast(elems []string, sep, lastSep string) string {
 var join strings.Builder
 joinCap := 0
 for i := 0; i < len(elems); i++ {
 joinCap += len(elems[i])
 }
 if len(elems) > 1 {
 joinCap += len(lastSep)
 if len(elems) > 2 {
 joinCap += (len(elems) - 2) * len(sep)
 }
 }
 join.Grow(joinCap)
 last := len(elems) - 1 - 1
 for i, elem := range elems {
 if i >= last {
 if i == last {
 sep = lastSep
 } else {
 sep = ""
 }
 }
 join.WriteString(elem)
 join.WriteString(sep)
 }
 return join.String()
}
func BenchmarkJoin(b *testing.B) {
 words := []string{"one", "two", "three", "four", "five"}
 b.ReportAllocs()
 b.ResetTimer()
 for N := 0; N < b.N; N++ {
 JoinLast(words, ", ", " and ")
 }
}
answered Apr 30, 2021 at 2:55
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.