diff --git a/day-3/go.mod b/day-3/go.mod new file mode 100644 index 0000000..0604fa0 --- /dev/null +++ b/day-3/go.mod @@ -0,0 +1,5 @@ +module github.com/onyx-and-iris/aoc2023/day-3 + +go 1.20 + +require github.com/go-playground/assert/v2 v2.2.0 diff --git a/day-3/go.sum b/day-3/go.sum new file mode 100644 index 0000000..1998fd7 --- /dev/null +++ b/day-3/go.sum @@ -0,0 +1,2 @@ +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= diff --git a/day-3/one.go b/day-3/one.go new file mode 100644 index 0000000..e49c8dc --- /dev/null +++ b/day-3/one.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "strconv" + "unicode" +) + +var symbols = [][]bool{} + +// enginePart represents a single engine part +type enginePart struct { + digits []digit +} + +// realValue returns the numeric value of all digits combined +func (e *enginePart) realValue() (int, error) { + var numStr string + for _, digit := range e.digits { + numStr += fmt.Sprintf("%d", digit.raw) + } + n, err := strconv.Atoi(numStr) + if err != nil { + return 0, err + } + return n, nil +} + +// digit represents a single digit in an engine part +type digit struct { + row int + col int + raw int + pass bool +} + +// checkDigit checks if any neighbouring characters are true (in symbol matrix) +func checkDigit(d digit) digit { + i := d.col - 1 + for ; i < len(symbols[d.row]) && i <= d.col+1; i += 1 { + if i < 0 { + continue + } + if d.row != 0 { + if symbols[d.row-1][i] { + d.pass = true + break + } + } + if symbols[d.row][i] { + d.pass = true + break + } + if d.row != len(symbols)-1 { + if symbols[d.row+1][i] { + d.pass = true + break + } + } + } + return d +} + +// checkDigits runs each digit in an engine part through checkDigit() +func checkDigits(i, j int, line string) (int, int, error) { + enginePart := enginePart{} + for ; j < len(line); j += 1 { + if !unicode.IsNumber(rune(line[j])) { + break + } + enginePart.digits = append(enginePart.digits, checkDigit(digit{row: i, col: j, raw: int(line[j]) - '0', pass: false})) + } + + if anyTrue(enginePart.digits) { + engineParts[i] = append(engineParts[i], enginePart) + n, err := enginePart.realValue() + if err != nil { + return 0, 0, err + } + return j, n, nil + } + return j, 0, nil +} + +// symbolToBool generates a boolean matrix that represents the placement of symbols +func symbolToBool(line string) []bool { + bool_arr := []bool{} + for _, c := range line { + bool_arr = append(bool_arr, !(unicode.IsNumber(c) || c == '.')) + } + return bool_arr +} + +// one deciphers which numbers represent engine parts +// it returns the sum of all engine parts +func one(lines []string) (int, error) { + for _, line := range lines { + symbols = append(symbols, symbolToBool(line)) + } + engineParts = make([][]enginePart, len(lines)) + + sum := 0 + for i, line := range lines { + for j := 0; j < len(line); j += 1 { + if unicode.IsNumber(rune(line[j])) { + next, n, err := checkDigits(i, j, line) + if err != nil { + return 0, err + } + sum += n + j = next + } + } + } + + return sum, nil +} diff --git a/day-3/one_test.go b/day-3/one_test.go new file mode 100644 index 0000000..97265cc --- /dev/null +++ b/day-3/one_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "testing" + + "github.com/go-playground/assert/v2" +) + +func TestSymbolToBool(t *testing.T) { + //t.Skip("skipping test") + input := []string{ + "467..114..", + "...*......", + "..35..633.", + "......#...", + } + + expected := [][]bool{ + {false, false, false, false, false, false, false, false, false, false}, + {false, false, false, true, false, false, false, false, false, false}, + {false, false, false, false, false, false, false, false, false, false}, + {false, false, false, false, false, false, true, false, false, false}, + } + + bool_matrix := [][]bool{} + for _, line := range input { + bool_matrix = append(bool_matrix, symbolToBool(line)) + } + + t.Run("Should produce equal boolean matrices", func(t *testing.T) { + assert.Equal(t, expected, bool_matrix) + }) +} + +func TestCheckDigit(t *testing.T) { + //t.Skip("skipping test") +} diff --git a/day-3/run.sh b/day-3/run.sh new file mode 100755 index 0000000..3f6776a --- /dev/null +++ b/day-3/run.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +INPUT="input.txt" + +cat $INPUT | go run . \ No newline at end of file diff --git a/day-3/solution.go b/day-3/solution.go new file mode 100644 index 0000000..3ea39e6 --- /dev/null +++ b/day-3/solution.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "log" +) + +func main() { + lines := readlines() + + ans, err := one(lines) + if err != nil { + log.Fatal(err) + } + fmt.Printf("solution one: %d\n", ans) + + ans, err = two(lines) + if err != nil { + log.Fatal(err) + } + fmt.Printf("solution two: %d\n", ans) +} diff --git a/day-3/two.go b/day-3/two.go new file mode 100644 index 0000000..c4b0723 --- /dev/null +++ b/day-3/two.go @@ -0,0 +1,55 @@ +package main + +var engineParts [][]enginePart + +// getGearRatio returns the product of engine parts if exactly two neighbour a gear +func getGearRatio(row, col int) (int, error) { + partsConsidered := []enginePart{} + + i := row - 1 + for ; i < len(engineParts) && i <= row+1; i += 1 { + if i < 0 { + continue + } + for _, enginePart := range engineParts[i] { + for _, part := range enginePart.digits { + if part.col == col-1 || part.col == col || part.col == col+1 { + if anyTrue(enginePart.digits) { + partsConsidered = append(partsConsidered, enginePart) + break + } + } + } + } + } + if len(partsConsidered) == 2 { + n1, err := partsConsidered[0].realValue() + if err != nil { + return 0, err + } + n2, err := partsConsidered[1].realValue() + if err != nil { + return 0, err + } + return n1 * n2, nil + } + return 0, nil +} + +// two returns the sum of all gear ratios +func two(lines []string) (int, error) { + sum := 0 + for i, line := range lines { + for j, c := range line { + if rune(c) == '*' { + n, err := getGearRatio(i, j) + if err != nil { + return 0, err + } + sum += n + } + } + } + + return sum, nil +} diff --git a/day-3/util.go b/day-3/util.go new file mode 100644 index 0000000..d0c53d3 --- /dev/null +++ b/day-3/util.go @@ -0,0 +1,34 @@ +package main + +import ( + "bufio" + "log" + "os" +) + +// readlines reads lines from stdin. +// Then it returns them as an array of strings +func readlines() []string { + lines := []string{} + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + return lines +} + +// anyTrue checks if any digits passed +func anyTrue(digits []digit) bool { + for _, digit := range digits { + if digit.pass { + return true + } + } + return false +}