diff --git a/2018/day10-1.go b/2018/day10-1.go new file mode 100644 index 0000000..d271691 --- /dev/null +++ b/2018/day10-1.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + + "internal/day10" + "internal/util" +) + +func main() { + data := util.ReadInput() + points := day10.ParseInput(data) + + i := 0 // debug + for { + for _, point := range points { + point.Move() + } + + groups := day10.GroupPoints(points) + if groups != nil { + for _, group := range groups { + day10.DrawPoints(group) + fmt.Println() + } + return + } + + // debug + i++ + if i > 100000 { + fmt.Println("oops") + return + } + // end debug + } +} diff --git a/2018/internal/day10/debug.go b/2018/internal/day10/debug.go new file mode 100644 index 0000000..6d06d38 --- /dev/null +++ b/2018/internal/day10/debug.go @@ -0,0 +1,15 @@ +package day10 + +import ( + "fmt" +) + +func (p *Point) DebugPrintPoint() { + fmt.Printf("p=<%d, %d>, v=<%d, %d>\n", p.X, p.Y, p.Xv, p.Yv) +} + +func DebugPrintPoints(points []*Point) { + for _, point := range points { + point.DebugPrintPoint() + } +} diff --git a/2018/internal/day10/grid.go b/2018/internal/day10/grid.go new file mode 100644 index 0000000..fa4a400 --- /dev/null +++ b/2018/internal/day10/grid.go @@ -0,0 +1,46 @@ +// Unused solution: this works fine for the example, but doesn't work at all for the +// real input data. +package day10 + +import "fmt" // debug + +// AnalyzePoints tries to determine whether the data looks sufficiently ordered. +// It returns 'true' if it thinks we've got letters. +func AnalyzePoints(points []*Point) bool { + pointArr := makeBuffer(points) + + // count adjacencies + count := 0 + + for y := 0; y < len(pointArr[0]); y++ { + for x := 0; x < len(pointArr); x++ { + if pointArr[x][y] != '#' { + continue + } + + if checkPoint(pointArr, x-1, y) || + checkPoint(pointArr, x+1, y) || + checkPoint(pointArr, x, y-1) || + checkPoint(pointArr, x, y+1) { + count++ + } + } + } + + fmt.Println(float64(count) / float64(len(points))) // debug + return float64(count) >= 0.8*float64(len(points)) +} + +// Checks the point in the grid. If it is '#' returns true. +func checkPoint(grid [][]byte, x, y int) bool { + if x < 0 || x >= len(grid) || + y < 0 || y >= len(grid[0]) { + return false + } + + if grid[x][y] == '#' { + return true + } + + return false +} diff --git a/2018/internal/day10/groups.go b/2018/internal/day10/groups.go new file mode 100644 index 0000000..aa32c20 --- /dev/null +++ b/2018/internal/day10/groups.go @@ -0,0 +1,114 @@ +package day10 + +// GroupPoints creates groups of points, then sorts them and returns the sorted list +// A 'group' is a series of points that are adjacent to each other. +// If this function finds any non-adjacent points, it returns nil. +func GroupPoints(points []*Point) [][]*Point { + // this seems a bit weird, but it's mapping points that just contain an x,y value to + // the actual point objects with velocity information. + pointMap := make(map[int]map[int]*Point) + for _, p := range points { + if pointMap[p.X] == nil { + pointMap[p.X] = make(map[int]*Point) + } + pointMap[p.X][p.Y] = p + } + + // If any point is not adjacent to at least one other, we are not done moving. + for _, p := range points { + if !adjacencyInMap(pointMap, p) { + return nil + } + } + + // now, for each point, check for presence in existing groups, + // then check the map for adjacencies + // This could be more efficient, but the readability of the range + // is desirable. + groups := [][]*Point{} + for _, p := range points { + if checkGroups(groups, p) { + continue + } + + // Find and place all adjacent points. + group := buildGroup(pointMap, []*Point{}, p) + groups = append(groups, group) + } + + // TODO: sort the groups + + return groups +} + +// returns true if point is already a member of one of groups +func checkGroups(groups [][]*Point, point *Point) bool { + for _, group := range groups { + if checkGroup(group, point) { + return true + } + } + return false +} + +func checkGroup(group []*Point, point *Point) bool { + for _, member := range group { + if point == member { + return true + } + } + return false +} + +func arePointsAdjacent(p1, p2 *Point) bool { + return (p1.X == p2.X-1 || p1.X == p2.X+1 || p1.X == p2.X) && + (p1.Y == p2.Y-1 || p1.Y == p2.Y+1 || p1.Y == p2.Y) +} + +func adjacencyInMap(pMap map[int]map[int]*Point, p *Point) bool { + return (pMap[p.X-1] != nil && pMap[p.X-1][p.Y] != nil) || + (pMap[p.X+1] != nil && pMap[p.X+1][p.Y] != nil) || + pMap[p.X][p.Y-1] != nil || pMap[p.X][p.Y+1] != nil +} + +// recursively walk all adjacent points, adding them to the group. +func buildGroup(pMap map[int]map[int]*Point, group []*Point, p *Point) []*Point { + // this both prevents duplicates and prevents infinite recursion + if checkGroup(group, p) { + return group + } + + group = append(group, p) + + // look in each of 8 directions, if a point is found, recurse + if pMap[p.X-1] != nil { + if pMap[p.X-1][p.Y] != nil { + group = buildGroup(pMap, group, pMap[p.X-1][p.Y]) + } + if pMap[p.X-1][p.Y-1] != nil { + group = buildGroup(pMap, group, pMap[p.X-1][p.Y-1]) + } + if pMap[p.X-1][p.Y+1] != nil { + group = buildGroup(pMap, group, pMap[p.X-1][p.Y+1]) + } + } + if pMap[p.X+1] != nil { + if pMap[p.X+1][p.Y] != nil { + group = buildGroup(pMap, group, pMap[p.X+1][p.Y]) + } + if pMap[p.X+1][p.Y-1] != nil { + group = buildGroup(pMap, group, pMap[p.X+1][p.Y-1]) + } + if pMap[p.X+1][p.Y+1] != nil { + group = buildGroup(pMap, group, pMap[p.X+1][p.Y+1]) + } + } + if pMap[p.X][p.Y-1] != nil { + group = buildGroup(pMap, group, pMap[p.X][p.Y-1]) + } + if pMap[p.X][p.Y+1] != nil { + group = buildGroup(pMap, group, pMap[p.X][p.Y+1]) + } + + return group +} diff --git a/2018/internal/day10/input.go b/2018/internal/day10/input.go new file mode 100644 index 0000000..50e763e --- /dev/null +++ b/2018/internal/day10/input.go @@ -0,0 +1,39 @@ +package day10 + +import ( + "regexp" + "strconv" + "strings" +) + +func ParseInput(data []string) []*Point { + points := []*Point{} + regex := regexp.MustCompile("position=<(.*)> velocity=<(.*)>") + + for _, line := range data { + p := &Point{} + matches := regex.FindSubmatch([]byte(line)) + p.X, p.Y = parseVector2(string(matches[1])) + p.Xv, p.Yv = parseVector2(string(matches[2])) + + points = append(points, p) + } + + return points +} + +func parseVector2(data string) (int, int) { + dataBuffer := strings.Split(data, ", ") + + x, err := strconv.Atoi(strings.TrimSpace(dataBuffer[0])) + if err != nil { + panic(err) + } + + y, err := strconv.Atoi(strings.TrimSpace(dataBuffer[1])) + if err != nil { + panic(err) + } + + return x, y +} diff --git a/2018/internal/day10/point.go b/2018/internal/day10/point.go new file mode 100644 index 0000000..0c9bd71 --- /dev/null +++ b/2018/internal/day10/point.go @@ -0,0 +1,77 @@ +package day10 + +import ( + "fmt" +) + +type Point struct { + X, Y int + Xv, Yv int +} + +func (p *Point) Move() { + p.X += p.Xv + p.Y += p.Yv +} + +func DrawPoints(points []*Point) { + pointArr := makeBuffer(points) + + // print the buffer + for y := 0; y < len(pointArr[0]); y++ { + for x := 0; x < len(pointArr); x++ { + if pointArr[x][y] != 0 { + fmt.Printf("%c", pointArr[x][y]) + } else { + fmt.Printf(".") + } + } + fmt.Println() + } +} + +// findBounds returns xMin, xMax, yMin, and yMax from the provided points. +func findBounds(points []*Point) (int, int, int, int) { + // find min and max values + var xMin, xMax, yMin, yMax int + for _, point := range points { + if point.X < xMin { + xMin = point.X + } + if point.X > xMax { + xMax = point.X + } + if point.Y < yMin { + yMin = point.Y + } + if point.Y > yMax { + yMax = point.Y + } + } + + return xMin, xMax, yMin, yMax +} + +func makeBuffer(points []*Point) [][]byte { + xMin, xMax, yMin, yMax := findBounds(points) + // get the total magnitude; we'll do adjustments later + xRange := xMax - xMin + yRange := yMax - yMin + + // construct the buffer + pointArr := make([][]byte, xRange+1) + for i := 0; i < len(pointArr); i++ { + pointArr[i] = make([]byte, yRange+1) + } + + // put the points into the buffer + for _, p := range points { + // the x and y values are adjusted by the mins + x := p.X - xMin + y := p.Y - yMin + + pointArr[x][y] = '#' + } + + return pointArr +}