I'm pretty sure this can be written as a couple of lines, but I'm unable to come up with a better solution.
package main
import (
"fmt"
"math/big"
)
func colonedSerial(i *big.Int) string {
hex := fmt.Sprintf("%x", i)
if len(hex)%2 == 1 {
hex = "0" + hex
}
numberOfColons := len(hex)/2 - 1
colonedHex := make([]byte, len(hex)+numberOfColons)
for i, j := 0, 0; i < len(hex)-1; i, j = i+1, j+1 {
colonedHex[j] = hex[i]
if i%2 == 1 {
j++
colonedHex[j] = []byte(":")[0]
}
}
// we skipped the last one to avoid the colon at the end
colonedHex[len(colonedHex)-1] = hex[len(hex)-1]
return string(colonedHex)
}
func main() {
fmt.Println(colonedSerial(big.NewInt(1234)))
// "04:d2"
fmt.Println(colonedSerial(big.NewInt(123456)))
// "01:e2:40"
fmt.Println(colonedSerial(big.NewInt(1234567891011121314)))
// "11:22:10:f4:b2:d2:30:a2"
}
Also the []byte(":")[0]
is very ugly. Is there a better way to do that? (I did not wanted to write the ASCII value of colon (58) as it would be a magic number).
Go Playground version: https://play.golang.org/p/GTxajcr3NY
2 Answers 2
In Go, we usually expect reasonable performance and brevity. For example,
package main
import (
"encoding/hex"
"fmt"
"math/big"
)
func formatSerial(serial *big.Int) string {
b := serial.Bytes()
buf := make([]byte, 0, 3*len(b))
x := buf[1*len(b) : 3*len(b)]
hex.Encode(x, b)
for i := 0; i < len(x); i += 2 {
buf = append(buf, x[i], x[i+1], ':')
}
return string(buf[:len(buf)-1])
}
func main() {
fmt.Println(formatSerial(big.NewInt(1234)))
// "04:d2"
fmt.Println(formatSerial(big.NewInt(123456)))
// "01:e2:40"
fmt.Println(formatSerial(big.NewInt(1234567891011121314)))
// "11:22:10:f4:b2:d2:30:a2"
}
Output:
04:d2
01:e2:40
11:22:10:f4:b2:d2:30:a2
Benchmark: serial := big.NewInt(1234567891011121314)
:
BenchmarkPeterSO 3000000 552 ns/op 72 B/op 3 allocs/op
BenchmarkKissgyorgy 500000 2545 ns/op 120 B/op 7 allocs/op
Benchmark200Success 30000 40675 ns/op 40675 B/op 35 allocs/op
-
1\$\begingroup\$ oh wow. I didn't wanted to use append to avoid multiple allocations, but the cap trick is nice! but how is yours so much faster than mine? what are those allocations in my version? I explicitly wanted to avoid those, that's whhy I made a byte slice and not string concatenation \$\endgroup\$kissgyorgy– kissgyorgy2017年06月14日 06:22:54 +00:00Commented Jun 14, 2017 at 6:22
-
2\$\begingroup\$ @kissgyorgy: When you write
fmt.Sprintf("%x", i)
, it interprets the format string and, by reflection, inspects the format variables at run time. Read the Go code forsrc/fmt/format.go
andsrc/fmt/print.go
. For example, try replacingfmt.Sprintf("%x", i)
withi.Text(16)
. My code is compile-time code that does close to the minimum. \$\endgroup\$peterSO– peterSO2017年06月14日 18:14:27 +00:00Commented Jun 14, 2017 at 18:14 -
1\$\begingroup\$ Ok I just understood your version now. You use only one array, store the hex elements from the middle and store the final from the start reusing the full array at the end. I like it very much, thanks! \$\endgroup\$kissgyorgy– kissgyorgy2017年06月14日 21:54:29 +00:00Commented Jun 14, 2017 at 21:54
Since the task mainly involves inserting a colon after every two hex digits, I would treat it as a search-and-replace operation, using a regular expression.
import (
"fmt"
"math/big"
"regexp"
"strings"
)
func colonedSerial(i *big.Int) string {
re := regexp.MustCompile("..")
hex := fmt.Sprintf("%x", i)
if len(hex)%2 == 1 {
hex = "0" + hex
}
return strings.TrimRight(re.ReplaceAllString(hex, "0ドル:"), ":")
}
-
\$\begingroup\$ This is actually really nice! \$\endgroup\$kissgyorgy– kissgyorgy2017年06月14日 06:27:17 +00:00Commented Jun 14, 2017 at 6:27