01020304050607080910111213141516171819202122232425

Advent of Code

2022/12

Hill Climbing Algorithm

in C#

by encse

You try contacting the Elves using your handheld device, but the river you're following must be too low to get a decent signal.

You ask the device for a heightmap of the surrounding area (your puzzle input). The heightmap shows the local area from above broken into a grid; the elevation of each square of the grid is given by a single lowercase letter, where a is the lowest elevation, b is the next-lowest, and so on up to the highest elevation, z.

Read the full puzzle.

namespace AdventOfCode.Y2022.Day12;

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

//
// Standard breadth-first algorithm, starting from the goal node and walking backwards. 
// I used a dictionary to represent valid coordinates, it's very handy when in need of
// enumerating all coordinates or checking if we are stepping to valid location.
//
[ProblemName("Hill Climbing Algorithm")]
class Solution : Solver {

    // I feel like a cartographer today
    record struct Coord(int lat, int lon);

    // we have two 'char' like things, let's introduce wrappers to keep them well separated in code
    record struct Symbol(char value);
    record struct Elevation(char value);

    // locations on the map will be represented by the following structure of points-of-interests.
    record struct Poi(Symbol symbol, Elevation elevation, int distanceFromGoal);

    Symbol startSymbol = new Symbol('S');
    Symbol goalSymbol = new Symbol('E');
    Elevation lowestElevation = new Elevation('a');
    Elevation highestElevation = new Elevation('z');

    public object PartOne(string input) =>
        GetPois(input)
            .Single(poi => poi.symbol == startSymbol)
            .distanceFromGoal;

    public object PartTwo(string input) =>
        GetPois(input)
            .Where(poi => poi.elevation == lowestElevation)
            .Select(poi => poi.distanceFromGoal)
            .Min();

    IEnumerable<Poi> GetPois(string input) {
        var map = ParseMap(input);
        var goal = map.Keys.Single(point => map[point] == goalSymbol);

        // starting from the goal symbol compute shortest paths for each point of 
        // the map using a breadth-first search.
        var poiByCoord = new Dictionary<Coord, Poi>() {
            {goal, new Poi(goalSymbol, GetElevation(goalSymbol), 0)}
        };

        var q = new Queue<Coord>();
        q.Enqueue(goal);
        while (q.Any()) {
            var thisCoord = q.Dequeue();
            var thisPoi = poiByCoord[thisCoord];

            foreach (var nextCoord in Neighbours(thisCoord).Where(map.ContainsKey)) {
                if (poiByCoord.ContainsKey(nextCoord)) {
                    continue;
                }

                var nextSymbol = map[nextCoord];
                var nextElevation = GetElevation(nextSymbol);

                if (thisPoi.elevation.value - nextElevation.value <= 1) {
                    poiByCoord[nextCoord] = new Poi(
                        symbol: nextSymbol,
                        elevation: nextElevation,
                        distanceFromGoal: thisPoi.distanceFromGoal + 1
                    );
                    q.Enqueue(nextCoord);
                }
            }

        }
        return poiByCoord.Values;
    }

    Elevation GetElevation(Symbol symbol) =>
        symbol.value switch {
            'S' => lowestElevation,
            'E' => highestElevation,
            _ => new Elevation(symbol.value)
        };

    // locations are parsed into a dictionary so that valid coordinates and
    // neighbours are easy to deal with
    ImmutableDictionary<Coord, Symbol> ParseMap(string input) {
        var lines = input.Split("\n");
        return (
            from y in Enumerable.Range(0, lines.Length)
            from x in Enumerable.Range(0, lines[0].Length)
            select new KeyValuePair<Coord, Symbol>(
                new Coord(x, y), new Symbol(lines[y][x])
            )
        ).ToImmutableDictionary();
    }

    IEnumerable<Coord> Neighbours(Coord coord) =>
        new[] {
           coord with {lat = coord.lat + 1},
           coord with {lat = coord.lat - 1},
           coord with {lon = coord.lon + 1},
           coord with {lon = coord.lon - 1},
        };
}

Please ☆ my repo if you like it!

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