3
\$\begingroup\$

I want to convert int64 to custom base 64 number string, with this specification:

0..9   -> 0..9
10..35 -> a..z
36..61 -> A..Z
62     -> _
63     -> -

Is this implementation correct/efficient?

func EncodeLogKey(id int64) string {
  str := make([]byte,0,12)
  if id == 0 { return "0" }
  for id>0 {
    var ch rune
    mod := rune(id % 64)
    if mod <= 10 {
      ch = '0' + mod
    } else if mod <= 10+26 {
      ch = 'a' + (mod - 10)
    } else if mod <= 10+26+26 {
      ch = 'A' + (mod - 10 - 26)
    } else if mod % 2 == 0 {
      ch = '_'
    } else {
      ch = '-'
    }
    str = append(str,byte(ch))
    id /= 64
  }
  return string(str)
}

func DecodeLogKey(logKey string) (int64, error) {
  res := int64(0)
  for ch := range logKey {
    res *= 64
    mod := int64(ch)
    if ch >= '0' && ch <= '9' {
      res += (mod - '0')
    } else if ch >= 'a' && ch <= 'z' {
      res += 10 + (mod - 'a')
    } else if ch >= 'A' && ch <= 'Z' {
      res += 10 + 26 + (mod - 'A')
    } else if ch == '_' {
      res += 10 + 26 + 26
    } else if ch == '-' {
      res += 10 + 26 + 26 + 1
    } else {
      return res, fmt.Errorf("Invalid logKey character: '%c'", res)
    }
  }
  return res, nil
}
\$\endgroup\$
2
\$\begingroup\$

Your solution as given has a few problems. Firstly, DecodeLogKey doesn't work, as you're ranging over the index of logKey rather than the value of logKey. Also, the range will start from the first character in the string, and for what you're doing, you need it to start from the last character in the string.

I'd suggest a solution like this:

var codes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-"

func EncodeLogKey(id int64) string {
    str := make([]byte, 0, 12)
    if id == 0 {
        return "0"
    }
    for id > 0 {
        ch := codes[id%64]
        str = append(str, byte(ch))
        id /= 64
    }
    return string(str)
}

func DecodeLogKey(logKey string) (int64, error) {
    res := int64(0)

    for i := len(logKey); i > 0; i-- {
        ch := logKey[i-1]
        res *= 64
        mod := strings.IndexRune(codes, rune(ch))
        if mod == -1 {
            return -1, fmt.Errorf("Invalid logKey character: '%c'", ch)
        }
        res += int64(mod)
    }
    return res, nil
}

That way you can easily change the character set you're working with, and the code becomes a lot more readable. It could be made more flexible by using len(codes) as the base rather than assuming it's 64.

You still need to include checks for non-Latin1 UTF-8 characters such as if you want to be really thorough.

In terms of testing for correctness, you should write unit tests (preferably table driven) to be sure that your code is working correctly. Dave Cheney has a good post on Writing table driven tests in Go that you should read.

Play for my solution

\$\endgroup\$

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.