From 46a418393c66ff0855d6339e0a8cd62c3f37cbf4 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Thu, 24 Dec 2020 00:45:35 -0500 Subject: [PATCH] 2020-day24: hex coordinates are tough. 1240/668 --- 2020/day24/main.go | 143 +++++++++++++++++++++++++++++++++------- 2020/day24/main_test.go | 48 ++++++++------ 2 files changed, 149 insertions(+), 42 deletions(-) diff --git a/2020/day24/main.go b/2020/day24/main.go index bde80ba..df1cfad 100644 --- a/2020/day24/main.go +++ b/2020/day24/main.go @@ -15,32 +15,129 @@ func main() { flag.Parse() fmt.Println("Running part", part) - if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) + ans := part1(util.ReadFile("./input.txt"), part) + fmt.Println("Output:", ans) +} + +// 1240/668 +func part1(input string, part int) int { + instructions := strings.Split(input, "\n") + tileIsBlack := map[[6]int]bool{} + for _, inst := range instructions { + // tally the steps taken in each of the hex directions + var tallyDirections [6]int + + // iterate thorugh the NONDELIMITED instructions + for i := 0; i < len(inst); { + // set the direction to the next two characters if possible + dir := string(inst[i]) + if i+2 <= len(inst) { + dir = inst[i : i+2] + } + + // if the two character direction is se, sw, nw or ne, both characters + // must be used, because using one is invalid ("n" or "s") + switch dir { + case "se", "sw", "nw", "ne": + tallyDirections[dirIndices[dir]]++ + i += 2 + default: + tallyDirections[dirIndices[dir[0:1]]]++ + i++ + } + // collapse and directions that cancel out, such as sw/nw -> w + tallyDirections = zeroOutHexDirections(tallyDirections) + } + // flip that tile + tileIsBlack[tallyDirections] = !tileIsBlack[tallyDirections] } -} -func part1(input string) int { - parsed := parseInput(input) - _ = parsed + // for part 2, play game of life 100 times + if part == 2 { + for i := 0; i < 100; i++ { + // flip based on neighbors + nextState := map[[6]int]bool{} - return 0 -} + // collect all coordinates to check + toCheck := map[[6]int]bool{} + for i := 0; i < 6; i++ { + for k := range tileIsBlack { + k[i]++ + toCheck[zeroOutHexDirections(k)] = true + } + } -func part2(input string) int { - return 0 -} - -func parseInput(input string) (ans []int) { - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, mathutil.StrToInt(l)) + for coord := range toCheck { + // count neighbors + var neighbors int + for i := 0; i < 6; i++ { + clone := coord // don't want to modify the original coord + clone[i]++ // generates the six directions around coord + clone = zeroOutHexDirections(clone) + if tileIsBlack[clone] { + neighbors++ + } + } + // flipping logic: + // back with zero or more than 2 neighbors becomes white + if tileIsBlack[coord] && (neighbors == 0 || neighbors > 2) { + nextState[coord] = false + } else if !tileIsBlack[coord] && neighbors == 2 { + // white with exactly 2 neighbors becomes black + nextState[coord] = true + } else { + // stays the same + nextState[coord] = tileIsBlack[coord] + } + } + tileIsBlack = nextState + } } - return ans + + var count int + for _, b := range tileIsBlack { + if b { + count++ + } + } + + return count + +} + +var dirIndices = map[string]int{ + "e": 0, + "se": 1, + "sw": 2, + "w": 3, + "nw": 4, + "ne": 5, +} + +// borrowed from my 2017 day 11 code which calculated hex coordinate manhattan distances +func zeroOutHexDirections(tally [6]int) [6]int { + // zero out opposite indices + for i := range tally { + if tally[i] != 0 { + oppositeIndex := (i + 3) % 6 + smaller := mathutil.MinInt(tally[oppositeIndex], tally[i]) + tally[oppositeIndex] -= smaller + tally[i] -= smaller + } + } + + // handle neighbors which collapse into the current direction + // e.g. sw,se == s + for i := range tally { + toLeft := (i + 5) % 6 + toRight := (i + 1) % 6 + if tally[toLeft] > 0 && tally[toRight] > 0 { + smaller := mathutil.MinInt(tally[toLeft], tally[toRight]) + tally[toLeft] -= smaller + tally[toRight] -= smaller + tally[i] += smaller + } + } + + return tally } diff --git a/2020/day24/main_test.go b/2020/day24/main_test.go index 22a4487..54a2691 100644 --- a/2020/day24/main_test.go +++ b/2020/day24/main_test.go @@ -2,38 +2,48 @@ package main import ( "testing" + + "github.com/alexchao26/advent-of-code-go/util" ) +var example = `sesenwnenenewseeswwswswwnenewsewsw +neeenesenwnwwswnenewnwwsewnenwseswesw +seswneswswsenwwnwse +nwnwneseeswswnenewneswwnewseswneseene +swweswneswnenwsewnwneneseenw +eesenwseswswnenwswnwnwsewwnwsene +sewnenenenesenwsewnenwwwse +wenwwweseeeweswwwnwwe +wsweesenenewnwwnwsenewsenwwsesesenwne +neeswseenwwswnwswswnw +nenwswwsewswnenenewsenwsenwnesesenew +enewnwewneswsewnwswenweswnenwsenwsw +sweneswneswneneenwnewenewwneswswnese +swwesenesewenwneswnwwneseswwne +enesenwswwswneneswsenwnewswseenwsese +wnwnesenesenenwwnenwsewesewsesesew +nenewswnwewswnenesenwnesewesw +eneswnwswnwsenenwnwnwwseeswneewsenese +neswnwewnwnwseenwseesewsenwsweewe +wseweeenwnesenwwwswnew` + func Test_part1(t *testing.T) { tests := []struct { name string input string + part int want int }{ - // {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER}, + {"part1_example", example, 1, 10}, + {"part1_actual", util.ReadFile("input.txt"), 1, 277}, + {"part2_example", example, 2, 2208}, + {"part2_actual", util.ReadFile("input.txt"), 2, 3531}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { + if got := part1(tt.input, tt.part); got != tt.want { t.Errorf("part1() = %v, want %v", got, tt.want) } }) } } - -func Test_part2(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - // {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) - } - }) - } -}