mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
dumping 5 days of unrefined solutions :)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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) string {
|
||||
stacks, steps := parseInput(input)
|
||||
|
||||
for _, step := range steps {
|
||||
// move crates ONE AT A TIME
|
||||
for q := 0; q < step.qty; q++ {
|
||||
top := stacks[step.from][len(stacks[step.from])-1]
|
||||
stacks[step.to] = append(stacks[step.to], top)
|
||||
stacks[step.from] = stacks[step.from][:len(stacks[step.from])-1]
|
||||
}
|
||||
}
|
||||
|
||||
ans := ""
|
||||
for _, stack := range stacks {
|
||||
ans += stack[len(stack)-1]
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func part2(input string) string {
|
||||
stacks, steps := parseInput(input)
|
||||
|
||||
for _, step := range steps {
|
||||
// move crates ONCE
|
||||
fromIndex := len(stacks[step.from]) - step.qty
|
||||
stacks[step.to] = append(stacks[step.to], stacks[step.from][fromIndex:]...)
|
||||
stacks[step.from] = stacks[step.from][:fromIndex]
|
||||
}
|
||||
|
||||
ans := ""
|
||||
for _, stack := range stacks {
|
||||
ans += stack[len(stack)-1]
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
// move 4 from 3 to 1
|
||||
type step struct {
|
||||
qty, from, to int
|
||||
}
|
||||
|
||||
func (s step) String() string {
|
||||
return fmt.Sprintf("move %d from %d to %d", s.qty, s.from, s.to)
|
||||
}
|
||||
|
||||
func parseInput(input string) ([][]string, []step) {
|
||||
parts := strings.Split(input, "\n\n")
|
||||
|
||||
state := parts[0]
|
||||
oversized := [][]string{}
|
||||
for _, row := range strings.Split(state, "\n") {
|
||||
oversized = append(oversized, strings.Split(row, ""))
|
||||
}
|
||||
oRows, oCols := len(oversized), len(oversized[0])
|
||||
|
||||
actual := [][]string{}
|
||||
|
||||
for c := 0; c < oCols-1; c++ {
|
||||
if oversized[oRows-1][c] != " " {
|
||||
// hit a column with values... move up from here
|
||||
stack := []string{}
|
||||
for r := oRows - 2; r >= 0; r-- {
|
||||
char := oversized[r][c]
|
||||
if char != " " {
|
||||
stack = append(stack, char)
|
||||
}
|
||||
}
|
||||
actual = append(actual, stack)
|
||||
}
|
||||
}
|
||||
|
||||
stepsRaw := parts[1]
|
||||
steps := []step{}
|
||||
for _, row := range strings.Split(stepsRaw, "\n") {
|
||||
inst := step{}
|
||||
_, err := fmt.Sscanf(row, "move %d from %d to %d", &inst.qty, &inst.from, &inst.to)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// subtract one so they're zero indexed...
|
||||
inst.from--
|
||||
inst.to--
|
||||
steps = append(steps, inst)
|
||||
}
|
||||
|
||||
return actual, steps
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = ` [D]
|
||||
[N] [C]
|
||||
[Z] [M] [P]
|
||||
1 2 3
|
||||
|
||||
move 1 from 2 to 1
|
||||
move 3 from 1 to 3
|
||||
move 2 from 2 to 1
|
||||
move 1 from 1 to 2`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: "CMZ",
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: "QNHWJVJZW",
|
||||
},
|
||||
}
|
||||
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 string
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: "MCD",
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: "BPCZJLFJW",
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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 {
|
||||
// packet starts w/ 4 characters that are all different
|
||||
for i := 0; i+4 <= len(input); i++ {
|
||||
if allDifferentLetters(input[i : i+4]) {
|
||||
return i + 4
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
// lazy but easier than sliding window...
|
||||
func allDifferentLetters(str string) bool {
|
||||
// if len(str) != 4 {
|
||||
// panic(fmt.Sprintf("invalid length %q", str))
|
||||
// }
|
||||
for i := 0; i < len(str); i++ {
|
||||
for j := i + 1; j < len(str); j++ {
|
||||
if str[i] == str[j] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
// wow super lazy but fast to write... ok
|
||||
for i := 0; i+14 <= len(input); i++ {
|
||||
if allDifferentLetters(input[i : i+14]) {
|
||||
return i + 14
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `mjqjpqmgbljsphdztnvjfqwrcgsmlb`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 7,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 1109,
|
||||
},
|
||||
}
|
||||
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: 19,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 3965,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"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 {
|
||||
root := parseInput(input)
|
||||
|
||||
return sumDirsUnder100000(root)
|
||||
}
|
||||
|
||||
func sumDirsUnder100000(itr *dir) int {
|
||||
SizeLimit := 100000
|
||||
|
||||
sum := 0
|
||||
if itr.totalSize <= SizeLimit {
|
||||
sum += itr.totalSize
|
||||
}
|
||||
for _, child := range itr.childDirs {
|
||||
sum += sumDirsUnder100000(child)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
root := parseInput(input)
|
||||
totalSapceAvailable := 70000000
|
||||
spaceNeeded := 30000000
|
||||
|
||||
// find smallest directory to be deleted that would free up enough space...
|
||||
directoryMinSize := spaceNeeded - (totalSapceAvailable - root.totalSize)
|
||||
return findSmallestDirToDelete(root, directoryMinSize)
|
||||
}
|
||||
|
||||
func findSmallestDirToDelete(itr *dir, directoryMinSize int) int {
|
||||
smallest := math.MaxInt64
|
||||
if itr.totalSize >= directoryMinSize {
|
||||
smallest = mathy.MinInt(smallest, itr.totalSize)
|
||||
}
|
||||
|
||||
for _, childDirs := range itr.childDirs {
|
||||
smallest = mathy.MinInt(smallest, findSmallestDirToDelete(childDirs, directoryMinSize))
|
||||
}
|
||||
|
||||
return smallest
|
||||
}
|
||||
|
||||
type dir struct {
|
||||
name string
|
||||
parentDir *dir
|
||||
childDirs map[string]*dir
|
||||
files map[string]int
|
||||
totalSize int
|
||||
}
|
||||
|
||||
func parseInput(input string) *dir {
|
||||
root := &dir{
|
||||
name: "root",
|
||||
childDirs: map[string]*dir{},
|
||||
}
|
||||
itr := root
|
||||
|
||||
cmds := strings.Split(input, "\n")
|
||||
c := 0
|
||||
|
||||
for c < len(cmds) {
|
||||
switch cmd := cmds[c]; cmd[0:1] {
|
||||
case "$":
|
||||
if cmd == "$ ls" {
|
||||
// just move on, we will assume we're always in an listing state
|
||||
c++
|
||||
} else {
|
||||
changeDir := strings.Split(cmd, "cd ")[1]
|
||||
changeDir = strings.TrimSpace(changeDir)
|
||||
if changeDir == ".." {
|
||||
itr = itr.parentDir
|
||||
} else {
|
||||
// if changeDir doesn't exist..
|
||||
if _, ok := itr.childDirs[changeDir]; !ok {
|
||||
itr.childDirs[changeDir] = &dir{
|
||||
name: changeDir,
|
||||
parentDir: itr,
|
||||
childDirs: map[string]*dir{},
|
||||
files: map[string]int{}}
|
||||
}
|
||||
|
||||
itr = itr.childDirs[changeDir]
|
||||
}
|
||||
c++
|
||||
}
|
||||
default:
|
||||
// assume we're listing a dir's contents... add it
|
||||
if strings.HasPrefix(cmd, "dir") {
|
||||
childDirName := cmd[4:]
|
||||
if _, ok := itr.childDirs[childDirName]; !ok {
|
||||
itr.childDirs[childDirName] = &dir{
|
||||
name: childDirName,
|
||||
parentDir: itr,
|
||||
childDirs: map[string]*dir{},
|
||||
files: map[string]int{},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// file name
|
||||
parts := strings.Split(cmd, " ")
|
||||
itr.files[parts[0]] = cast.ToInt(parts[0])
|
||||
}
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
populateFileSizes(root)
|
||||
return root
|
||||
}
|
||||
|
||||
func populateFileSizes(itr *dir) int {
|
||||
totalSize := 0
|
||||
|
||||
for _, childItr := range itr.childDirs {
|
||||
totalSize += populateFileSizes(childItr)
|
||||
}
|
||||
|
||||
for _, sz := range itr.files {
|
||||
totalSize += sz
|
||||
}
|
||||
|
||||
itr.totalSize = totalSize
|
||||
|
||||
return totalSize
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `$ cd /
|
||||
$ ls
|
||||
dir a
|
||||
14848514 b.txt
|
||||
8504156 c.dat
|
||||
dir d
|
||||
$ cd a
|
||||
$ ls
|
||||
dir e
|
||||
29116 f
|
||||
2557 g
|
||||
62596 h.lst
|
||||
$ cd e
|
||||
$ ls
|
||||
584 i
|
||||
$ cd ..
|
||||
$ cd ..
|
||||
$ cd d
|
||||
$ ls
|
||||
4060174 j
|
||||
8033020 d.log
|
||||
5626152 d.ext
|
||||
7214296 k`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 95437,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 1423358,
|
||||
},
|
||||
}
|
||||
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: 24933642,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 545729,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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 {
|
||||
pairs := parseInput(input)
|
||||
// sum all the indexes that are in the right order
|
||||
// ONE INDEXED NOT ZERO
|
||||
goodIndexSum := 0
|
||||
for i, pair := range pairs {
|
||||
left, right := pair[0], pair[1]
|
||||
if isInOrder(left, right) {
|
||||
goodIndexSum += i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return goodIndexSum
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
pairs := parseInput(input)
|
||||
allPackets := [][]interface{}{
|
||||
// good reminder that json.Unmarshal will convert numbers to float64...
|
||||
// so need this to match for the way to isInOrder() function works..
|
||||
{[]interface{}{float64(2)}},
|
||||
{[]interface{}{float64(6)}},
|
||||
}
|
||||
for _, pair := range pairs {
|
||||
allPackets = append(allPackets, pair[0])
|
||||
allPackets = append(allPackets, pair[1])
|
||||
}
|
||||
|
||||
sort.Slice(allPackets, func(i, j int) bool {
|
||||
left, right := allPackets[i], allPackets[j]
|
||||
return isInOrder(left, right)
|
||||
})
|
||||
|
||||
ans := 1
|
||||
for i, p := range allPackets {
|
||||
if fmt.Sprint(p) == "[[2]]" || fmt.Sprint(p) == "[[6]]" {
|
||||
ans *= i + 1
|
||||
}
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
func parseInput(input string) (ans [][2][]interface{}) {
|
||||
for _, packetPairs := range strings.Split(input, "\n\n") {
|
||||
pairs := strings.Split(packetPairs, "\n")
|
||||
ans = append(ans, [2][]interface{}{
|
||||
parseRawString(pairs[0]),
|
||||
parseRawString(pairs[1]),
|
||||
})
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
// will parse as JSON with elements as either int or []int...
|
||||
func parseRawString(raw string) []interface{} {
|
||||
ans := []interface{}{}
|
||||
json.Unmarshal([]byte(raw), &ans)
|
||||
return ans
|
||||
}
|
||||
|
||||
func isInOrder(left, right []interface{}) bool {
|
||||
for l := 0; l < len(left); l++ {
|
||||
if l > len(right)-1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// attempt to convert both to ints...
|
||||
leftNum, isLeftNum := left[l].(float64)
|
||||
rightNum, isRightNum := right[l].(float64)
|
||||
|
||||
leftList, isLeftList := left[l].([]interface{})
|
||||
rightList, isRightList := right[l].([]interface{})
|
||||
if isLeftNum && isRightNum {
|
||||
if leftNum != rightNum {
|
||||
return leftNum < rightNum
|
||||
}
|
||||
} else if isLeftNum || isRightNum {
|
||||
if isLeftNum {
|
||||
leftList = []interface{}{leftNum}
|
||||
} else if isRightNum {
|
||||
rightList = []interface{}{rightNum}
|
||||
} else {
|
||||
panic(fmt.Sprintf("expected one num %T:%v, %T:%v", left[l],
|
||||
left[l], right[l], right[l]))
|
||||
}
|
||||
return isInOrder(leftList, rightList)
|
||||
} else {
|
||||
// both lists
|
||||
if !isLeftList || !isRightList {
|
||||
panic(fmt.Sprintf("expected two lists %T:%v, %T:%v", left[l],
|
||||
left[l], right[l], right[l]))
|
||||
}
|
||||
return isInOrder(leftList, rightList)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `[1,1,3,1,1]
|
||||
[1,1,5,1,1]
|
||||
|
||||
[[1],[2,3,4]]
|
||||
[[1],4]
|
||||
|
||||
[9]
|
||||
[[8,7,6]]
|
||||
|
||||
[[4,4],4,4]
|
||||
[[4,4],4,4,4]
|
||||
|
||||
[7,7,7,7]
|
||||
[7,7,7]
|
||||
|
||||
[]
|
||||
[3]
|
||||
|
||||
[[[]]]
|
||||
[[]]
|
||||
|
||||
[1,[2,[3,[4,[5,6,7]]]],8,9]
|
||||
[1,[2,[3,[4,[5,6,0]]]],8,9]`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 13,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 5760,
|
||||
},
|
||||
}
|
||||
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: 140,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 26670,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
"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 {
|
||||
matrix := parseInput(input)
|
||||
originCol := 0
|
||||
for i, c := range matrix[0] {
|
||||
if c == "+" {
|
||||
originCol = i
|
||||
}
|
||||
}
|
||||
|
||||
ans := 0
|
||||
for !dropSand(matrix, originCol) {
|
||||
ans++
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
matrix := parseInput(input)
|
||||
originCol := 0
|
||||
for i, c := range matrix[0] {
|
||||
if c == "+" {
|
||||
originCol = i
|
||||
}
|
||||
matrix[len(matrix)-1][i] = "#"
|
||||
}
|
||||
|
||||
ans := 0
|
||||
for !dropSand(matrix, originCol) {
|
||||
ans++
|
||||
// COULD incorporate this into the for loop conditional but then the
|
||||
// ordering is important... must check origin cell BEFORE running
|
||||
// dropSand... it's easier to read here...
|
||||
if matrix[0][originCol] == "o" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return ans
|
||||
}
|
||||
|
||||
func parseInput(input string) (matrix [][]string) {
|
||||
coordSets := [][][2]int{}
|
||||
lowestCol := math.MaxInt64
|
||||
highestRow := 0
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
rawCoords := strings.Split(line, " -> ")
|
||||
coords := [][2]int{}
|
||||
for _, rawCoord := range rawCoords {
|
||||
rawNums := strings.Split(rawCoord, ",")
|
||||
col, row := cast.ToInt(rawNums[0]), cast.ToInt(rawNums[1])
|
||||
coord := [2]int{
|
||||
col, row,
|
||||
}
|
||||
coords = append(coords, coord)
|
||||
|
||||
lowestCol = mathy.MinInt(lowestCol, col)
|
||||
highestRow = mathy.MaxInt(highestRow, row)
|
||||
}
|
||||
coordSets = append(coordSets, coords)
|
||||
}
|
||||
|
||||
// lowering this number to 1 makes it easier to print the matrix, which I
|
||||
// used for part 1... but then needed to up it for part 2... or just have a
|
||||
// massive screen and make the terminal text tiny...
|
||||
ExtraLeftSpace := 200
|
||||
|
||||
highestCol := 0
|
||||
for s, set := range coordSets {
|
||||
for i := range set {
|
||||
coordSets[s][i][0] -= lowestCol - ExtraLeftSpace
|
||||
highestCol = mathy.MaxInt(highestCol, coordSets[s][i][0])
|
||||
}
|
||||
}
|
||||
|
||||
matrix = make([][]string, highestRow+3)
|
||||
for r := range matrix {
|
||||
matrix[r] = make([]string, highestCol+ExtraLeftSpace*2)
|
||||
}
|
||||
|
||||
for _, set := range coordSets {
|
||||
for i := 1; i < len(set); i++ {
|
||||
cols := []int{set[i-1][0], set[i][0]}
|
||||
rows := []int{set[i-1][1], set[i][1]}
|
||||
|
||||
sort.Ints(cols)
|
||||
sort.Ints(rows)
|
||||
|
||||
if cols[0] == cols[1] {
|
||||
for r := rows[0]; r <= rows[1]; r++ {
|
||||
matrix[r][cols[0]] = "#"
|
||||
}
|
||||
} else if rows[0] == rows[1] {
|
||||
for c := cols[0]; c <= cols[1]; c++ {
|
||||
matrix[rows[0]][c] = "#"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
originCol := 500 - lowestCol + ExtraLeftSpace
|
||||
// make it a plus so it's searchable in the next step... or could just
|
||||
// return this value too...
|
||||
matrix[0][originCol] = "+"
|
||||
|
||||
for i, r := range matrix {
|
||||
for j := range r {
|
||||
if matrix[i][j] == "" {
|
||||
matrix[i][j] = "."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printMatrix(matrix)
|
||||
return matrix
|
||||
}
|
||||
|
||||
func printMatrix(matrix [][]string) {
|
||||
for _, r := range matrix {
|
||||
fmt.Println(r)
|
||||
}
|
||||
}
|
||||
|
||||
func dropSand(matrix [][]string, originCol int) (fallsIntoAbyss bool) {
|
||||
r, c := 0, originCol
|
||||
|
||||
for r < len(matrix)-1 {
|
||||
below := matrix[r+1][c]
|
||||
diagonallyLeft := matrix[r+1][c-1]
|
||||
diagonallyRight := matrix[r+1][c+1]
|
||||
if below == "." {
|
||||
r++
|
||||
} else if diagonallyLeft == "." {
|
||||
r++
|
||||
c--
|
||||
} else if diagonallyRight == "." {
|
||||
r++
|
||||
c++
|
||||
} else {
|
||||
matrix[r][c] = "o"
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `498,4 -> 498,6 -> 496,6
|
||||
503,4 -> 502,4 -> 502,9 -> 494,9`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 24,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 961,
|
||||
},
|
||||
}
|
||||
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: 93,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 26375,
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user