Advent Of Code 2021 - Day 5

Day 5 of Advent of Code was a little bit tricky actually, and I struggled for a bit between Part 1 and Part 2.

We have come across some thermal vents that produces large clouds. We want to avoid these clouds. The clouds seems to form in lines, according to our input that looks like this:

0,9 -> 5,9
8,0 -> 0,8
9,4 -> 3,4
2,2 -> 2,1
7,0 -> 7,4
6,4 -> 2,0
0,9 -> 2,9
3,4 -> 1,4
0,0 -> 8,8
5,5 -> 8,2

These numbers represents the lines, were the first set of numbers is the starting point, and the second set is the end point (x1,y1 -> x2,y2). The lines will cover any points inbetween the start and end. For part 1, we're only looking at horizontal and vertical lines, not the diagonal ones. This means only lines where x1 == x2 or y1 == y2.

The input above can be translated to the following diagram:

..1....1..
..1....1..
.......1..
.112111211
..........
..........
..........
..........
222111....

The numbers represent the number of lines that cover that particular point. Higher number equals a more dangerous point, and we want to avoid that. The question is, how many points are cover by two or more lines? In this example, it's 5 points that we need to avoid.

For Part 2, the question is the same but this time we need to include diagonal lines aswell. My solution can take care of both parts, we just need to pass a bool telling it to consider diagonal lines or not.

I've created a class to help me handle the lines:

public class Line
{
    public int xStart { get; set; }
    public int xEnd { get; set; }
    public int yStart { get; set; }
    public int yEnd { get; set; }
}

Then we parse our input, and create a list of lines:

var input = File.ReadAllLines("input.txt");           
var lines = new List<Line>();

foreach (var val in input)
{
    var values = val.Split("->");
    var startValues = values[0].Split(',');
    var endValues = values[1].Split(',');

    var line = new Line
    {
        xStart = int.Parse(startValues[0].Trim()),
        yStart = int.Parse(startValues[1].Trim()),
        xEnd = int.Parse(endValues[0].Trim()),
        yEnd = int.Parse(endValues[1].Trim())
    };

    lines.Add(line);
}

Finally, it's time to get some answers:

var points = new List<Point>();
var includeDiagonal = true;

foreach (var line in lines)
{
    if (line.xStart == line.xEnd)
    {
        var start = line.yStart < line.yEnd ? line.yStart : line.yEnd;

        var yValues = Enumerable.Range(start, Math.Abs(line.yStart - line.yEnd) + 1);
        foreach (var value in yValues)
        {
            points.Add(new Point { X = line.xStart, Y = value });
        }
    }
    else if (line.yStart == line.yEnd)
    {
        var start = line.xStart < line.xEnd ? line.xStart : line.xEnd;
        var xValues = Enumerable.Range(start, Math.Abs(line.xStart - line.xEnd) + 1);
        foreach (var value in xValues)
        {
            points.Add(new Point { X = value, Y = line.yStart });
        }
    }
    else if (includeDiagonal)
    {
        var xDirection = line.xStart == line.xEnd ? 0 : line.xEnd > line.xStart ? 1 : -1;
        var yDirection = line.yStart == line.yEnd ? 0 : line.yEnd > line.yStart ? 1 : -1;
        var distance = Math.Max(Math.Abs(line.xStart - line.xEnd), Math.Abs(line.yStart - line.yEnd)) + 1;

        points.AddRange(Enumerable.Range(0, distance).Select(e => new Point(line.xStart + (e * xDirection), line.yStart + (e * yDirection))));
    }
}

Console.WriteLine($"Answer: {points.GroupBy(p => p).Count(x => x.Count() > 1)}");

As you can see, the includeDiagonal bool is what decides if we get the answer for Part 1 or Part 2.

For Part 1 it's quite simple. We get the range of values covered. If xStart and xEnd are the same, we need to get all the yValues between yStart and yEnd, and vice versa. Then we just add the points to our list of all points and move on to the next line. At the end we can then group our points and see how many of them exists more than once in our list.

For vertical or horizontal lines, it doesn't matter if the start is higher or lower than the end. We can just find the difference and use that to get all points in between. When it comes to diagonal lines, we have to know if it's going up, down, left or right.

Finding the direction is not that difficult, we just need to compare the start and end values for X and Y. Getting the distance is also quite easy, and works similar as to what we did with horizontal/vertical lines.

What took me a bit too long is to figure out how to correctly create new points based on this information. But, since we know the distance and both the xDirection and yDirection, we can quite easily generate new points by creating a range of number using the distance, and then multiply each number in the range with the direction modifiers. Add the result to the starting point values, and we have our new points.

Finally, we can group and count our points just like we did in Part 1. So to get both answers, run the code twice and just change includeDiagonal depending on which part you're interested in.