01020304050607080910111213141516171819202122232425

Advent of Code

2021/9

Smoke Basin

in C#

by encse

These caves seem to be lava tubes. Parts are even still volcanically active; small hydrothermal vents release smoke into the caves that slowly settles like rain.

If you can model how the smoke flows through the caves, you might be able to avoid it and be that much safer. The submarine generates a heightmap of the floor of the nearby caves for you (your puzzle input).

Read the full puzzle.

using System.Collections.Immutable;
using System.Collections.Generic;
using System.Linq;

namespace AdventOfCode.Y2021.Day09;

[ProblemName("Smoke Basin")]
class Solution : Solver {

    public object PartOne(string input) {
        var map = GetMap(input);

        // find the 'low points' and return a hash computed from their heights:
        return GetLowPoints(map).Select(point => 1 + map[point]).Sum();
    }

    public object PartTwo(string input) {
        var map = GetMap(input);

        // find the 3 biggest basins and return a hash computed from their size:
        return GetLowPoints(map)
            .Select(p => BasinSize(map, p))
            .OrderByDescending(basinSize => basinSize)
            .Take(3)
            .Aggregate(1, (m, basinSize) => m * basinSize);
    }

    // store the points in a dictionary so that we can iterate over them and 
    // to easily deal with points outside the area using GetValueOrDefault
    ImmutableDictionary<Point, int> GetMap(string input) {
        var map = input.Split("\n");
        return (
            from y in Enumerable.Range(0, map.Length)
            from x in Enumerable.Range(0, map[0].Length)
            select new KeyValuePair<Point, int>(new Point(x, y), map[y][x] - '0')
        ).ToImmutableDictionary();
    }

    IEnumerable<Point> Neighbours(Point point) => 
        new [] {
           point with {y = point.y + 1},
           point with {y = point.y - 1},
           point with {x = point.x + 1},
           point with {x = point.x - 1},
        };

    public IEnumerable<Point> GetLowPoints(ImmutableDictionary<Point, int>  map) =>
        from point in map.Keys 
        // point is low if each of its neighbours is higher:
        where Neighbours(point).All(neighbour => map[point] < map.GetValueOrDefault(neighbour, 9))
        select point;

    public int BasinSize(ImmutableDictionary<Point, int> map, Point point) {
        // flood fill algorithm
        var filled = new HashSet<Point>{point};
        var queue = new Queue<Point>(filled);

        while (queue.Any()) {
            foreach (var neighbour in Neighbours(queue.Dequeue()).Except(filled)) {
                if (map.GetValueOrDefault(neighbour, 9) != 9) {
                    queue.Enqueue(neighbour);
                    filled.Add(neighbour);
                }
            }
        }
        return filled.Count;
    }
}

record Point(int x, int y);

Please ☆ my repo if you like it!

© 2025 Advent of Code is a registered trademark in the US Images provided by Bing image creator