//go:build !solution package varfmt import ( "fmt" "strconv" "strings" "unicode/utf8" ) // fmt.Sprintf does 1 alloc // this does 3-4 allocations and is a bit slower than fmt.Sprintf // can be further optimized, by casting args slice // to string without allocating new space (somehow) func Sprintf(format string, args ...any) string { // init output strings.Builder and num rune buffer // cant use strings.Builder for storing num since // builder can't clear its contents without reallocating output, num := strings.Builder{}, make([]rune, 0, 2) // first alloc here // convert args slice to strArgs string slice strArgs, maxLen := make([]string, len(args), cap(args)), 0 // second alloc here for i, arg := range args { strArgs[i] = fmt.Sprint(arg) // third and other allocs happen here // could be further optimized if len(strArgs[i]) > maxLen { maxLen = len(strArgs[i]) } } // allocate maximum possible length of the resulting string output.Grow(len(format) + len(args)*maxLen) // third alloc here start, argIndex := 0, 0 // decode runes one by one for start < len(format) { r, n := utf8.DecodeRuneInString(format[start:]) start += n // if specifier is found, start forming index if r == '{' { numLength := 0 for { r, n = utf8.DecodeRuneInString(format[start:]) start += n if r == '}' { break } numLength++ // append new rune if the number is bigger // than the current buffer if numLength >= len(num) { num = append(num, r) // further allocs happen here } else { num[numLength] = r } } var index int // if specifier is empty get arg at argIndex // otherwise get arg at the specified index from num if len(num) == 0 { index = argIndex } else { var err error index, err = strconv.Atoi(string(num)) if err != nil { panic(err) } } output.WriteString(strArgs[index]) argIndex++ // set len of num to 0, but keep capacity num = num[:0] continue } output.WriteRune(r) } return output.String() }