Problem 622

completed September 2, 2019

Comments

This problem was a fun one and it wasn’t too hard so I enjoyed it. It definitely didn’t keep me up all night so that was a plus! I ultimately solved it in three stages.

Stage one: brute force. First I created a func that given a deck size, would return the number of shuffles required to put it back to its original state. I was able to figure out the algorithm for this by doing a little trial and error on paper. Ends up it has to do with powers of 2 and modulo math. I let this run overnight (might as well right?) and found it continuing to chug away in the morning. Doing a bit of math, I found out that I was needing to get through each number up to 2 to the power of 60! Wow, that’s a big number and definitely not one you can brute force your way to. So I tried something else.

Stage two: reversing modulo. I had figured out that the algorithm had to do with modulus math. Instead of brute forcing, I needed to find a way to devise the answers. This meant reversing the modulo function. In other words, given a mod b = c we know that the answers are going to be in the form a = n*b + c where n is an integer. This solves for a, but I wanted to solve for b which ends up being b = (a - c) / n. I used this formua for a while and still found it to be incredibly slow.

Stage three: divisors of (a - c). I looked closer and realized that if n does not divide (a - c) evenly, then there is no point in trying it. Therefore, I tried creating a slice of all integers that divide (a - c) evenly and only trying those. I was worried that creating this divisor slice would take an insane amount of time. But it didn’t! And eventually I was lead to the answer in just 19 seconds of program time. Pretty awesome!

Was this my first problem solved in Golang? Maybe! Funtimes!

Code

package main

import (
	"fmt"
	"math"
)

// nextLocation gives the next location of a given card location.
func nextLocation(currentLocation, deckSize int) int {
	return (currentLocation * 2) % (deckSize - 1)
}

var (
	overageErr = fmt.Errorf("passed goal shuffle count")
	oddErr     = fmt.Errorf("deck size should not be odd")
)

// shufflesNeeded returns the total shuffles need to return a deck of size
// deckSize back to its original configuration.
func shufflesNeeded(deckSize, goal int) (int, error) {
	if deckSize%2 != 0 {
		return 0, oddErr
	}
	currentLocation := 1
	shuffles := 0
	for {
		shuffles++
		currentLocation = nextLocation(currentLocation, deckSize)
		if currentLocation == 1 {
			return shuffles, nil
		}
		if currentLocation == deckSize-2 {
			return shuffles * 2, nil
		}
		if shuffles > goal {
			return 0, overageErr
		}
	}
}

// getDivisors returns a slice of the divisors of num.
func getDivisors(num int) []int {
	halfWay := int(math.Sqrt(float64(num))) + 1
	d := []int{1, num}
	for i := 2; i < halfWay; i++ {
		if num%i == 0 {
			d = append(d, i)
			if div := num / i; div != i {
				d = append(d, div)
			}
		}
	}
	return d
}

// sumShufflesNeeded returns the total of all deck sizes that are possible to
// shuffler perfectly in goal times.
func sumShufflesNeeded(goal int) int {
	max := int(math.Pow(2, float64(goal))) - 1
	var totalDeckSizes int
	for _, n := range getDivisors(max) {
		i := (max / n) + 1
		if shuffles, _ := shufflesNeeded(i, goal); shuffles == goal {
			totalDeckSizes += i
		}
	}
	return totalDeckSizes
}

func main() {
	result := sumShufflesNeeded(60)
	fmt.Println(result)
}

Tests and Benchmarks

package main

import "testing"

func TestShufflesNeeded(t *testing.T) {
	testcases := []struct {
		deckSize    int
		maxShuffles int
		expected    int
	}{
		{deckSize: 52, maxShuffles: 100, expected: 8},
		{deckSize: 52, maxShuffles: 8, expected: 8},
		{deckSize: 86, maxShuffles: 100, expected: 8},
		{deckSize: 6, maxShuffles: 100, expected: 4},
		{deckSize: 8, maxShuffles: 100, expected: 3},
		{deckSize: 10, maxShuffles: 100, expected: 6},
		{deckSize: 12, maxShuffles: 100, expected: 10},
		{deckSize: 4, maxShuffles: 100, expected: 2},
		{deckSize: 12, maxShuffles: 2, expected: 0},
	}

	for _, test := range testcases {
		if actual, _ := shufflesNeeded(test.deckSize, test.maxShuffles); actual != test.expected {
			t.Errorf("incorrect shufflesNeeded for deck size %d: actual=%d expected=%d",
				test.deckSize, actual, test.expected)
		}
	}
}

func TestSumShufflesNeeded(t *testing.T) {
	testcases := []struct {
		goal     int
		expected int
	}{
		{goal: 3, expected: 8},
		{goal: 4, expected: 22},
		{goal: 5, expected: 32},
		{goal: 6, expected: 96},
		{goal: 7, expected: 128},
		{goal: 8, expected: 412},
		{goal: 9, expected: 586},
		{goal: 10, expected: 1506},
		{goal: 11, expected: 2162},
		{goal: 12, expected: 8628},
		{goal: 13, expected: 8192},
		{goal: 14, expected: 22402},
		{goal: 15, expected: 38878},
		{goal: 16, expected: 111032},
		{goal: 17, expected: 131072},
		{goal: 18, expected: 472936},
		{goal: 19, expected: 524288},
		{goal: 20, expected: 1998354},
		{goal: 21, expected: 2465922},
		{goal: 22, expected: 5907608},
		{goal: 23, expected: 8567138},
		{goal: 24, expected: 38044940},
		{goal: 25, expected: 34713702},
		{goal: 26, expected: 89513986},
		{goal: 27, expected: 155492948},
		{goal: 28, expected: 462252066},
	}

	for _, test := range testcases {
		if actual := sumShufflesNeeded(test.goal); actual != test.expected {
			t.Errorf("incorrect sumShufflesNeeded for goal %d: actual=%d expected=%d",
				test.goal, actual, test.expected)
		}
	}
}

func BenchmarkShufflesNeeded(b *testing.B) {
	b.ReportAllocs()
	for n := 0; n < b.N; n++ {
		shufflesNeeded(52, 8)
	}
}

func BenchmarkSumShufflesNeeded(b *testing.B) {
	b.ReportAllocs()
	for n := 0; n < b.N; n++ {
		sumShufflesNeeded(8)
	}
}