Advent of Code Day 3: Calculating the Joltage of Batteries

On Day 3 of Advent of Code, we’re finding the joltage of a bank of batteries to power an escalator.

Elevators are out of service and the escalator needs the batteries with the higher joltage.

Finding pairs of batteries

I use brute force to find all battery pairs.

Two for loops didn’t feel old-schoool. So I borrow a LINQ query from this StackOverflow answer.

static class Extensions
{
    public static IEnumerable<(T First, T Second)> Pairs<T>(this IEnumerable<T> self)
    {
        return self.SelectMany((fst, i) => self.Skip(i + 1).Select(snd => (fst, snd)));
    }
}

Calculating the joltage

Once we have the pairs, I need two methods to calculate joltage: for a pair and for a bank.

static int FindJoltage(Bank bank)
    => bank.Batteries.Pairs().Select(FindJoltageOfPair).Max();

static int FindJoltageOfPair((int Battery1, int Battery2) pair)
    => pair.Battery1 * 10 + pair.Battery2;

Here’s my full solution:


var allBanks = new List<Bank>
{
    new Bank([ 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1 ]),
    new Bank([ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9 ]),
    new Bank([ 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 3, 4, 2, 7, 8 ]),
    new Bank([ 8, 1, 8, 1, 8, 1, 9, 1, 1, 1, 1, 2, 1, 1, 1 ])
};
var totalJoltage = allBanks.Select(FindJoltage).Sum();
Console.WriteLine(totalJoltage);
Console.ReadLine();

static int FindJoltage(Bank bank)
    => bank.Batteries.Pairs().Select(FindJoltageOfPair).Max();

static int FindJoltageOfPair((int Battery1, int Battery2) pair)
    => pair.Battery1 * 10 + pair.Battery2;

record Bank(IEnumerable<int> Batteries);

static class Extensions
{
    public static IEnumerable<(T First, T Second)> Pairs<T>(this IEnumerable<T> self)
    {
        return self.SelectMany((fst, i) => self.Skip(i + 1).Select(snd => (fst, snd)));
    }
}

Et voilà!

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

Advent of Code Day 2: Finding Invalid Product IDs

On Day 2 of Advent of Code, we’re helping the Elves find invalid IDs.

I considered a string-based solution, but that felt like cheating. So I tried a “numeric” one instead.

Let’s start with a bottom-up approach.

Finding if a product ID is invalid

First, I count the digits of a number, then split the number in half using powers of 10.

I borrowed this StackOverflow answer to count digits.

And since a product ID is invalid if it has a sequence repeated twice, product IDs with an odd number of digits are valid.

static bool IsAnInvalidId(int n)
    => HasSequenceRepeatedTwice(n);

static bool HasSequenceRepeatedTwice(int n)
{
    var digitCount = (int)(Math.Log10(n) + 1);
    if (digitCount % 2 != 0)
    {
        return false;
    }

    var firstHalf = n / (int)Math.Pow(10, digitCount / 2);
    var secondHalf = n % (int)Math.Pow(10, digitCount / 2);

    return firstHalf == secondHalf;
}

Once we check for repeated sequences, the only task left is counting IDs in each range.

Counting invalid product IDs

I’m creating a function to enumerate a range, Enumerate(), and a LINQ query to find and count invalid IDs.

Here’s my full solution:

var ranges = new[]
{
    new Range(11, 22),
    new Range(95, 115),
    new Range(998, 1012),
    new Range(1188511880, 1188511890),
    new Range(222220, 222224),
    new Range(1698522, 1698528),
    new Range(446443, 446449),
    new Range(38593856, 38593862),
    new Range(565653, 565659),
    new Range(824824821, 824824827),
    new Range(2121212118, 2121212124),
};

var sum = ranges.SelectMany(Enumerate)
                .Where(IsAnInvalidId)
                .Sum();
Console.WriteLine(sum);
Console.ReadLine();

static IEnumerable<int> Enumerate(Range r)
    => Enumerable.Range(r.Left, r.Right - r.Left + 1);

static bool IsAnInvalidId(int n)
    => HasSequenceRepeatedTwice(n);

static bool HasSequenceRepeatedTwice(int n)
{
    var digitCount = (int)(Math.Log10(n) + 1);
    if (digitCount % 2 != 0)
    {
        return false;
    }

    var firstHalf = n / (int)Math.Pow(10, digitCount / 2);
    var secondHalf = n % (int)Math.Pow(10, digitCount / 2);

    return firstHalf == secondHalf;
}

record Range(int Left, int Right);

Et voilà!

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

Advent of Code Day 1: Unlocking the North Pole

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;
        }
    }
}

Et voilà!

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

Here's What's Wrong with Too Much AI

I sit somewhere in the middle of the AI hype cycle. Not a hater nor a fanatic.

I’ve been trying to make sense of AI by documenting my experiences and reacting to others.

Here are 4 issues I’ve found with using AI too much:

#1. It makes us lazier than usual. It’s tempting to go straight to a chat for a quick, unreliable answer. That’s the real danger.

#2. It steals the joy of figuring out problems. No more aha moments or happy dances. Just like asking someone to chew our food to be more productive.

#3. It makes you feel you’re cheating. Copy-pasting without understanding has always been cheating, even in the days of forums and StackOverflow.

#4. It doesn’t let you build mental models to solve problems. It’s like asking someone else to go to the gym for us, then wondering why our muscles are still weak.

The Only Goal That Matters for Every New Digital Writer

My first blog post was a word vomit.

I wrote long sentences and paragraphs. I dumped a bunch of words on a page. That was what I thought writing was. I only knew fiction writers who described every detail of a room in a novel. I didn’t know writing for the Internet was a different game.

That’s the mistake of every new writer.

Today, while reading a Hacker News submission about how cringe-worthy LinkedIn posts can be, I found this comment that captures this problem:

The highest level of cringe you can feel is when you see people you know well in real life post on LinkedIn. The contrast between the way they speak in real life and on LinkedIn is often immense, you don’t feel that level of contrast with random internet strangers.

“I’m pleased to announce…”

“I’m excited to share…”

“Dear LinkedIn network:”

You wouldn’t say those words in real life. So why use them anywhere online? To impress? To sound corporate?

If you’re writing online, the real goal isn’t to sound like a “real” writer. There’s no such thing as real writing, by the way. Your goal is to make readers hear your voice.

Record your voice or write inside WhatsApp. Let your true voice speak. That’s the only goal that matters.