mirror of
https://github.com/Luzifer/aoc2019.git
synced 2024-12-22 05:51:16 +00:00
Add solution for Day 3
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
9b67f2728c
commit
ef7a1c5e33
6 changed files with 397 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
day03_debug.png
|
269
day03.go
Normal file
269
day03.go
Normal file
|
@ -0,0 +1,269 @@
|
|||
package aoc2019
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/llgcode/draw2d/draw2dimg"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type day3LineSegment [4]int
|
||||
|
||||
func (d day3LineSegment) Draw(img *image.RGBA, col color.Color) {
|
||||
var ptMod func(x, y int) (int, int, bool)
|
||||
|
||||
switch {
|
||||
case d.IsVertical() && d[1] < d[3]:
|
||||
ptMod = func(x, y int) (int, int, bool) { return x, y + 1, y+1 <= d[3] }
|
||||
case d.IsVertical() && d[1] > d[3]:
|
||||
ptMod = func(x, y int) (int, int, bool) { return x, y - 1, y-1 >= d[3] }
|
||||
case !d.IsVertical() && d[0] < d[2]:
|
||||
ptMod = func(x, y int) (int, int, bool) { return x + 1, y, x+1 <= d[2] }
|
||||
case !d.IsVertical() && d[0] > d[2]:
|
||||
ptMod = func(x, y int) (int, int, bool) { return x - 1, y, x-1 >= d[2] }
|
||||
}
|
||||
|
||||
var pX, pY, ok = d[0], d[1], true
|
||||
|
||||
for ok {
|
||||
img.Set(pX, pY, col)
|
||||
pX, pY, ok = ptMod(pX, pY)
|
||||
}
|
||||
}
|
||||
|
||||
func (d day3LineSegment) GetIntersection(in day3LineSegment) ([2]int, bool) {
|
||||
if d.IsVertical() == in.IsVertical() {
|
||||
// Both lines have the same direction, there is no intersection
|
||||
// NOTE: This might yield false negative when lines are overlapping?
|
||||
return [2]int{0, 0}, false
|
||||
}
|
||||
|
||||
var (
|
||||
vert, horiz day3LineSegment
|
||||
)
|
||||
|
||||
if d.IsVertical() {
|
||||
vert, horiz = d, in
|
||||
} else {
|
||||
vert, horiz = in, d
|
||||
}
|
||||
|
||||
// Normalize lines for intersection finding
|
||||
if vert[1] > vert[3] {
|
||||
vert[1], vert[3] = vert[3], vert[1]
|
||||
}
|
||||
|
||||
if horiz[0] > horiz[2] {
|
||||
horiz[0], horiz[2] = horiz[2], horiz[0]
|
||||
}
|
||||
|
||||
if vert[0] < horiz[0] || vert[0] > horiz[2] || horiz[1] < vert[1] || horiz[1] > vert[3] {
|
||||
// Lines do not have an intersection
|
||||
return [2]int{0, 0}, false
|
||||
}
|
||||
|
||||
return [2]int{vert[0], horiz[1]}, true
|
||||
}
|
||||
|
||||
func (d day3LineSegment) HasPoint(x, y int) bool {
|
||||
switch {
|
||||
case d.IsVertical() && x != d[0]: // vertical line, test Y not on line
|
||||
fallthrough
|
||||
case !d.IsVertical() && y != d[1]: // horizontal line, text X not on line
|
||||
fallthrough
|
||||
case d.IsVertical() && d[1] < d[3] && (y < d[1] || y > d[3]): // vertical low to high, test Y not in range
|
||||
fallthrough
|
||||
case d.IsVertical() && d[1] > d[3] && (y > d[1] || y < d[3]): // vertical high to low, test Y not in range
|
||||
fallthrough
|
||||
case !d.IsVertical() && d[0] < d[2] && (x < d[0] || x > d[2]): // horizontal left to right, test X not in range
|
||||
fallthrough
|
||||
case !d.IsVertical() && d[0] > d[2] && (x > d[0] || x < d[2]): // horizontal right to left, test X not in range
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (d day3LineSegment) IsVertical() bool {
|
||||
return d[0] == d[2]
|
||||
}
|
||||
|
||||
func (d day3LineSegment) Steps() int {
|
||||
if d.IsVertical() {
|
||||
return int(math.Abs(float64(d[1] - d[3])))
|
||||
}
|
||||
return int(math.Abs(float64(d[0] - d[2])))
|
||||
}
|
||||
|
||||
func (d day3LineSegment) StepsToPoint(x, y int) int {
|
||||
if d.IsVertical() {
|
||||
return int(math.Abs(float64(d[1] - y)))
|
||||
}
|
||||
return int(math.Abs(float64(d[0] - x)))
|
||||
}
|
||||
|
||||
type day3Line []day3LineSegment
|
||||
|
||||
func (d day3Line) GetIntersections(in day3Line) [][2]int {
|
||||
var inter [][2]int
|
||||
|
||||
for _, dseg := range d {
|
||||
for _, iseg := range in {
|
||||
if is, ok := dseg.GetIntersection(iseg); ok {
|
||||
inter = append(inter, is)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inter
|
||||
}
|
||||
|
||||
func debugDay3DrawImage(l1, l2 day3Line) error {
|
||||
dest := image.NewRGBA(image.Rect(-500, -500, 500, 500))
|
||||
|
||||
// Draw l1 in red
|
||||
for _, ls := range l1 {
|
||||
ls.Draw(dest, color.RGBA{0xff, 0x0, 0x0, 0xff})
|
||||
}
|
||||
|
||||
// Draw l2 in blue
|
||||
for _, ls := range l2 {
|
||||
ls.Draw(dest, color.RGBA{0x0, 0x0, 0xff, 0xff})
|
||||
}
|
||||
|
||||
// Draw detected intersections in purple
|
||||
for _, i := range l1.GetIntersections(l2) {
|
||||
dest.SetRGBA(i[0], i[1], color.RGBA{0xff, 0x0, 0xff, 0xff})
|
||||
}
|
||||
|
||||
// Draw "home-point" in green
|
||||
dest.SetRGBA(0, 0, color.RGBA{0x0, 0xff, 0x0, 0xff})
|
||||
|
||||
draw2dimg.SaveToPngFile("day03_debug.png", dest)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getDay3MinIntersectionDistance(l1, l2 day3Line, originX, originY int) int {
|
||||
var (
|
||||
inter = l1.GetIntersections(l2)
|
||||
min = math.MaxInt64
|
||||
)
|
||||
|
||||
for _, i := range inter {
|
||||
dist := manhattenDistance(originX, originY, i[0], i[1])
|
||||
if dist > 0 && dist < min {
|
||||
min = dist
|
||||
}
|
||||
}
|
||||
|
||||
return min
|
||||
}
|
||||
|
||||
func getDay3MinIntersectionSteps(l1, l2 day3Line) int {
|
||||
var minSteps int = math.MaxInt64
|
||||
|
||||
for _, is := range l1.GetIntersections(l2) {
|
||||
var combinedsteps int
|
||||
|
||||
for _, l := range []day3Line{l1, l2} {
|
||||
for _, ls := range l {
|
||||
if ls.HasPoint(is[0], is[1]) {
|
||||
combinedsteps += ls.StepsToPoint(is[0], is[1])
|
||||
break
|
||||
}
|
||||
combinedsteps += ls.Steps()
|
||||
}
|
||||
}
|
||||
|
||||
if combinedsteps > 0 && combinedsteps < minSteps {
|
||||
minSteps = combinedsteps
|
||||
}
|
||||
}
|
||||
|
||||
return minSteps
|
||||
}
|
||||
|
||||
func parseDay3LineDefinition(definition string, startX, startY int) (day3Line, error) {
|
||||
var (
|
||||
directions = strings.Split(strings.TrimSpace(definition), ",")
|
||||
pX, pY = startX, startY
|
||||
out day3Line
|
||||
)
|
||||
|
||||
for _, d := range directions {
|
||||
l, err := strconv.Atoi(d[1:])
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Unable to parse direction %q", d)
|
||||
}
|
||||
|
||||
var tX, tY int
|
||||
switch d[0] {
|
||||
case 'D':
|
||||
tX, tY = pX, pY-l
|
||||
|
||||
case 'L':
|
||||
tX, tY = pX-l, pY
|
||||
|
||||
case 'R':
|
||||
tX, tY = pX+l, pY
|
||||
|
||||
case 'U':
|
||||
tX, tY = pX, pY+l
|
||||
|
||||
default:
|
||||
return nil, errors.Wrapf(err, "Invalid direction %q given", d)
|
||||
}
|
||||
|
||||
out = append(out, [4]int{pX, pY, tX, tY})
|
||||
pX, pY = tX, tY
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func solveDay3Part1(inFile string) (int, error) {
|
||||
raw, err := ioutil.ReadFile(inFile)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to read input")
|
||||
}
|
||||
|
||||
defs := strings.Split(string(raw), "\n")
|
||||
l1, err := parseDay3LineDefinition(defs[0], 0, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to parse L1")
|
||||
}
|
||||
|
||||
l2, err := parseDay3LineDefinition(defs[1], 0, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to parse L1")
|
||||
}
|
||||
|
||||
return getDay3MinIntersectionDistance(l1, l2, 0, 0), nil
|
||||
}
|
||||
|
||||
func solveDay3Part2(inFile string) (int, error) {
|
||||
raw, err := ioutil.ReadFile(inFile)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to read input")
|
||||
}
|
||||
|
||||
defs := strings.Split(string(raw), "\n")
|
||||
l1, err := parseDay3LineDefinition(defs[0], 0, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to parse L1")
|
||||
}
|
||||
|
||||
l2, err := parseDay3LineDefinition(defs[1], 0, 0)
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "Unable to parse L1")
|
||||
}
|
||||
|
||||
return getDay3MinIntersectionSteps(l1, l2), nil
|
||||
}
|
2
day03_input.txt
Normal file
2
day03_input.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
R1003,U741,L919,U341,L204,U723,L113,D340,L810,D238,R750,U409,L104,U65,R119,U58,R94,D738,L543,U702,R612,D998,L580,U887,R664,D988,R232,D575,R462,U130,L386,U386,L217,U155,L68,U798,R792,U149,L573,D448,R76,U896,L745,D640,L783,D19,R567,D271,R618,U677,L449,D651,L843,D117,L636,U329,R484,U853,L523,U815,L765,U834,L500,U321,R874,U90,R473,U31,R846,U549,L70,U848,R677,D557,L702,U90,R78,U234,R282,D289,L952,D514,R308,U255,R752,D338,L134,D335,L207,U167,R746,U328,L65,D579,R894,U716,R510,D932,L396,U766,L981,D115,L668,U197,R773,U898,L22,U294,L548,D634,L31,U626,R596,U442,L103,U448,R826,U511,R732,U680,L279,D693,R292,U641,R253,U977,R699,U861,R534,D482,L481,U929,L244,U863,L951,D744,R775,U198,L658,U700,L740,U725,R286,D105,L629,D117,L991,D778,L627,D389,R942,D17,L791,D515,R231,U418,L497,D421,L508,U91,R841,D823,L88,U265,L223,D393,L399,D390,L431,D553,R40,U724,L566,U121,L436,U797,L42,U13,R19,D858,R912,D571,L207,D5,L981,D996,R814,D918,L16,U872,L5,U281,R706,U596,R827,D19,R976,D664,L930,U56,R168,D892,R661,D751,R219,U343,R120,U21,L659,U976,R498,U282,R1,U721,R475,D798,L5,U396,R268,D454,R118,U260,L709,D369,R96,D232,L320,D763,R548,U670,R102,D253,L947,U845,R888,D645,L734,D734,L459,D638,L82,U933,L485,U235,R181,D51,L45,D979,L74,D186,L513,U974,R283,D493,R128,U909,L96,D861,L291,U640,R793,D712,R421,D315,L152,U220,L252,U642,R126,D417,R137,D73,R1,D711,R880,U718,R104,U444,L36,D974,L360,U12,L890,D337,R184,D745,R164,D931,R915,D999,R452,U221,L399,D761,L987,U562,R25,D642,R411,D605,R964
|
||||
L1010,U302,L697,D105,R618,U591,R185,U931,R595,D881,L50,D744,L320,D342,L221,D201,L862,D959,R553,D135,L238,U719,L418,U798,R861,U80,L571,U774,L896,U772,L960,U368,R415,D560,R276,U33,L532,U957,R621,D137,R373,U53,L842,U118,L299,U203,L352,D531,R118,U816,R355,U678,L983,D175,R652,U230,R190,D402,R111,D842,R756,D961,L82,U206,L576,U910,R622,D494,R630,D893,L200,U943,L696,D573,L143,D640,L885,D184,L52,D96,L580,U204,L793,D806,R477,D651,L348,D318,L924,D700,R675,D689,L723,D418,L156,D215,L943,D397,L301,U350,R922,D721,R14,U399,L774,U326,L14,D465,L65,U697,R564,D4,L40,D250,R914,U901,R316,U366,R877,D222,L672,D329,L560,U882,R321,D169,R161,U891,L552,U86,L194,D274,L567,D669,L682,U60,L985,U401,R587,U569,L1,D325,L73,U814,L338,U618,L49,U67,L258,D596,R493,D249,L310,D603,R810,D735,L829,D378,R65,U85,L765,D854,L863,U989,L595,U564,L373,U76,R923,U760,L965,U458,L610,U461,R900,U151,L650,D437,L1,U464,L65,D349,R256,D376,L686,U183,L403,D354,R867,U993,R819,D333,L249,U466,L39,D878,R855,U166,L254,D532,L909,U48,L980,U652,R393,D291,L502,U230,L738,U681,L393,U935,L333,D139,L499,D813,R302,D415,L693,D404,L308,D603,R968,U753,L510,D356,L356,U620,R386,D205,R587,U212,R715,U360,L603,U792,R58,U619,R73,D958,L53,D666,L756,U71,L621,D576,L174,U779,L382,U977,R890,D830,R822,U312,R716,U767,R36,U340,R322,D175,L417,U710,L313,D526,L573,D90,L493,D257,L918,U425,R93,D552,L691,U792,R189,U43,L633,U934,L953,U817,L404,D904,L384,D15,L670,D889,L648,U751,L928,D744,L932,U761,R879,D229,R491,U902,R134,D219,L634,U423,L241
|
102
day03_test.go
Normal file
102
day03_test.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package aoc2019
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseDay3LineDefinition(t *testing.T) {
|
||||
for ld, expLine := range map[string]day3Line{
|
||||
"R5": {{0, 0, 5, 0}},
|
||||
"R8,U5,L5,D3": {{0, 0, 8, 0}, {8, 0, 8, 5}, {8, 5, 3, 5}, {3, 5, 3, 2}},
|
||||
"U7,R6,D4,L4": {{0, 0, 0, 7}, {0, 7, 6, 7}, {6, 7, 6, 3}, {6, 3, 2, 3}},
|
||||
} {
|
||||
l, err := parseDay3LineDefinition(ld, 0, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Day 3 line parser failed: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(expLine, l) {
|
||||
t.Errorf("Mismatch in line of definition %q: exp=%+v got=%+v", ld, expLine, l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDay3LineGetIntersections(t *testing.T) {
|
||||
for ld, expInter := range map[string][][2]int{
|
||||
"R8,U5,L5,D3|U7,R6,D4,L4": {{0, 0}, {6, 5}, {3, 3}},
|
||||
"R75,D30,R83,U83,L12,D49,R71,U7,L72|U62,R66,U55,R34,D71,R55,D58,R83": {{0, 0}, {158, -12}, {146, 46}, {155, 4}, {155, 11}},
|
||||
} {
|
||||
lds := strings.Split(ld, "|")
|
||||
l1, _ := parseDay3LineDefinition(lds[0], 0, 0)
|
||||
l2, _ := parseDay3LineDefinition(lds[1], 0, 0)
|
||||
|
||||
inter := l1.GetIntersections(l2)
|
||||
if !reflect.DeepEqual(expInter, inter) {
|
||||
t.Errorf("Mismatch in line inters of def %q: exp=%+v got=%+v", ld, expInter, inter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDay3MinIntersectionDistance(t *testing.T) {
|
||||
for ld, expDist := range map[string]int{
|
||||
"R8,U5,L5,D3|U7,R6,D4,L4": 6,
|
||||
"R75,D30,R83,U83,L12,D49,R71,U7,L72|U62,R66,U55,R34,D71,R55,D58,R83": 159,
|
||||
"R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51|U98,R91,D20,R16,D67,R40,U7,R15,U6,R7": 135,
|
||||
} {
|
||||
lds := strings.Split(ld, "|")
|
||||
l1, _ := parseDay3LineDefinition(lds[0], 0, 0)
|
||||
l2, _ := parseDay3LineDefinition(lds[1], 0, 0)
|
||||
|
||||
if dist := getDay3MinIntersectionDistance(l1, l2, 0, 0); dist != expDist {
|
||||
t.Errorf("Mismatch in intersection distance of def %q: exp=%d got=%d", ld, expDist, dist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDay3MinIntersectionSteps(t *testing.T) {
|
||||
for ld, expDist := range map[string]int{
|
||||
"R8,U5,L5,D3|U7,R6,D4,L4": 30,
|
||||
"R75,D30,R83,U83,L12,D49,R71,U7,L72|U62,R66,U55,R34,D71,R55,D58,R83": 610,
|
||||
"R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51|U98,R91,D20,R16,D67,R40,U7,R15,U6,R7": 410,
|
||||
} {
|
||||
lds := strings.Split(ld, "|")
|
||||
l1, _ := parseDay3LineDefinition(lds[0], 0, 0)
|
||||
l2, _ := parseDay3LineDefinition(lds[1], 0, 0)
|
||||
|
||||
if dist := getDay3MinIntersectionSteps(l1, l2); dist != expDist {
|
||||
t.Errorf("Mismatch in intersection steps of def %q: exp=%d got=%d", ld, expDist, dist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDay3DebugRender(t *testing.T) {
|
||||
var ld = "R75,D30,R83,U83,L12,D49,R71,U7,L72|U62,R66,U55,R34,D71,R55,D58,R83"
|
||||
|
||||
lds := strings.Split(ld, "|")
|
||||
l1, _ := parseDay3LineDefinition(lds[0], 0, 0)
|
||||
l2, _ := parseDay3LineDefinition(lds[1], 0, 0)
|
||||
|
||||
if err := debugDay3DrawImage(l1, l2); err != nil {
|
||||
t.Fatalf("Day 3 debug failed: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateDay3_Part1(t *testing.T) {
|
||||
codeP0, err := solveDay3Part1("day03_input.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Day 3 solver failed: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Solution Day 3 Part 1: %d", codeP0)
|
||||
}
|
||||
|
||||
func TestCalculateDay3_Part2(t *testing.T) {
|
||||
codeP0, err := solveDay3Part2("day03_input.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("Day 3 solver failed: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("Solution Day 3 Part 2: %d", codeP0)
|
||||
}
|
7
helpers.go
Normal file
7
helpers.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package aoc2019
|
||||
|
||||
import "math"
|
||||
|
||||
func manhattenDistance(x1, y1, x2, y2 int) int {
|
||||
return int(math.Abs(float64(x1-x2)) + math.Abs(float64(y1-y2)))
|
||||
}
|
16
helpers_test.go
Normal file
16
helpers_test.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package aoc2019
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestManhattenDistance(t *testing.T) {
|
||||
for expDist, p := range map[int][4]int{
|
||||
2: {0, 0, 0, 2},
|
||||
3: {0, 0, 3, 0},
|
||||
6: {-3, 0, 3, 0},
|
||||
7: {-3, 0, 0, 4},
|
||||
} {
|
||||
if dist := manhattenDistance(p[0], p[1], p[2], p[3]); dist != expDist {
|
||||
t.Errorf("Unexpected distance for %+v: exp=%d got=%d", p, expDist, dist)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue