Go version of Advent of Code, day 5 - Dealing with runes 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 &lt; 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
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 &amp; allocations, but there's no way around them. The other functions can be modified easily to accommodate the type change.