package coords import ( "strconv" "strings" ) type Point struct { X int Y int RectArea int } // Turn input data into useable structured point objects. func ParsePoints(data []string) []*Point { points := []*Point{} for _, pointInfo := range data { components := strings.Split(pointInfo, ", ") x, _ := strconv.Atoi(components[0]) y, _ := strconv.Atoi(components[1]) points = append(points, &Point{ X: x, Y: y, }) } return points } // Mutates the passed-in data in-place, computing the rectilinear // area for all points within the space bounded by (0,0) and the points themselves. // Sets the area of the 4 corner-most points (which definitionally must have // unbounded areas) to -1 func ComputeRectilinearAreas(data []*Point) { // first we find the bounds maxX, maxY := findUpperBounds(data) // now for each integer point in our range, we compute the distance to each // point. We save the closest point and its distance, then: // 1. If there are two or more identically closest points, we do nothing. // 2. Otherwise, we increment RectArea for the closest point. for x := -maxX; x <= maxX*2; x++ { for y := -maxY; y <= maxY*2; y++ { closest := findClosest(data, &Point{X: x, Y: y}) if closest == nil { continue } closest.RectArea++ } } // now we find any unbounded points and set their areas to the sentinel // value detectUnboundedPoints(data) } // ComputeNearnessRegion takes a list of points, returns the number of points // whose rectilinear distance to *all* the listed points sums to less than the // target value. func ComputeNearnessRegion(points []*Point, target int) int { maxX, maxY := findUpperBounds(points) // this is a 'brute-force' solution that just picks a suitably wide // area to search. area := 0 for x := -target; x < maxX+target; x++ { for y := -target; y < maxY+target; y++ { if sumDistances(points, &Point{X: x, Y: y}) < target { area++ } } } return area } // sumDistances calculates the distance from each point in `points` to `target`, and // returns the sum of all of those distances func sumDistances(points []*Point, target *Point) int { area := 0 for _, point := range points { area += Distance(point, target) } return area } // Find the nearest point in `data` to `point`, using rectilinear distance. func findClosest(data []*Point, point *Point) *Point { closest := data[0] distance := Distance(data[0], point) for i := 1; i < len(data); i++ { candidate := data[i] newDistance := Distance(candidate, point) if newDistance < distance { closest = candidate distance = newDistance } else if newDistance == distance { closest = nil } } return closest } func detectUnboundedPoints(data []*Point) { for _, candidate := range data { // look in each direction up := findClosest(data, &Point{X: candidate.X, Y: candidate.Y + 1000000}) down := findClosest(data, &Point{X: candidate.X, Y: candidate.Y - 1000000}) left := findClosest(data, &Point{X: candidate.X + 1000000, Y: candidate.Y}) right := findClosest(data, &Point{X: candidate.X - 1000000, Y: candidate.Y}) // if any of those points are closest to our point, we're unbounded if up == candidate || down == candidate || left == candidate || right == candidate { candidate.RectArea = -1 } } } func findUpperBounds(points []*Point) (int, int) { maxX := 0 maxY := 0 for _, point := range points { if point.X > maxX { maxX = point.X } if point.Y > maxY { maxY = point.Y } } return maxX, maxY } func Distance(p1 *Point, p2 *Point) int { return abs(p2.Y-p1.Y) + abs(p2.X-p1.X) } func abs(x int) int { if x < 0 { return -x } return x }