2
\$\begingroup\$

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.

  1. Idiomatic Go style
  2. Usage of the Go standard library
  3. Performance improvments, while it's fast enough yet
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

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.

\$\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.