1
0
Fork 0
mirror of https://github.com/Luzifer/aoc2019.git synced 2024-10-18 03:04:19 +00:00

Add solution for Day 3

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-12-03 17:14:23 +01:00
parent 9b67f2728c
commit ef7a1c5e33
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
6 changed files with 397 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
day03_debug.png

269
day03.go Normal file
View 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
View 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
View 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
View 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
View 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)
}
}
}