Day 10: Knot Hash
# 10 Dec 2017Notice: If you have coding experience, I highly recommend to try and solve the puzzle in a language you already know. It's a lot of fun and you'll be able to focus on Go more later on.
How good are you at following the instructions? Let’s give it a try: go and read the problem description. Go on, I’ll wait here until you’re ready.
Good, you’re back! Unfortunately, there isn’t a lot of new Go on todays agenda. The problem literally boils down to following the instructions to the letter.
There isn’t much to say about this. The functions follow the steps in the problem description quite closely:
- Reverse reverses the ranges for a certain amount of rounds
- Densify collapses the sparse list of 256 to 16 items
- Hex converts the dense list to a hex string
package main
import "fmt"
var HEXCHAR = []byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
type list []byte
func (l list) reverse(pos, length int) {
for i := 0; i < length/2; i++ {
a := (pos + i) % len(l)
b := (pos + length - 1 - i) % len(l)
l[a], l[b] = l[b], l[a]
}
}
func (l list) Reverse(lengths []int, rounds int) list {
pos := 0
skip := 0
for i := 0; i < rounds; i++ {
for _, length := range lengths {
l.reverse(pos, length)
pos += length + skip
skip++
}
}
return l
}
func (l list) Densify() list {
d := make(list, 16)
idx := 0
for i := 0; i < 16; i++ {
var b byte
for j := 0; j < 16; j++ {
b ^= l[idx]
idx++
}
d[i] = b
}
return d
}
func (l list) Hex() string {
b := make([]byte, 32)
for i, v := range l {
b[2*i] = HEXCHAR[v/16]
b[2*i+1] = HEXCHAR[v%16]
}
return string(b)
}
func main() {
l := make(list, 256)
for i := 0; i < 256; i++ {
l[i] = byte(i)
}
lengths := []int{...}
l.Reverse(lengths, 1)
// Multiply as ints to avoid byte overflow
fmt.Println("Part A:", int(l[0])*int(l[1]))
// reset the list
for i := 0; i < 256; i++ {
l[i] = byte(i)
}
input := "..."
lengths = make([]int, len(input), len(input)+5)
lengths = append(lengths, 17, 31, 73, 47, 23)
for i, b := range input {
lengths[i] = int(b)
}
h := l.Reverse(lengths, 64).Densify().Hex()
fmt.Printf("Part B: %s\n", h)
}
If I had to point out one language feature, it’s the use of the slice length and capacity when creating the second length. By providing a length, we’re creating a slice with that amount of zeros. The capacity makes sure the underlying memory block has enough space to add 5 more numbers. It allows us to add all remaining 5 integers efficiently in a single line. If you would print lengths
after every step, with an input length of 10, you would see something like this:
// create the slice
length = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] (len=10, cap=15)
// append the last five numbers
length = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 31, 73, 47, 23] (len=15, cap=15)
// set the translated input
length = [1, 2, 4, 5, 3, 8, 12, 42, 45, 23, 17, 31, 73, 47, 23] (len=15, cap=15)
Read through it, process it, understand it. Tomorrow we’re back for another puzzle. As usual, my full solution is available in my github repository. See you all tomorrow!