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.
1 Answer 1
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 ")
}
}