Day 6: Memory Reallocation

Notice: 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.

Day 6, and we keep on going! Welcome back for another puzzle and some Go. You know the drill by know: if you haven’t done so yet, go and read the problem description. Go on, I’ll wait here until you’re ready.

Good, you’re back! Let’s get started. On the agenda for today: functions as objects.

Again, like yesterday, today’s puzzle is relatively easy. Let’s start out with the pieces of Go we already know:

// Define a banks type as a slice of ints
type banks []int

// A function that finds the bank with highest value
func (b banks) max() (int, int) {
	maxIdx := 0
	max := b[0]
	for i := 1; i < len(b); i++ {
		if b[i] > max {
			max = b[i]
			maxIdx = i
		}
	}
	return maxIdx, max
}

// A function that redistributes the highest value
// across all banks
func (b banks) redistribute() {
	idx, val := b.max()

	// set the source bank to 0
	b[idx] = 0
	idx = (idx + 1) % len(b)

	// distribute val over the next banks
	// wrapping around at the end
	for ; val > 0; val-- {
		b[idx]++
		idx = (idx + 1) % len(b)
	}
}

// A function that transforms the banks into a string.
func (b banks) String() string {
	return fmt.Sprintf("%v", []int(b))
}

Did I lie? That does look relatively easy, right? We’ve got the redistribution functionality, now we only have to repeat it until we encounter a state we saw before. That’s where the String function above comes in. We don’t want to copy the slice after each redistribution. We don’t want to loop over each previous slice to check if we have already seen a slice. Checking if two strings are equal is so much easier. And we can use the string representation of the slice as a key in a set or map. So let’s use a map[string]int to keep track of which states we saw and when we saw them:

func main() {
	b := banks([]int{...})

	steps := 0

	set := make(map[string]int)

	seen := func(s string) bool {
		_, ok := set[s]
		return ok
	}

	for !seen(b.String()) {
		set[b.String()] = steps
		b.redistribute()
		steps++
	}

	fmt.Println("Part A", steps)
	fmt.Println("Part B", steps-set[b.String()])
}

So what’s up with that seen variable? Well, Go has support for first-class functions, which means functions can be treated as regular types and objects.

We can define a type Validator as a function that takes a string and returns a bool

type Validator func(s string) bool

We can pass the functions as arguments to other functions, which in turn can execute them:

func Run(v Validator) {
	s := "my test string"
	b := v(s)
	fmt.Println("Was string valid:", b)
}

We could return them anonymously from another function, creating a closure (more on that later, probably).

func MakeValidator(i int) Validator {
	return function(s string) bool {
		return len(s) > i
	}
}

Or we can define them inside another function, like main and assign them to an object seen. The cool thing about this, is that seen can access all objects that are defined within the scope of main. It has access to the set that keeps track of seen states! That makes it possible to use the seen function as the condition of the while loop. Nice!

That’s it for today. I know first-class functions may be a bit complex at first. Let it sink in, or go play around with them. There will probably be more examples in the next puzzles. As usual, my full solution is available in my github repository. See you all tomorrow!