mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
dump 15 17 18
This commit is contained in:
@@ -0,0 +1,174 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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, 2000000)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
} else {
|
||||||
|
ans := part2(input, 4000000)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// x is col, y is row
|
||||||
|
// unbounded grid...
|
||||||
|
// In the row where y=2000000, how many positions canNOT contain a beacon?
|
||||||
|
//
|
||||||
|
// very naive approach of marking each coord that is visible from some sensor
|
||||||
|
// then remove all beacons that are on the target row
|
||||||
|
// then just return the length of the map containing "seen" cells on target row
|
||||||
|
//
|
||||||
|
// this is brutally slow, no way this approach works for part 2
|
||||||
|
func part1(input string, targetRow int) int {
|
||||||
|
pairs := parseInput(input)
|
||||||
|
|
||||||
|
blockedCoords := map[[2]int]bool{}
|
||||||
|
for _, p := range pairs {
|
||||||
|
manhattanDist := mathy.ManhattanDistance(p.beaconRow, p.beaconCol,
|
||||||
|
p.sensorRow, p.sensorCol)
|
||||||
|
|
||||||
|
// if target row is reachable, block coords on it...
|
||||||
|
blockable := manhattanDist - mathy.AbsInt(p.sensorRow-targetRow)
|
||||||
|
if blockable > 0 {
|
||||||
|
for i := 0; i <= blockable; i++ {
|
||||||
|
// add blocks to map in both left and right directions
|
||||||
|
blockedCoords[[2]int{
|
||||||
|
targetRow, p.sensorCol - i,
|
||||||
|
}] = true
|
||||||
|
blockedCoords[[2]int{
|
||||||
|
targetRow, p.sensorCol + i,
|
||||||
|
}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any beacons that are present in the input?
|
||||||
|
for _, p := range pairs {
|
||||||
|
delete(blockedCoords, [2]int{p.beaconRow, p.beaconCol})
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(blockedCoords)
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string, coordLimit int) int {
|
||||||
|
pairs := parseInput(input)
|
||||||
|
|
||||||
|
sensors := []parsedSensor{}
|
||||||
|
for _, p := range pairs {
|
||||||
|
sensors = append(sensors, parsedSensor{
|
||||||
|
sensorRow: p.sensorRow,
|
||||||
|
sensorCol: p.sensorCol,
|
||||||
|
manhattanDist: mathy.ManhattanDistance(p.sensorCol, p.sensorRow,
|
||||||
|
p.beaconCol, p.beaconRow),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// search space is too large to iterate over the entire thing and check if
|
||||||
|
// SOME sensor can see that location...
|
||||||
|
//
|
||||||
|
// we can assume that the final resting point will be 1 cell away from the
|
||||||
|
// border of a (actually multiple) sensor. this runs under the assumption
|
||||||
|
// that there is only one answer
|
||||||
|
for _, sensor := range sensors {
|
||||||
|
distPlusOne := sensor.manhattanDist + 1
|
||||||
|
|
||||||
|
// checking in this pattern w/ manhattan distance of 1
|
||||||
|
// 1
|
||||||
|
// 2 3
|
||||||
|
// 4 S 5
|
||||||
|
// 6B7
|
||||||
|
// 8
|
||||||
|
for r := -distPlusOne; r <= distPlusOne; r++ {
|
||||||
|
targetRow := sensor.sensorRow + r
|
||||||
|
|
||||||
|
if targetRow < 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if targetRow > coordLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// check left and right on the target row
|
||||||
|
// zero for first and last r's... then subtract or add it from the
|
||||||
|
// sensor's col
|
||||||
|
colOffset := distPlusOne - mathy.AbsInt(r)
|
||||||
|
colLeft := sensor.sensorCol - colOffset
|
||||||
|
colRight := sensor.sensorCol + colOffset
|
||||||
|
|
||||||
|
if colLeft >= 0 && colLeft <= coordLimit &&
|
||||||
|
!isReachable(sensors, colLeft, targetRow) {
|
||||||
|
return colLeft*4000000 + targetRow
|
||||||
|
}
|
||||||
|
if colRight >= 0 && colRight <= coordLimit &&
|
||||||
|
!isReachable(sensors, colRight, targetRow) {
|
||||||
|
return colRight*4000000 + targetRow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
type pair struct {
|
||||||
|
sensorRow, sensorCol int
|
||||||
|
beaconRow, beaconCol int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (ans []pair) {
|
||||||
|
// Sensor at x=2150774, y=3136587: closest beacon is at x=2561642, y=2914773
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
p := pair{}
|
||||||
|
_, err := fmt.Sscanf(line,
|
||||||
|
"Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d",
|
||||||
|
&p.sensorCol, &p.sensorRow, &p.beaconCol, &p.beaconRow)
|
||||||
|
if err != nil {
|
||||||
|
panic("parsing: " + err.Error())
|
||||||
|
}
|
||||||
|
ans = append(ans, p)
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
type parsedSensor struct {
|
||||||
|
sensorRow, sensorCol int
|
||||||
|
manhattanDist int
|
||||||
|
}
|
||||||
|
|
||||||
|
func isReachable(sensors []parsedSensor, c, r int) bool {
|
||||||
|
for _, sensor := range sensors {
|
||||||
|
// if reachable, break
|
||||||
|
if sensor.manhattanDist >= mathy.ManhattanDistance(c, r,
|
||||||
|
sensor.sensorCol, sensor.sensorRow) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `Sensor at x=2, y=18: closest beacon is at x=-2, y=15
|
||||||
|
Sensor at x=9, y=16: closest beacon is at x=10, y=16
|
||||||
|
Sensor at x=13, y=2: closest beacon is at x=15, y=3
|
||||||
|
Sensor at x=12, y=14: closest beacon is at x=10, y=16
|
||||||
|
Sensor at x=10, y=20: closest beacon is at x=10, y=16
|
||||||
|
Sensor at x=14, y=17: closest beacon is at x=10, y=16
|
||||||
|
Sensor at x=8, y=7: closest beacon is at x=2, y=10
|
||||||
|
Sensor at x=2, y=0: closest beacon is at x=2, y=10
|
||||||
|
Sensor at x=0, y=11: closest beacon is at x=2, y=10
|
||||||
|
Sensor at x=20, y=14: closest beacon is at x=25, y=17
|
||||||
|
Sensor at x=17, y=20: closest beacon is at x=21, y=22
|
||||||
|
Sensor at x=16, y=7: closest beacon is at x=15, y=3
|
||||||
|
Sensor at x=14, y=3: closest beacon is at x=15, y=3
|
||||||
|
Sensor at x=20, y=1: closest beacon is at x=15, y=3`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
targetRow int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
targetRow: 10,
|
||||||
|
want: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
targetRow: 2000000,
|
||||||
|
want: 4560025,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part1(tt.input, tt.targetRow); got != tt.want {
|
||||||
|
t.Errorf("part1() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
coordLimit int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
coordLimit: 20,
|
||||||
|
want: 56000011,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
coordLimit: 4000000,
|
||||||
|
want: 12480406634249,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input, tt.coordLimit); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
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, 1000000000000)
|
||||||
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
||||||
|
fmt.Println("Output:", ans)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
// ####
|
||||||
|
|
||||||
|
// .#.
|
||||||
|
// ###
|
||||||
|
// .#.
|
||||||
|
|
||||||
|
// ..#
|
||||||
|
// ..#
|
||||||
|
// ###
|
||||||
|
|
||||||
|
// #
|
||||||
|
// #
|
||||||
|
// #
|
||||||
|
// #
|
||||||
|
|
||||||
|
// ##
|
||||||
|
// ##
|
||||||
|
|
||||||
|
// chamber is 7 units wide
|
||||||
|
// rocks fall w/ 2 spaces to left wall, 3 spaces to next rock or floor
|
||||||
|
// rocks pushed by wind, then fall down 1 space
|
||||||
|
// WHEN rock touches something below (ground or another settled rock), rock is "done" and next rock appears
|
||||||
|
// this also means that rocks will be pushed once L/R before fully settling (unless blocked L/R)
|
||||||
|
|
||||||
|
s := newState(input)
|
||||||
|
for i := 0; i < 2022; i++ {
|
||||||
|
s.dropRock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// height after 2022 rocks fall
|
||||||
|
return s.highestSettledRow + 1 // 1 indexed
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string, wantedRocks int) int {
|
||||||
|
s := newState(input)
|
||||||
|
// obviously can't do the calculations literally anymore...
|
||||||
|
// need some kind of "last time this state was seen" check to skip steps
|
||||||
|
// need to hash states
|
||||||
|
// - the top 10ish rows should be plenty
|
||||||
|
// - rock index
|
||||||
|
// - value = steps since last seen AND highestRow to calc skips
|
||||||
|
|
||||||
|
// [2]int{steps when last seen, rocks dropped, highestRow to calc diff}
|
||||||
|
pastStates := map[string][3]int{}
|
||||||
|
|
||||||
|
// will keep track of rows that are mathematically skipped otherwise it'll
|
||||||
|
// mess with state and the settled matrix
|
||||||
|
dupeRows := 0
|
||||||
|
|
||||||
|
rocksDropped := 0
|
||||||
|
for rocksDropped < wantedRocks {
|
||||||
|
s.dropRock()
|
||||||
|
rocksDropped++
|
||||||
|
|
||||||
|
h := s.hash(20)
|
||||||
|
if past, ok := pastStates[h]; ok {
|
||||||
|
pastSteps, pastRocksDropped, pastHighRow := past[0], past[1], past[2]
|
||||||
|
|
||||||
|
stepsToSkip := s.stepIndex - pastSteps
|
||||||
|
rocksToSkip := rocksDropped - pastRocksDropped
|
||||||
|
rowsToAdd := s.highestSettledRow - pastHighRow
|
||||||
|
|
||||||
|
iterationsToSkip := (wantedRocks - rocksDropped) / rocksToSkip
|
||||||
|
dupeRows += rowsToAdd * iterationsToSkip
|
||||||
|
s.stepIndex += stepsToSkip * iterationsToSkip
|
||||||
|
rocksDropped += rocksToSkip * iterationsToSkip
|
||||||
|
} else {
|
||||||
|
pastStates[h] = [3]int{
|
||||||
|
s.stepIndex, rocksDropped, s.highestSettledRow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// height after 2022 rocks fall
|
||||||
|
return s.highestSettledRow + 1 + dupeRows // 1 indexed
|
||||||
|
}
|
||||||
|
|
||||||
|
type state struct {
|
||||||
|
settled [][]string
|
||||||
|
highestSettledRow int
|
||||||
|
fallingCoords [][2]int
|
||||||
|
nextRockIndex int
|
||||||
|
steps []string
|
||||||
|
stepIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newState(input string) state {
|
||||||
|
s := state{
|
||||||
|
settled: [][]string{},
|
||||||
|
highestSettledRow: -1,
|
||||||
|
fallingCoords: nil,
|
||||||
|
nextRockIndex: 0,
|
||||||
|
steps: strings.Split(input, ""),
|
||||||
|
stepIndex: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// knew I'd need this for debugging...
|
||||||
|
func (s state) printState() {
|
||||||
|
copySettled := [][]string{}
|
||||||
|
for _, row := range s.settled {
|
||||||
|
copyRow := make([]string, len(row))
|
||||||
|
copy(copyRow, row)
|
||||||
|
copySettled = append(copySettled, copyRow)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, coord := range s.fallingCoords {
|
||||||
|
copySettled[coord[0]][coord[1]] = "@"
|
||||||
|
}
|
||||||
|
|
||||||
|
var sb strings.Builder
|
||||||
|
for r := len(copySettled) - 1; r >= 0; r-- {
|
||||||
|
sb.WriteString(strings.Join(copySettled[r], "") + cast.ToString(r) + "\n")
|
||||||
|
}
|
||||||
|
fmt.Println(sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) dropRock() {
|
||||||
|
s.populateNextBaseCoords()
|
||||||
|
|
||||||
|
highestRow := 0
|
||||||
|
for _, c := range s.fallingCoords {
|
||||||
|
highestRow = mathy.MaxInt(highestRow, c[0])
|
||||||
|
}
|
||||||
|
for len(s.settled) <= highestRow {
|
||||||
|
s.settled = append(s.settled, newEmptyRow())
|
||||||
|
}
|
||||||
|
|
||||||
|
// will be set back to nil when settled
|
||||||
|
for s.fallingCoords != nil {
|
||||||
|
switch s.steps[s.stepIndex%len(s.steps)] {
|
||||||
|
case ">":
|
||||||
|
// check if can move right
|
||||||
|
canMoveRight := true
|
||||||
|
for _, c := range s.fallingCoords {
|
||||||
|
if c[1] == 6 || s.settled[c[0]][c[1]+1] != "." {
|
||||||
|
canMoveRight = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if canMoveRight {
|
||||||
|
for i := range s.fallingCoords {
|
||||||
|
s.fallingCoords[i][1]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "<":
|
||||||
|
// check if can move left
|
||||||
|
canMoveLeft := true
|
||||||
|
for _, c := range s.fallingCoords {
|
||||||
|
if c[1] == 0 || s.settled[c[0]][c[1]-1] != "." {
|
||||||
|
canMoveLeft = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if canMoveLeft {
|
||||||
|
for i := range s.fallingCoords {
|
||||||
|
s.fallingCoords[i][1]--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(s.steps[s.stepIndex])
|
||||||
|
}
|
||||||
|
s.stepIndex++
|
||||||
|
|
||||||
|
// move down
|
||||||
|
canMoveDown := true
|
||||||
|
for _, c := range s.fallingCoords {
|
||||||
|
if c[0] == 0 || s.settled[c[0]-1][c[1]] != "." {
|
||||||
|
canMoveDown = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// is blocked, draw onto settled then make nil
|
||||||
|
if !canMoveDown {
|
||||||
|
for _, c := range s.fallingCoords {
|
||||||
|
s.settled[c[0]][c[1]] = "#"
|
||||||
|
}
|
||||||
|
s.fallingCoords = nil
|
||||||
|
|
||||||
|
for r := len(s.settled) - 1; r >= 0; r-- {
|
||||||
|
if strings.Join(s.settled[r], "") != "......." {
|
||||||
|
s.highestSettledRow = r
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range s.fallingCoords {
|
||||||
|
s.fallingCoords[i][0]--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmptyRow() []string {
|
||||||
|
row := make([]string, 7)
|
||||||
|
for i := range row {
|
||||||
|
row[i] = "."
|
||||||
|
}
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseCoords = [][][2]int{
|
||||||
|
{
|
||||||
|
// line ####
|
||||||
|
{0, 0},
|
||||||
|
{0, 1},
|
||||||
|
{0, 2},
|
||||||
|
{0, 3},
|
||||||
|
}, {
|
||||||
|
// plus
|
||||||
|
{0, 1},
|
||||||
|
{1, 0},
|
||||||
|
{1, 1},
|
||||||
|
{1, 2},
|
||||||
|
{2, 1},
|
||||||
|
}, {
|
||||||
|
// flipped L
|
||||||
|
{0, 0},
|
||||||
|
{0, 1},
|
||||||
|
{0, 2},
|
||||||
|
{1, 2},
|
||||||
|
{2, 2},
|
||||||
|
}, {
|
||||||
|
// vert line
|
||||||
|
{0, 0},
|
||||||
|
{1, 0},
|
||||||
|
{2, 0},
|
||||||
|
{3, 0},
|
||||||
|
}, {
|
||||||
|
// square
|
||||||
|
{0, 0},
|
||||||
|
{0, 1},
|
||||||
|
{1, 0},
|
||||||
|
{1, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// add 2 cols to all baseCoords because they fall 2 off of left wall
|
||||||
|
for i := range baseCoords {
|
||||||
|
for j := range baseCoords[i] {
|
||||||
|
baseCoords[i][j][1] += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) populateNextBaseCoords() {
|
||||||
|
copyCoords := make([][2]int, len(baseCoords[s.nextRockIndex]))
|
||||||
|
copy(copyCoords, baseCoords[s.nextRockIndex])
|
||||||
|
s.nextRockIndex++
|
||||||
|
s.nextRockIndex %= 5
|
||||||
|
|
||||||
|
// lowest row of baseCoords...
|
||||||
|
|
||||||
|
for i := range copyCoords {
|
||||||
|
copyCoords[i][0] += s.highestSettledRow + 1 + 3
|
||||||
|
}
|
||||||
|
s.fallingCoords = copyCoords
|
||||||
|
}
|
||||||
|
|
||||||
|
// for part 2 to find return states
|
||||||
|
// NOTE: had to play with the number of rows to be hashed... 20 seems to
|
||||||
|
// work on the example input
|
||||||
|
func (s *state) hash(topRowsToHash int) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString(cast.ToString(s.nextRockIndex))
|
||||||
|
for r := s.highestSettledRow; r >= 0 && r > s.highestSettledRow-topRowsToHash; r-- {
|
||||||
|
sb.WriteString("\n" + strings.Join(s.settled[r], ""))
|
||||||
|
}
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `>>><<><>><<<>><>>><<<>>><<<><<<>><>><<>>`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 3068,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 3219,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
wantedRocks int
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
// part 1 values should work as well, so using them for extra testing
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
wantedRocks: 2022,
|
||||||
|
want: 3068,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
wantedRocks: 2022,
|
||||||
|
want: 3219,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
wantedRocks: 1000000000000,
|
||||||
|
want: 1514285714288,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
wantedRocks: 1000000000000,
|
||||||
|
want: 1582758620701,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := part2(tt.input, tt.wantedRocks); got != tt.want {
|
||||||
|
t.Errorf("part2() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to six adjacent face...
|
||||||
|
var diffs = [][3]int{
|
||||||
|
{0, 0, 1},
|
||||||
|
{0, 1, 0},
|
||||||
|
{1, 0, 0},
|
||||||
|
{0, 0, -1},
|
||||||
|
{0, -1, 0},
|
||||||
|
{-1, 0, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1(input string) int {
|
||||||
|
rawCoords := parseInput(input)
|
||||||
|
mapCoords := convertRawCoordsToMap(rawCoords)
|
||||||
|
|
||||||
|
totalSurfaceArea := 0
|
||||||
|
for _, coord := range rawCoords {
|
||||||
|
neighbors := 6
|
||||||
|
for _, d := range diffs {
|
||||||
|
if mapCoords[[3]int{
|
||||||
|
coord[0] - d[0],
|
||||||
|
coord[1] - d[1],
|
||||||
|
coord[2] - d[2],
|
||||||
|
}] {
|
||||||
|
neighbors--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalSurfaceArea += neighbors
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalSurfaceArea
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2(input string) int {
|
||||||
|
rawCoords := parseInput(input)
|
||||||
|
mapCoords := convertRawCoordsToMap(rawCoords)
|
||||||
|
|
||||||
|
// get bounds
|
||||||
|
var limitX, limitY, limitZ int
|
||||||
|
for c := range mapCoords {
|
||||||
|
limitX = mathy.MaxInt(limitX, c[0])
|
||||||
|
limitY = mathy.MaxInt(limitY, c[1])
|
||||||
|
limitZ = mathy.MaxInt(limitZ, c[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// bfs to see if an edge can be reached
|
||||||
|
// delete if not useful
|
||||||
|
|
||||||
|
totalExternalSurfaceArea := 0
|
||||||
|
|
||||||
|
for coord := range mapCoords {
|
||||||
|
totalExternalSurfaceArea += facesThatCanReachEdge(coord, mapCoords,
|
||||||
|
limitX, limitY, limitZ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// too low: 1036
|
||||||
|
return totalExternalSurfaceArea
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInput(input string) (ans [][3]int) {
|
||||||
|
for _, line := range strings.Split(input, "\n") {
|
||||||
|
parts := strings.Split(line, ",")
|
||||||
|
ans = append(ans, [3]int{
|
||||||
|
cast.ToInt(parts[0]),
|
||||||
|
cast.ToInt(parts[1]),
|
||||||
|
cast.ToInt(parts[2]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
func convertRawCoordsToMap(rawCoords [][3]int) map[[3]int]bool {
|
||||||
|
set := map[[3]int]bool{}
|
||||||
|
for _, coord := range rawCoords {
|
||||||
|
set[coord] = true
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
// there would be a big optimization here to keep track of all coords that have
|
||||||
|
// a known path to an edge, that would eliminate a lot of duplicate work... but
|
||||||
|
// i think this is a small enough problem space to ignore that...
|
||||||
|
func facesThatCanReachEdge(coord [3]int, set map[[3]int]bool, limitX, limitY, limitZ int) int {
|
||||||
|
ans := 0
|
||||||
|
for _, d := range diffs {
|
||||||
|
next := [3]int{
|
||||||
|
coord[0] + d[0],
|
||||||
|
coord[1] + d[1],
|
||||||
|
coord[2] + d[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
reachResult := canReachEdge(next, set, limitX, limitY, limitZ)
|
||||||
|
if reachResult {
|
||||||
|
ans++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ans
|
||||||
|
}
|
||||||
|
|
||||||
|
func canReachEdge(coord [3]int, set map[[3]int]bool, limitX, limitY, limitZ int,
|
||||||
|
) bool {
|
||||||
|
queue := [][3]int{coord}
|
||||||
|
seen := map[[3]int]bool{}
|
||||||
|
for len(queue) > 0 {
|
||||||
|
front := queue[0]
|
||||||
|
queue = queue[1:]
|
||||||
|
|
||||||
|
// seen already or hit some other droplet, skip
|
||||||
|
if seen[front] || set[front] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[front] = true
|
||||||
|
|
||||||
|
// edge reached
|
||||||
|
if front[0] <= 0 || front[0] >= limitX ||
|
||||||
|
front[1] <= 0 || front[1] >= limitY ||
|
||||||
|
front[2] <= 0 || front[2] >= limitZ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range diffs {
|
||||||
|
next := [3]int{
|
||||||
|
front[0] + d[0],
|
||||||
|
front[1] + d[1],
|
||||||
|
front[2] + d[2],
|
||||||
|
}
|
||||||
|
queue = append(queue, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var example = `2,2,2
|
||||||
|
1,2,2
|
||||||
|
3,2,2
|
||||||
|
2,1,2
|
||||||
|
2,3,2
|
||||||
|
2,2,1
|
||||||
|
2,2,3
|
||||||
|
2,2,4
|
||||||
|
2,2,6
|
||||||
|
1,2,5
|
||||||
|
3,2,5
|
||||||
|
2,1,5
|
||||||
|
2,3,5`
|
||||||
|
|
||||||
|
func Test_part1(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
want: 4636,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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 flatDisc = `5,5,5
|
||||||
|
5,5,6
|
||||||
|
5,5,7
|
||||||
|
5,6,5
|
||||||
|
5,6,6
|
||||||
|
5,6,7
|
||||||
|
5,7,5
|
||||||
|
5,7,6
|
||||||
|
5,7,7`
|
||||||
|
|
||||||
|
// 3x3x1 disc...
|
||||||
|
func Test_part2(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "dumb simple",
|
||||||
|
input: "1,1,1\n2,1,1",
|
||||||
|
want: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dumber simpleer",
|
||||||
|
input: "2,1,1",
|
||||||
|
want: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "example",
|
||||||
|
input: example,
|
||||||
|
want: 58,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "flatDisc",
|
||||||
|
input: flatDisc,
|
||||||
|
// 9 + 9 + 3 * 4 = 30
|
||||||
|
want: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "actual",
|
||||||
|
input: input,
|
||||||
|
// PAIN, used coord instead of front in the bfs check :/
|
||||||
|
want: 2572,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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