Skip to content

Latest commit

 

History

History
236 lines (201 loc) · 8.64 KB

6.md

File metadata and controls

236 lines (201 loc) · 8.64 KB

Day 6: Chronal Coordinates - Part 1 & Part 2

Using only the Manhattan distance, determine the area around each coordinate by counting the number of integer X,Y locations that are closest to that coordinate (and aren't tied in distance to any other coordinate).

Your goal is to find the size of the largest area that isn't infinite. For example, consider the following list of coordinates:

1, 1
1, 6
8, 3
3, 4
5, 5
8, 9

If we name these coordinates A through F, we can draw them on a grid, putting 0,0 at the top left:

..........
.A........
..........
........C.
...D......
.....E....
.B........
..........
..........
........F.

This view is partial - the actual grid extends infinitely in all directions. Using the Manhattan distance, each location's closest coordinate can be determined, shown here in lowercase:

aaaaa.cccc
aAaaa.cccc
aaaddecccc
aadddeccCc
..dDdeeccc
bb.deEeecc
bBb.eeee..
bbb.eeefff
bbb.eeffff
bbb.ffffFf

Locations shown as . are equally far from two or more coordinates, and so they don't count as being closest to any.

In this example, the areas of coordinates A, B, C, and F are infinite - while not shown here, their areas extend forever outside the visible grid. However, the areas of coordinates D and E are finite: D is closest to 9 locations, and E is closest to 17 (both including the coordinate's location itself). Therefore, in this example, the size of the largest area is 17.

What is the size of the largest area that isn't infinite?

Part 2

Find a region near as many coordinates as possible.

For example, suppose you want the sum of the Manhattan distance to all of the coordinates to be less than 32. For each location, add up the distances to all of the given coordinates; if the total of those distances is less than 32, that location is within the desired region. Using the same coordinates as above, the resulting region looks like this:

..........
.A........
..........
...###..C.
..#D###...
..###E#...
.B.###....
..........
..........
........F.

The # represent locations where the distance to all coordinates (A to F) is less than 32. There are 16 #s. (One is on the same location as D and the other is on E). E.g., for for location (x,y) = (4,3) (top center #), the total distance is:

  • Distance to coordinate A: abs(4-1) + abs(3-1) = 5
  • Distance to coordinate B: abs(4-1) + abs(3-6) = 6
  • Distance to coordinate C: abs(4-8) + abs(3-3) = 4
  • Distance to coordinate D: abs(4-3) + abs(3-4) = 2
  • Distance to coordinate E: abs(4-5) + abs(3-5) = 3
  • Distance to coordinate F: abs(4-8) + abs(3-9) = 10
  • Total distance: 5 + 6 + 4 + 2 + 3 + 10 = 30

What is the size of the region containing all locations which have a total distance to all given coordinates of less than 10000?

Solution

Part 1

  • Name each coordinate from AA to ZZ.
  • Get the bounds of the coordinates. I.e., find [minX, maxX] and [minY, maxY].
  • Do a BFS expansion from each coordinate:
    • If a coordinate goes out of bounds, consider it an infinite coordinate, don't expand from the current point (otherwise you'll get more into infinity), but continue expanding from the coordinate.
    • If the coordinante lands on point c that's visited,
      • If the distance on point c is less than the current distance, don't expand from here.
      • If the distance is same and it's not marked as ., mark it as . and deduct regionSize from the existing coordinate on c.

Helper functions:

const getName = (i: number) => String.fromCharCode(
    'A'.charCodeAt(0) + Math.trunc(i / 26),
    'A'.charCodeAt(0) + i % 26
);

const getBounds = (points: IPoint[]): IBound => {
    const cloned = [...points];
    cloned.sort((a, b) => a.col - b.col);
    const colBounds = [first(cloned).col, last(cloned).col] as const;

    cloned.sort((a, b) => a.row - b.row);
    const rowBounds = [first(cloned).row, last(cloned).row] as const;

    return { colBounds, rowBounds };
};

const makeIsInBounds = (bounds: IBound) => (p: IPoint) => {
    const { row, col } = p;
    const { rowBounds: [minR, maxR], colBounds: [minC, maxC] } = bounds;
    if (row < minR || row > maxR || col < minC || col > maxC)
        return false;
    return true;
};

const excludedName = '.';

BFS Search:

const getFiniteAreas = (points: INamedPoint[]) => {
    const queue = new Queue<INamedPoint & {distance: number}>();
    const visited = new Map<string, {name: string; distance: number}>(); // point -> name&distance
    const isInBounds = makeIsInBounds(getBounds(points));
    const areaPerCoordinate = new Map<string, number>(points.map(p => [p.name, 0])); // name -> regionSize
    const hasInfiniteArea = new Set<string>();

    points.forEach(p => queue.enqueue({...p, distance: 0}));

    while (!queue.isEmpty()) {
        const { distance, name, ...p } = queue.dequeue()!;
        const pointStr = toKey(p);

        if (!visited.has(pointStr)) {
            visited.set(pointStr, {name, distance});
            areaPerCoordinate.set(name, (areaPerCoordinate.get(name) ?? 0) + 1);
        }
        else {
            const {
                name: contenderName, distance: contenderDistance
            } = visited.get(pointStr)!;
            if (contenderDistance < distance || contenderName === excludedName || contenderName === name)
                continue;
            // if both are equidistance, put a '.' on the spot, and subtract that point
            // from the name currently on it
            else if (contenderDistance === distance) {
                visited.set(pointStr, {name: excludedName, distance});
                areaPerCoordinate.set(contenderName, areaPerCoordinate.get(contenderName)! - 1);
                continue;
            }
            // the case of contenderDistance > distance shouldn't happen, because otherwise it
            // would've been popped
        }

        let neighbors = getNeighbors(p);
        if (!neighbors.every(isInBounds))
            hasInfiniteArea.add(name);

        neighbors = neighbors.filter(isInBounds).filter(p => !visited.has(toKey(p)));
        for (const neighbor of neighbors)
            queue.enqueue({...neighbor, name, distance: distance + 1});
    }

    return new Map(Array.from(areaPerCoordinate.entries()).filter(e => !hasInfiniteArea.has(e[0])));
};

const getLargestFiniteArea = (map: Map<string, number>) => Math.max(...Array.from(map.values()));

Part 2

  • The minimum distance to all coordinates is at the center of all points, which is obtained by getting the average of the x's and y's.
  • Start a BFS or a DFS expansion from the center: expand all points that produce a distance of less than 10,000 and have not been visited.

Helper functions:

const getDistance = (from: IPoint, to: IPoint) =>
    Math.abs(from.col - to.col) + Math.abs(from.row - to.row);

const makeGetDistanceAll =
    (points: IPoint[]) =>
    (p: IPoint) =>
        points.reduce((a, c) => a + getDistance(p, c), 0);

const getCenter = (points: IPoint[]) => ({
    // avg betw all points is min distance - it's the center of all points
    row: Math.round(points.reduce((a, c) => a + c.row, 0) / points.length),
    col: Math.round(points.reduce((a, c) => a + c.col, 0) / points.length)
});

BFS

const getRegionClosestToAll = (maxDistance: number, points: IPoint[]) => {
    const starting = getCenter(points);
    const isInBounds = makeIsInBounds(getBounds(points));
    const getDistanceAll = makeGetDistanceAll(points);
    const queue = new Queue<IPoint>();
    const visited = new GenericSet<IPoint>(toKey);
    let regionSize = 0;

    queue.enqueue(starting);
    visited.add(starting);
    while (!queue.isEmpty()) {
        regionSize++;

        getNeighbors(queue.dequeue()!)
            .filter(isInBounds)
            .filter(p => !visited.has(p))
            .filter(p => getDistanceAll(p) < maxDistance)
            .forEach(n => {
                queue.enqueue(n);
                visited.add(n);
            });
    }
    return regionSize;
};

DFS

const getRegionClosestToAll2 = (maxDistance: number, points: IPoint[]) => {
    const isInBounds = makeIsInBounds(getBounds(points));
    const getDistanceAll = makeGetDistanceAll(points);
    const visited = new GenericSet<IPoint>(toKey);

    const helper = (p: IPoint): number => {
        if (visited.has(p))
            return 0;

        visited.add(p);
        return 1 + getNeighbors(p)
            .filter(isInBounds)
            .filter(n => !visited.has(n))
            .filter(n => getDistanceAll(n) < maxDistance)
            .map(helper)
            .reduce((a, c) => a + c, 0);
    };

    return helper(getCenter(points));
};