I tried to solve the Advent of Code quiz for day 5 in Go, since my brute force algorithm wasn't efficient in Ruby. I solved the first part of the quiz with the following Go code.
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
)
func main() {
fmt.Println("Password:", extractPassword("abc"))
}
func extractPassword(roomId string) string {
position := 0
i := 0
password := make([]rune, 8)
for position < 8 {
letter := evaluateIndex(roomId, i)
if 0 != letter {
fmt.Printf("Found %c for index %d\n", letter, i)
password[position] = letter
position++
}
i++
}
return string(password)
}
func evaluateIndex(roomId string, index int) rune {
text := fmt.Sprintf("%s%d", roomId, index)
hash := md5Hash(text)
if strings.HasPrefix(hash, "00000") {
return []rune(hash)[5]
} else {
return 0
}
}
func md5Hash(text string) string {
hash := md5.Sum([]byte(text))
return hex.EncodeToString(hash[:])
}
My biggest issue was the string vs rune conflict. I'm interested in following points.
- Idiomatic Go style
- Usage of the Go standard library
- Performance improvments, while it's fast enough yet
1 Answer 1
Your code is pretty idiomatic. The only obvious style thing I'd change is the end of the evaluateIndex
function:
if strings.HasPrefix(hash, "00000") {
return []rune(hash)[5]
}
return 0
you could also write if letter := evaluateIndex(roomId, i); letter != 0 {
but it's not that important.
Now, the rune/string/byte question is the interesting one. md5
deals with []byte
only; and converting strings to bytes and back is expensive (new memory allocations each time). So I'd change your code to use []byte
instead of string
, and byte
instead of rune
. The evaluateIndex
function becomes faster:
var prefix = []byte("00000")
func evaluateIndex(roomId []byte, index int) byte {
bytes := append(roomId, []byte(fmt.Sprint(index))...)
hash := md5Hash(bytes)
if bytes.HasPrefix(hash, prefix) {
return hash[5]
}
return 0
}
func md5Hash(bytes []byte) []byte {
rawHash := md5.Sum(bytes)
encHash := make([]byte, md5.EncodedLen(len(rawHash)))
hex.Encode(encHash, rawHash)
return encHash
}
I put zeroes outside the func, to avoid converting it over and over. You still have a few conversions & allocations, but there's no way around them. The other functions can be modified easily to accommodate the type change.