From adb8381b85cd5591cfb65d48859e210c65807e16 Mon Sep 17 00:00:00 2001 From: alexchao26 Date: Wed, 16 Dec 2020 18:11:34 -0500 Subject: [PATCH] 2017-day14: knot hash, num islands, disk defrag :) --- 2017/day14/main.go | 181 ++++++++++++++++++++++++++++++++++++++++ 2017/day14/main_test.go | 28 +++++++ 2 files changed, 209 insertions(+) create mode 100644 2017/day14/main.go create mode 100644 2017/day14/main_test.go diff --git a/2017/day14/main.go b/2017/day14/main.go new file mode 100644 index 0000000..50b4545 --- /dev/null +++ b/2017/day14/main.go @@ -0,0 +1,181 @@ +package main + +import ( + "flag" + "fmt" + "strconv" + "strings" + + "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) + + ans := diskDefrag(util.ReadFile("./input.txt"), part) + fmt.Println("Output:", ans) +} + +func diskDefrag(input string, part int) int { + // 128x128 grid, free (false) or used (true) + diskState := make([][]bool, 128) + for i := 0; i < 128; i++ { + // hash input = {puzzleInput}-{0 through 127} + hashKeyRaw := fmt.Sprintf("%s-%d", input, i) + hexHash := calcHexKnotHash(hashKeyRaw) + + // convert hex-hash to bits + bitsHash := transformHexToBits(hexHash) + + if len(bitsHash) != 128 { + panic(fmt.Sprintf("expected bit hash to be 128 chars long, got %d", len(bitsHash))) + } + + // set disk state for this row + diskState[i] = make([]bool, 128) + for b, bit := range bitsHash { + switch bit { + case '0': + diskState[i][b] = false // free + case '1': + diskState[i][b] = true // used + } + } + } + + if part == 1 { + // tally up used cells for part 1 + var usedCount int + for _, row := range diskState { + for _, b := range row { + if b { + usedCount++ + } + } + } + + return usedCount + } + + return numIslands(diskState) +} + +func parseInputASCII(input string) (ans []int) { + for _, char := range input { + ans = append(ans, int(char)) + } + // add default lengths to end + ans = append(ans, 17, 31, 73, 47, 23) + return ans +} + +func calcHexKnotHash(input string) string { + lengths := parseInputASCII(input) + nums := make([]int, 256) + for i := range nums { + nums[i] = i + } + var position, skipSize int + + // 64 rounds of hashing + for i := 0; i < 64; i++ { + for _, length := range lengths { + if length > 0 { + nums = reverse(nums, position, position+length-1) + } + position += skipSize + length + position %= len(nums) + skipSize++ + } + } + + var denseHash []int + for i := 0; i < 16; i++ { + var xord int + for j := i * 16; j < (i+1)*16; j++ { + xord ^= nums[j] + } + denseHash = append(denseHash, xord) + } + var hexdHash string + for _, dense := range denseHash { + // use %x to get hexadecimal version & 02 ensures leading 0 if needed + hexdHash += fmt.Sprintf("%02x", dense) + } + + return hexdHash +} + +// helper for knot hasher +func reverse(nums []int, left, right int) []int { + right %= len(nums) + if right < left { + right += len(nums) + } + + for left < right { + leftModded := left % len(nums) + rightModded := right % len(nums) + nums[leftModded], nums[rightModded] = nums[rightModded], nums[leftModded] + left++ + right-- + } + + return nums +} + +func transformHexToBits(hex string) string { + var bits string + for _, char := range strings.Split(hex, "") { + // parse hex (base 16) to an int type (always base 10) + baseTen, err := strconv.ParseInt(char, 16, 64) + if err != nil { + panic("strconv error " + err.Error()) + } + // add to bits string in binary form, 4 characters required + bits += fmt.Sprintf("%04b", baseTen) + } + return bits +} + +// for part 2, iterative algo for counting the number of connected islands +func numIslands(grid [][]bool) int { + var count int + + directions := [4][2]int{ + {0, 1}, + {0, -1}, + {1, 0}, + {-1, 0}, + } + for i := 0; i < len(grid); i++ { + for j := 0; j < len(grid[0]); j++ { + if grid[i][j] { + // zero out connected cells (i.e. zero out this island) + queue := [][2]int{{i, j}} + for len(queue) > 0 { + // pop off queue + currentRow, currentCol := queue[0][0], queue[0][1] + grid[currentRow][currentCol] = false + queue = queue[1:] + + // check neighbors, add to queue if true + for _, dir := range directions { + nextRow, nextCol := currentRow+dir[0], currentCol+dir[1] + if nextRow >= 0 && nextRow < len(grid) && nextCol >= 0 && nextCol < len(grid[0]) { + if grid[nextRow][nextCol] { + queue = append(queue, [2]int{nextRow, nextCol}) + } + } + } + } + count++ + } + } + } + + return count +} diff --git a/2017/day14/main_test.go b/2017/day14/main_test.go new file mode 100644 index 0000000..0e5cc5f --- /dev/null +++ b/2017/day14/main_test.go @@ -0,0 +1,28 @@ +package main + +import ( + "testing" + + "github.com/alexchao26/advent-of-code-go/util" +) + +func Test_diskDefrag(t *testing.T) { + tests := []struct { + name string + input string + part int + want int + }{ + {"example_part1", "flqrgnkx", 1, 8108}, + {"actual_part1", util.ReadFile("input.txt"), 1, 8204}, + {"example_part2", "flqrgnkx", 2, 1242}, + // {"actual_part2", util.ReadFile("input.txt"), 2, 1089}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := diskDefrag(tt.input, tt.part); got != tt.want { + t.Errorf("diskDefrag() = %v, want %v", got, tt.want) + } + }) + } +}