mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-06-07 12:45:10 +02:00
2024-18 still dont get shoelace and picks...
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"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 {
|
||||
digInstructions := parseInput(input)
|
||||
|
||||
trenchCoords := getTrenchCoords(digInstructions)
|
||||
|
||||
containedCoords := getContainedCoords(trenchCoords)
|
||||
|
||||
return len(containedCoords) + len(trenchCoords)
|
||||
}
|
||||
|
||||
func getTrenchCoords(digInstructions []digInstruction) map[[2]int]bool {
|
||||
trenchCoords := map[[2]int]bool{
|
||||
{0, 0}: true,
|
||||
}
|
||||
|
||||
var row, col int
|
||||
diffs := map[string][2]int{
|
||||
"L": {0, -1},
|
||||
"R": {0, 1},
|
||||
"U": {-1, 0},
|
||||
"D": {1, 0},
|
||||
}
|
||||
|
||||
for _, inst := range digInstructions {
|
||||
for i := 1; i <= inst.length; i++ {
|
||||
row += diffs[inst.dir][0]
|
||||
col += diffs[inst.dir][1]
|
||||
trenchCoords[[2]int{row, col}] = true
|
||||
}
|
||||
}
|
||||
return trenchCoords
|
||||
}
|
||||
|
||||
func getContainedCoords(trenchCoords map[[2]int]bool) map[[2]int]bool {
|
||||
// check around a coordinate that's part of a straight line for a cell that _could_ be contained
|
||||
// straight lines will have one side that's in and one that is out
|
||||
// we'll only check vertical lines to make it easier to code...
|
||||
var testCoords [][2]int
|
||||
|
||||
for coord := range trenchCoords {
|
||||
upCoords := [2]int{coord[0] - 1, coord[1]}
|
||||
downCoords := [2]int{coord[0] + 1, coord[1]}
|
||||
leftCoords := [2]int{coord[0], coord[1] - 1}
|
||||
rightCoords := [2]int{coord[0], coord[1] + 1}
|
||||
|
||||
if trenchCoords[upCoords] && trenchCoords[downCoords] &&
|
||||
!trenchCoords[leftCoords] && !trenchCoords[rightCoords] {
|
||||
// part of vertical line
|
||||
testCoords = append(testCoords, leftCoords, rightCoords)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max size that can be contained (equal to the box containing all the coordinates)
|
||||
var (
|
||||
left = testCoords[0][1]
|
||||
right = testCoords[0][1]
|
||||
top = testCoords[0][0]
|
||||
bottom = testCoords[0][0]
|
||||
)
|
||||
for coords := range trenchCoords {
|
||||
left = min(left, coords[1])
|
||||
right = max(right, coords[1])
|
||||
top = min(top, coords[0])
|
||||
bottom = max(bottom, coords[0])
|
||||
}
|
||||
|
||||
maxContainedSize := (right - left + 1) * (bottom - top + 1)
|
||||
|
||||
for _, coord := range testCoords {
|
||||
queue := [][2]int{coord}
|
||||
seen := map[[2]int]bool{}
|
||||
|
||||
for len(queue) > 0 && len(seen) < maxContainedSize {
|
||||
current := queue[0]
|
||||
queue = queue[1:]
|
||||
|
||||
if seen[current] {
|
||||
continue
|
||||
}
|
||||
seen[current] = true
|
||||
|
||||
for _, diff := range [][2]int{
|
||||
{-1, 0},
|
||||
{1, 0},
|
||||
{0, -1},
|
||||
{0, 1},
|
||||
} {
|
||||
nextRow := current[0] + diff[0]
|
||||
nextCol := current[1] + diff[1]
|
||||
nextCoord := [2]int{nextRow, nextCol}
|
||||
// if already seen or it's part of the trench, skip
|
||||
if trenchCoords[nextCoord] || seen[nextCoord] {
|
||||
continue
|
||||
}
|
||||
// otherwise add it to be searched
|
||||
queue = append(queue, nextCoord)
|
||||
}
|
||||
}
|
||||
|
||||
if len(queue) == 0 {
|
||||
return seen
|
||||
}
|
||||
}
|
||||
panic("should return from loop")
|
||||
}
|
||||
|
||||
func part2(input string) int {
|
||||
digInstructions := parseInput(input)
|
||||
|
||||
vertices := [][2]int{}
|
||||
currentPoint := [2]int{0, 0}
|
||||
|
||||
for _, inst := range digInstructions {
|
||||
hex := inst.color[1 : len(inst.color)-1]
|
||||
dirCode := inst.color[len(inst.color)-1:]
|
||||
|
||||
convInt, err := strconv.ParseInt(hex, 16, 0)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
switch dirCode {
|
||||
case "0": // R
|
||||
currentPoint[1] += int(convInt)
|
||||
case "1": // D
|
||||
currentPoint[0] += int(convInt)
|
||||
case "2": // L
|
||||
currentPoint[1] -= int(convInt)
|
||||
case "3": // U
|
||||
currentPoint[0] -= int(convInt)
|
||||
}
|
||||
vertices = append(vertices, currentPoint)
|
||||
}
|
||||
|
||||
return shoelace(vertices) + 1
|
||||
}
|
||||
|
||||
func shoelace(coordinates [][2]int) int {
|
||||
area := 0
|
||||
|
||||
for i := 0; i < len(coordinates); i++ {
|
||||
coordA := coordinates[i]
|
||||
coordB := coordinates[(i+1)%(len(coordinates))]
|
||||
|
||||
area += (coordA[1] * coordB[0]) - (coordB[1] * coordA[0]) +
|
||||
max(abs(coordA[0]-coordB[0]), abs(coordA[1]-coordB[1]))
|
||||
}
|
||||
|
||||
return area / 2
|
||||
}
|
||||
|
||||
func abs(i int) int {
|
||||
if i < 0 {
|
||||
return -i
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
type digInstruction struct {
|
||||
dir string
|
||||
length int
|
||||
color string
|
||||
}
|
||||
|
||||
func parseInput(input string) (ans []digInstruction) {
|
||||
for _, line := range strings.Split(input, "\n") {
|
||||
parts := strings.Split(line, " ")
|
||||
ans = append(ans, digInstruction{
|
||||
dir: parts[0],
|
||||
length: cast.ToInt(parts[1]),
|
||||
color: parts[2][1 : len(parts[2])-1],
|
||||
})
|
||||
}
|
||||
return ans
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var example = `R 6 (#70c710)
|
||||
D 5 (#0dc571)
|
||||
L 2 (#5713f0)
|
||||
D 2 (#d2c081)
|
||||
R 2 (#59c680)
|
||||
D 2 (#411b91)
|
||||
L 5 (#8ceee2)
|
||||
U 2 (#caa173)
|
||||
L 1 (#1b58a2)
|
||||
U 2 (#caa171)
|
||||
R 2 (#7807d2)
|
||||
U 3 (#a77fa3)
|
||||
L 2 (#015232)
|
||||
U 2 (#7a21e3)`
|
||||
|
||||
func Test_part1(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "example",
|
||||
input: example,
|
||||
want: 62,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 47527,
|
||||
},
|
||||
}
|
||||
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: 952408144115,
|
||||
},
|
||||
{
|
||||
name: "actual",
|
||||
input: input,
|
||||
want: 52240187443190,
|
||||
},
|
||||
}
|
||||
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