String Builder
Strings in Go are immutable. Nothing can change one in place, so every +
and every += allocates a new string and copies both halves into it. In a loop
that is quadratic:
s := ""
for i := range 10000 {
s += "x" // 10000 allocations, ~50 million bytes copied
}
The tenth iteration copies nine characters, the ten-thousandth copies 9999. The work grows with the square of the length, and none of it is your program's actual job.
strings.Builder is the answer. It writes
into a growable byte buffer and hands the buffer over as a string at the end,
without copying it again:
var b strings.Builder
b.Grow(10000) // reserve once, optional but free to get right
for range 10000 {
b.WriteString("x")
}
s := b.String()
The zero value is ready to use, so var b strings.Builder needs no make and
no &. Its methods are WriteString, WriteByte, WriteRune and Write, and
Len reports how much has been written so far. None of the writes can fail, so
their errors are always nil and idiomatic code ignores them.
Because a Builder implements io.Writer,
fmt.Fprintf(&b, ...) writes formatted output
straight into it - no intermediate Sprintf string. Note the &: it needs the
pointer.
That pointer is not a detail. A Builder must not be copied after first
use. It keeps a pointer to itself to detect exactly that, and a copy panics
with "illegal use of non-zero Builder copied by value". So pass *strings.Builder
to a function, never strings.Builder, and never assign one to another
variable.
Finally, the formatting verbs are worth knowing: %q quotes and escapes a
string, %02x renders a byte as two hex digits, %-10s left-aligns in a
ten-column field, and %5d right-aligns a number in five.
Task
Fill in the six functions in main.go. Every one of them should build its
result with a strings.Builder.
Repeat(s, n)writessoutntimes, reserving the space withGrowfirst - there is a test that counts allocations.JoinLines(lines)writes each line followed by"\n".AppendTo(b, items...)appends into a builder it is given, separating with", "and joining onto whatever is already there.QuoteList(items)renders["a", "b"].HexDump(data)renders"47 6f".Table(names, counts)renders%-10s%5drows.
Hints
b.Grow(len(s) * n)before the loop is what makesRepeatallocate once.AppendTotakes a*strings.Builderbecause aBuildercannot be copied, and because it has to add to the caller's one rather than to a copy of it.b.Len() > 0is how it tells "first item" from "not the first" across calls.- For a separator between items and not after them, the usual shape is
if i > 0 { write the separator }at the top of the loop. fmt.Fprintf(&b, "%q", item)- remember the&, or it will not compile.%02xon abytegives the leading zero;%xalone does not, so0x07would come out as7.min(a, b)is a builtin, which is allTableneeds to stop at the shorter slice.