Compare commits

..

3 commits

Author SHA1 Message Date
5bc4119abb Implemented speller
The solution is efficient, but unreadable
Since the performance is not the priority in this task, a prettier solution must be implemented
2024-06-04 03:24:20 +03:00
4bd325a744 Small improvements for varfmt 2024-06-03 20:56:45 +03:00
bfb0daa1ef Updated .gitignore to ignore .out and .test profiling files
Replaced interface{} with any in varfmt
Implemented varfmt, could be further optimized
2024-06-03 20:26:36 +03:00
5 changed files with 187 additions and 33 deletions

6
.gitignore vendored
View file

@ -2,4 +2,8 @@
*.swp
.vscode/
/example/
/example/
# Go profiling files
*.out
*.test

View file

@ -10,50 +10,50 @@ import (
"time"
)
type HttpGetResult struct {
type HTTPGetResult struct {
Success bool
Body string
Elapsed time.Duration
Size int
Url string
URL string
Error error
}
func (result HttpGetResult) String() string {
func (result HTTPGetResult) String() string {
if result.Success {
return fmt.Sprintf("%v\t%v\t%v", result.Elapsed, result.Size, result.Url)
return fmt.Sprintf("%v\t%v\t%v", result.Elapsed, result.Size, result.URL)
} else {
return result.Error.Error()
}
}
func GetHttpBody(url string, ch chan HttpGetResult) {
func GetHTTPBody(url string, ch chan HTTPGetResult) {
startTime := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- HttpGetResult{Success: false, Error: err}
ch <- HTTPGetResult{Success: false, Error: err}
return
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
ch <- HttpGetResult{Success: false, Error: err}
ch <- HTTPGetResult{Success: false, Error: err}
return
}
ch <- HttpGetResult{
ch <- HTTPGetResult{
Success: true,
Body: string(data),
Elapsed: time.Since(startTime),
Size: len(data),
Url: url,
URL: url,
}
}
func main() {
startTime := time.Now()
urls, ch := os.Args[1:], make(chan HttpGetResult)
urls, ch := os.Args[1:], make(chan HTTPGetResult)
for _, url := range urls {
go GetHttpBody(url, ch)
go GetHTTPBody(url, ch)
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-ch)

View file

@ -2,6 +2,87 @@
package speller
func Spell(n int64) string {
return ""
import (
"strings"
)
var numToDigits = []string{
"", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine",
}
var teens = []string{
"ten", "eleven", "twelve", "thirteen", "fourteen",
"fifteen", "sixteen", "seventeen", "eighteen", "nineteen",
}
var decs = []string{
"", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety",
}
var large = []string{
"billion", "million", "thousand",
}
func digitsReversedSlice(n int64) []uint8 {
digits := make([]uint8, 12)
for i := 0; i < len(digits); i, n = i+1, n/10 {
digits[i] = uint8(n % 10)
}
return digits
}
func Spell(n int64) string {
if n == 0 {
return "zero"
}
sign := n < 0
if sign {
n = -n
}
num, digits := strings.Builder{}, digitsReversedSlice(n)
// 35 is the optimal number which gives approx needed
// space for the number in 1 allocation
num.Grow(len(digits) * 35)
if sign {
num.WriteString("minus ")
}
largeIndex, iters := 0, 0
for i := len(digits) - 1; i >= 0; i, largeIndex = i-3, largeIndex+1 {
first, second, third := digits[i-2], digits[i-1], digits[i]
if first == 0 && second == 0 && third == 0 {
continue
}
if iters != 0 {
num.WriteRune(' ')
}
hundreds := numToDigits[third]
if hundreds != "" {
num.WriteString(hundreds)
num.WriteString(" hundred")
if second != 0 || first != 0 {
num.WriteRune(' ')
}
}
if second > 1 {
num.WriteString(decs[second])
if first > 0 {
num.WriteRune('-')
num.WriteString(numToDigits[first])
}
} else if second == 1 {
num.WriteString(teens[first])
} else {
if first > 0 {
num.WriteString(numToDigits[first])
}
}
if largeIndex < len(large) {
num.WriteRune(' ')
num.WriteString(large[largeIndex])
}
iters++
}
return num.String()
}

View file

@ -2,6 +2,75 @@
package varfmt
func Sprintf(format string, args ...interface{}) string {
return ""
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()
}

View file

@ -9,49 +9,49 @@ import (
)
func TestFormat(t *testing.T) {
s := make([]interface{}, 1002)
s := make([]any, 1002)
s[10] = 1
s[100] = 2
s[1000] = 3
for _, tc := range []struct {
format string
args []interface{}
args []any
result string
}{
{
format: "{}",
args: []interface{}{0},
args: []any{0},
result: "0",
},
{
format: "{0} {0}",
args: []interface{}{1},
args: []any{1},
result: "1 1",
},
{
format: "{1} {5}",
args: []interface{}{0, 1, 2, 3, 4, 5, 6},
args: []any{0, 1, 2, 3, 4, 5, 6},
result: "1 5",
},
{
format: "{} {} {} {} {}",
args: []interface{}{0, 1, 2, 3, 4},
args: []any{0, 1, 2, 3, 4},
result: "0 1 2 3 4",
},
{
format: "{} {0} {0} {0} {}",
args: []interface{}{0, 1, 2, 3, 4},
args: []any{0, 1, 2, 3, 4},
result: "0 0 0 0 4",
},
{
format: "Hello, {2}",
args: []interface{}{0, 1, "World"},
args: []any{0, 1, "World"},
result: "Hello, World",
},
{
format: "He{2}o",
args: []interface{}{0, 1, "ll"},
args: []any{0, 1, "ll"},
result: "Hello",
},
{
@ -80,22 +80,22 @@ func BenchmarkFormat(b *testing.B) {
for _, tc := range []struct {
name string
format string
args []interface{}
args []any
}{
{
name: "small int",
format: "{}",
args: []interface{}{42},
args: []any{42},
},
{
name: "small string",
format: "{} {}",
args: []interface{}{"Hello", "World"},
args: []any{"Hello", "World"},
},
{
name: "big",
format: strings.Repeat("{0}{1}", 1000),
args: []interface{}{42, 43},
args: []any{42, 43},
},
} {
b.Run(tc.name, func(b *testing.B) {
@ -111,21 +111,21 @@ func BenchmarkSprintf(b *testing.B) {
for _, tc := range []struct {
name string
format string
args []interface{}
args []any
}{
{
name: "small",
format: "%d",
args: []interface{}{42},
args: []any{42},
},
{
name: "small string",
format: "%v %v",
args: []interface{}{"Hello", "World"},
args: []any{"Hello", "World"},
}, {
name: "big",
format: strings.Repeat("%[0]v%[1]v", 1000),
args: []interface{}{42, 43},
args: []any{42, 43},
},
} {
b.Run(tc.name, func(b *testing.B) {