starting 2023

This commit is contained in:
alexchao26
2024-01-22 23:37:55 -05:00
parent 615f16cc15
commit eb16913f7c
6 changed files with 621 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
package main
import (
_ "embed"
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/cast"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
var sum int
for _, line := range strings.Split(input, "\n") {
var tens, ones int
for i := 0; i < len(line); i++ {
if strings.ContainsAny(line[i:i+1], "0123456789") {
tens = cast.ToInt(line[i : i+1])
break
}
}
for i := len(line) - 1; i >= 0; i-- {
if strings.ContainsAny(line[i:i+1], "0123456789") {
ones = cast.ToInt(line[i : i+1])
break
}
}
sum += tens*10 + ones
}
return sum
}
func part2(input string) int {
prefixes := map[string]int{
"one": 1,
"two": 2,
"three": 3,
"four": 4,
"five": 5,
"six": 6,
"seven": 7,
"eight": 8,
"nine": 9,
"zero": 0,
}
for i := 0; i <= 9; i++ {
prefixes[cast.ToString(i)] = i
}
var sum int
for _, line := range strings.Split(input, "\n") {
var first, last int
for len(line) > 0 {
for prefix, val := range prefixes {
if doesStringHavePrefix(line, prefix) {
if first == 0 {
first = val
}
last = val
break
}
}
// shorten line
line = line[1:]
}
sum += first*10 + last
}
return sum
}
func doesStringHavePrefix(str string, prefix string) bool {
if len(str) < len(prefix) {
return false
}
return str[:len(prefix)] == prefix
}
+70
View File
@@ -0,0 +1,70 @@
package main
import (
"testing"
)
var example = `1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet`
func Test_part1(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{
name: "example",
input: example,
want: 142,
},
{
name: "actual",
input: input,
want: 55488,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part1(tt.input); got != tt.want {
t.Errorf("part1() = %v, want %v", got, tt.want)
}
})
}
}
var example2 = `two1nine
eightwothree
abcone2threexyz
xtwone3four
4nineeightseven2
zoneight234
7pqrstsixteen`
func Test_part2(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{
name: "example",
input: example2,
want: 281,
},
{
name: "actual",
input: input,
want: 55614,
},
}
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)
}
})
}
}
+112
View File
@@ -0,0 +1,112 @@
package main
import (
_ "embed"
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/cast"
"github.com/alexchao26/advent-of-code-go/mathy"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
games := parseInput(input)
var possibleIDSum int
for _, g := range games {
isPossible := true
for _, step := range g.steps {
// only 12 red cubes, 13 green cubes, and 14 blue cubes
if step["red"] > 12 || step["green"] > 13 || step["blue"] > 14 {
isPossible = false
break
}
}
if isPossible {
possibleIDSum += g.id
}
}
return possibleIDSum
}
func part2(input string) int {
games := parseInput(input)
var sum int
for _, g := range games {
lowestPossibleCount := map[string]int{}
for _, step := range g.steps {
lowestPossibleCount["red"] = mathy.MaxInt(lowestPossibleCount["red"], step["red"])
lowestPossibleCount["green"] = mathy.MaxInt(lowestPossibleCount["green"], step["green"])
lowestPossibleCount["blue"] = mathy.MaxInt(lowestPossibleCount["blue"], step["blue"])
}
sum += lowestPossibleCount["red"] * lowestPossibleCount["blue"] * lowestPossibleCount["green"]
}
return sum
}
type game struct {
id int
steps []map[string]int
}
func parseInput(input string) (ans []game) {
for i, line := range strings.Split(input, "\n") {
parts := strings.Split(line, ": ")
g := game{
id: i + 1,
}
for _, p := range strings.Split(parts[1], "; ") {
step := map[string]int{}
for _, group := range strings.Split(p, ", ") {
numberColor := strings.Split(group, " ")
if len(numberColor) != 2 {
panic(fmt.Sprintf("group not in two pieces %q", group))
}
step[numberColor[1]] = cast.ToInt(numberColor[0])
}
g.steps = append(g.steps, step)
}
ans = append(ans, g)
}
return ans
}
+63
View File
@@ -0,0 +1,63 @@
package main
import (
"testing"
)
var example = `Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red
Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red
Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green`
func Test_part1(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{
name: "example",
input: example,
want: 8,
},
{
name: "actual",
input: input,
want: 2632,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part1(tt.input); 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
}{
{
name: "example",
input: example,
want: 2286,
},
// {
// name: "actual",
// input: input,
// want: 0,
// },
}
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)
}
})
}
}
+198
View File
@@ -0,0 +1,198 @@
package main
import (
_ "embed"
"flag"
"fmt"
"regexp"
"strings"
"github.com/alexchao26/advent-of-code-go/cast"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
var numReg = regexp.MustCompile("[0-9]")
func part1(input string) int {
matrix := [][]string{}
// collect special characters
specialChars := map[string]bool{}
for _, row := range strings.Split(input, "\n") {
matrix = append(matrix, strings.Split(row, ""))
for _, val := range strings.Split(row, "") {
if !numReg.MatchString(val) && val != "." {
specialChars[val] = true
}
}
}
specialCharsString := ""
for k := range specialChars {
specialCharsString += k
}
var sum int
diffs := [8][2]int{
{-1, -1},
{-1, 0},
{-1, 1},
{0, -1},
{0, 1},
{1, -1},
{1, 0},
{1, 1},
}
seen := map[[2]int]bool{}
for r, row := range matrix {
for c, val := range row {
coords := [2]int{r, c}
if seen[coords] {
continue
}
seen[coords] = true
// if we hit a number, collect the entire number and check along the way if it's
// adjacent to a special char
if numReg.MatchString(val) {
hasAdjacentSpecialChar := false
numStr := ""
for j := 0; j+c < len(matrix[0]); j++ {
char := row[c+j]
// breaks on period or special character, loop itself breaks on out of range
if !numReg.MatchString(char) {
break
}
// keep collecting number
numStr += char
// check all 8 directions for special char
for _, d := range diffs {
dr, dc := r+d[0], c+j+d[1]
if dr >= 0 && dr < len(matrix) && dc >= 0 && dc < len(matrix[0]) {
if strings.ContainsAny(matrix[dr][dc], specialCharsString) {
hasAdjacentSpecialChar = true
}
seen[[2]int{r, c + j}] = true
}
}
}
if hasAdjacentSpecialChar {
sum += cast.ToInt(numStr)
}
}
}
}
return sum
}
// getNumber returns -1 if a number is "not found" which could include the number
// already being seen
func getNumber(matrix [][]string, coord [2]int, seen map[[2]int]bool) int {
if !numReg.MatchString(matrix[coord[0]][coord[1]]) {
return -1
}
if seen[coord] {
return -1
}
// go to the left most digit
r, c := coord[0], coord[1]
for c-1 >= 0 {
if numReg.MatchString(matrix[r][c-1]) {
c--
} else {
break
}
}
numStr := ""
for c < len(matrix[0]) && numReg.MatchString(matrix[r][c]) {
numStr += matrix[r][c]
seen[[2]int{r, c}] = true
c++
}
return cast.ToInt(numStr)
}
func part2(input string) int {
// lucky edge case that there are not multiple gears that share the same number such as
// ....*....
// .123.456. OR ...123*456*789...
// ....*....
// with the current useage of "seen", this would only get counted once
seen := map[[2]int]bool{}
matrix := [][]string{}
for _, row := range strings.Split(input, "\n") {
matrix = append(matrix, strings.Split(row, ""))
}
diffs := [8][2]int{
{-1, -1},
{-1, 0},
{-1, 1},
{0, -1},
{0, 1},
{1, -1},
{1, 0},
{1, 1},
}
sum := 0
for r, rows := range matrix {
for c, val := range rows {
if val == "*" {
nums := []int{}
for _, diff := range diffs {
dr, dc := r+diff[0], c+diff[1]
if dr >= 0 && dr < len(matrix) && dc >= 0 && dc < len(matrix[0]) {
foundNum := getNumber(matrix, [2]int{dr, dc}, seen)
if foundNum != -1 {
nums = append(nums, foundNum)
}
}
}
if len(nums) == 2 {
sum += nums[0] * nums[1]
}
}
}
}
return sum
}
+68
View File
@@ -0,0 +1,68 @@
package main
import (
"testing"
)
var example = `467..114..
...*......
..35..633.
......#...
617*......
.....+.58.
..592.....
......755.
...$.*....
.664.598..`
func Test_part1(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{
name: "example",
input: example,
want: 4361,
},
{
name: "actual",
input: input,
want: 536576,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part1(tt.input); 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
}{
{
name: "example",
input: example,
want: 467835,
},
// {
// name: "actual",
// input: input,
// want: 0,
// },
}
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)
}
})
}
}