From ec9a15afbe9110226df55daa6da9709039932e01 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Sat, 19 Dec 2020 21:51:28 -0500 Subject: [PATCH] 2017-day21: sudoku-style grid plus game of life... --- 2017/day21/main.go | 147 ++++++++++++++++++++++++++++++++++++++++ 2017/day21/main_test.go | 35 ++++++++++ 2 files changed, 182 insertions(+) create mode 100644 2017/day21/main.go create mode 100644 2017/day21/main_test.go diff --git a/2017/day21/main.go b/2017/day21/main.go new file mode 100644 index 0000000..04b07e2 --- /dev/null +++ b/2017/day21/main.go @@ -0,0 +1,147 @@ +package main + +import ( + "flag" + "fmt" + "strings" + + "github.com/alexchao26/advent-of-code-go/mathutil" + + "github.com/alexchao26/advent-of-code-go/algos" + + "github.com/alexchao26/advent-of-code-go/util" +) + +func main() { + var part int + flag.IntVar(&part, "part", 1, "part 1 or 2") + flag.Parse() + fmt.Println("Running part", part) + + var ans int + if part == 1 { + ans = fractalArt(util.ReadFile("./input.txt"), 5) + } else { + ans = fractalArt(util.ReadFile("./input.txt"), 18) + } + fmt.Println("Output:", ans) +} + +var startingPattern = `.#. +..# +###` + +func fractalArt(input string, rounds int) int { + var state [][]string + for _, line := range strings.Split(startingPattern, "\n") { + state = append(state, strings.Split(line, "")) + } + + rules := parseInput(input) + + for i := 0; i < rounds; i++ { + state = tick(state, rules) + } + + var count int + for _, row := range state { + for _, v := range row { + if v == "#" { + count++ + } + } + } + return count +} + +func parseInput(input string) map[string][][]string { + // some helper functions for generating the rules map + // need to parse the left sides of the enhancement rules + // then helper functions that rotate them (util.RotateStringGrid()) and + // one to mirror image it + makeGridFromString := func(str string) [][]string { + var grid [][]string + for _, line := range strings.Split(str, "/") { + grid = append(grid, strings.Split(line, "")) + } + return grid + } + stringifyGrid := func(grid [][]string) (str string) { + for _, row := range grid { + for _, v := range row { + str += v + } + } + return str + } + mirrorImageGrid := func(grid [][]string) (flipped [][]string) { + for i := range grid { + flipped = append(flipped, []string{}) + for j := len(grid[i]) - 1; j >= 0; j-- { + flipped[i] = append(flipped[i], grid[i][j]) + } + } + return flipped + } + rules := map[string][][]string{} + for _, line := range strings.Split(input, "\n") { + parts := strings.Split(line, " => ") + keyGrid := makeGridFromString(parts[0]) + resultGrid := makeGridFromString(parts[1]) + + for i := 0; i < 4; i++ { + keyGrid = algos.RotateStringGrid(keyGrid) + rules[stringifyGrid(keyGrid)] = resultGrid + rules[stringifyGrid((mirrorImageGrid(keyGrid)))] = resultGrid + } + } + return rules +} + +func tick(grid [][]string, rules map[string][][]string) [][]string { + var nextState [][]string + + // determine the size of break up the grid by. prioritize 2x2 grids + var edgeSize int + if len(grid)%2 == 0 { + edgeSize = 2 + } else if len(grid)%3 == 0 { + edgeSize = 3 + } else { + panic("grid is not evenly divisible by 2 or 3, got " + mathutil.IntToStr(len(grid))) + } + + // iterate over like a sudoku grid, r and c iterate over the top left corner + // of each sub-square + for r := 0; r < len(grid); r += edgeSize { + // a new row of sub-squares is being iterated over, add edgeSize+1 number + // of empty slices onto the nextState grid + for i := 0; i < edgeSize+1; i++ { + nextState = append(nextState, []string{}) + } + for c := 0; c < len(grid[0]); c += edgeSize { + // generate the string to match a key in the rules map + var strToMatch string + for i := 0; i < edgeSize; i++ { + for j := 0; j < edgeSize; j++ { + // r+i and c+j point at coords within the original grid + strToMatch += grid[r+i][c+j] + } + } + + // finding the result of the enhancement rule for the string to match + resulting, ok := rules[strToMatch] + if !ok { + panic("No matching pattern found for " + strToMatch) + } + + // append the values from the result onto the appropriate nextState row + for i, vals := range resulting { + nextStateIndex := len(nextState) - edgeSize - 1 + i + nextState[nextStateIndex] = append(nextState[nextStateIndex], vals...) + } + } + } + + return nextState +} diff --git a/2017/day21/main_test.go b/2017/day21/main_test.go new file mode 100644 index 0000000..b567127 --- /dev/null +++ b/2017/day21/main_test.go @@ -0,0 +1,35 @@ +package main + +import ( + "testing" + + "github.com/alexchao26/advent-of-code-go/util" +) + +var example = `../.# => ##./#../... +.#./..#/### => #..#/..../..../#..#` + +func Test_fractalArt(t *testing.T) { + type args struct { + input string + rounds int + } + tests := []struct { + name string + args args + want int + }{ + {"example", args{example, 2}, 12}, + {"actual_part1", args{util.ReadFile("input.txt"), 5}, 194}, + {"actual_part2", args{util.ReadFile("input.txt"), 18}, 2536879}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := fractalArt(tt.args.input, tt.args.rounds); got != tt.want { + t.Logf("Ruleset: %s", tt.args.input) + t.Logf("Rounds: %d", tt.args.rounds) + t.Errorf("fractalArt() = %v, want %v", got, tt.want) + } + }) + } +}