5
\$\begingroup\$

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

\$\endgroup\$
3
\$\begingroup\$

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
\$\endgroup\$
  • 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 Jun 14 '17 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 for src/fmt/format.go and src/fmt/print.go. For example, try replacing fmt.Sprintf("%x", i) with i.Text(16). My code is compile-time code that does close to the minimum. \$\endgroup\$ – peterSO Jun 14 '17 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 Jun 14 '17 at 21:54
2
\$\begingroup\$

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:"), ":")
}
\$\endgroup\$
  • \$\begingroup\$ This is actually really nice! \$\endgroup\$ – kissgyorgy Jun 14 '17 at 6:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.