dumping 5 days of unrefined solutions :)

This commit is contained in:
alexchao26
2022-12-20 01:06:57 -05:00
parent d9e783478b
commit f3396ea86a
10 changed files with 1045 additions and 0 deletions
+126
View File
@@ -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
}
+67
View File
@@ -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)
}
})
}
}
+75
View File
@@ -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
}
+59
View File
@@ -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)
}
})
}
}
+168
View File
@@ -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
}
+81
View File
@@ -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)
}
})
}
}
+139
View File
@@ -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
}
+81
View File
@@ -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)
}
})
}
}
+189
View File
@@ -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
}
+60
View File
@@ -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)
}
})
}
}