Unlocking the North Pole: Solving Advent of Code Day 1

Back in 2022, I challenged myself with a different kind of Advent project.

Instead of running an Advent of Code, I ran an Advent of Posts. I wrote 22 posts in the days before Christmas. I missed two days but I declared the mission complete.

This year, I’m following the Advent of Code. I’d like to challenge myself to write “functionalish” solutions.

Here are the instructions for Day 1 and my solution:

Opening the secret entrance to the North Pole

Since there are only two rotations: left and right, I’m creating a discriminated union-like hierarchy. And I’m writing a separate class for the dial itself.

abstract record Rotation
{
    public record Left(int Distance) : Rotation;
    public record Right(int Distance) : Rotation;
}

record Dial(int Position)
{
    public Dial Turn(Rotation rotation)
    {
        return rotation switch
        {
            Rotation.Left f => this with { Position = (Position - f.Distance)%100 },
            Rotation.Right r => this with { Position = (Position + r.Distance)%100 },
            _ => throw new NotImplementedException(),
        };
    }
}

So far, I could do:

var d = new Dial(11);
d.Turn(new Rotation.Right(8)).Turn(new Rotation.Left(19))
// [Dial { Position = 0 }]

I have to confess, figuring out the modulus operation to move the dial took me a while.

With the dial rotating, the only missing piece is applying rotations and counting zeros,

static class Password
{
    public static int Find(Dial d, IEnumerable<Rotation> rotations)
    {
        var state = rotations.Aggregate((Dial: d, Password: 0), (state, rotation) =>
        {
            var dial = state.Dial.Turn(rotation);
            var pwd = dial.Position == 0 ? state.Password + 1: state.Password;
            return (dial, pwd);
        });
        return state.Password;
    }
}

I’m using LINQ Aggregate method to turn the dial and count the zeros.

That works, but Copilot and others’ solutions showed me a cleaner approach: separating moving the dial and counting with Scan(),

static class Password
{
    public static int Find(Dial d, IEnumerable<Rotation> rotations)
    {
        return rotations.Scan(d, (dial, rotation) => dial.Turn(rotation))
                        .Count(d => d.Position == 0);
    }

    public static IEnumerable<TAccumulate> Scan<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        var acc = seed;
        yield return acc;
        foreach (var item in source)
        {
            acc = func(acc, item);
            yield return acc;
        }
    }
}

And here’s the full solution:

var dial = new Dial(50);
var rotations = new Rotation[]
{
    new Rotation.Left(68),
    new Rotation.Left(30),
    new Rotation.Right(48),
    new Rotation.Left(5),
    new Rotation.Right(60),
    new Rotation.Left(55),
    new Rotation.Left(1),
    new Rotation.Left(99),
    new Rotation.Right(14),
    new Rotation.Left(82)
};
var pwd = Password.Find(dial, rotations);
Console.WriteLine(pwd);
Console.ReadLine();

abstract record Rotation
{
    public record Left(int Distance) : Rotation;
    public record Right(int Distance) : Rotation;
}

record Dial(int Position)
{
    public Dial Turn(Rotation rotation)
    {
        return rotation switch
        {
            Rotation.Left f => this with { Position = (Position - f.Distance)%100 },
            Rotation.Right r => this with { Position = (Position + r.Distance)%100 },
            _ => throw new NotImplementedException(),
        };
    }
}

static class Password
{
    public static int Find(Dial d, IEnumerable<Rotation> rotations)
    {
        return rotations.Scan(d, (dial, rotation) => dial.Turn(rotation))
                        .Count(d => d.Position == 0);
    }

    public static IEnumerable<TAccumulate> Scan<TSource, TAccumulate>(
        this IEnumerable<TSource> source,
        TAccumulate seed,
        Func<TAccumulate, TSource, TAccumulate> func)
    {
        var acc = seed;
        yield return acc;
        foreach (var item in source)
        {
            acc = func(acc, item);
            yield return acc;
        }
    }
}

Advent of Code sharpens your coding skills. But coding is more than typing symbols fast. It’s also about teamwork, collaboration, and many skills I share in my book, Street-Smart Coding: 30 Ways to Get Better at Coding. That’s the roadmap I wish I’d known from day one.

Get your copy of Street-Smart Coding here