Advent of Code Day 8: Connecting Junction Boxes

On Day 8 of Advent of Code, we’re helping elves with some decorations, so we’re connecting junction boxes.

Unlike the first 7 days, I cheated. Yes! This puzzle wasn’t fun anymre after spending a couple of hours to make the example work. In a moment of frustration, I went to Reddit for some help and I reworked my original solution.

First, I tried to create circuits as I visited pair of junction boxes. But, after getting some help from Reddit, every junction box is a 1-box circuit to then merge all the pairs.

Finding distances

To find the distance between all possibles pair of junction boxes, I borrowed the Pairs() method from Day 3.

To calculate the distance, I use the squared of the distance as a small optimization.

var distances = allBoxes.Pairs()
    .Select(p => (p.First, p.Second, Distance: Distance(p.First, p.Second)))
    .OrderBy(p => p.Distance);

static int Distance(JunctionBox p1, JunctionBox p2)
{
    var d2 = (p1.X - p2.X) * (p1.X - p2.X)
                + (p1.Y - p2.Y) * (p1.Y - p2.Y)
                + (p1.Z - p2.Z) * (p1.Z - p2.Z);
    return d2;
}

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

Connecting boxes

A circuit is a wrapper around a HashSet with a method to merge two circuits.

record Circuit(HashSet<JunctionBox> Boxes)
{
    public Circuit(JunctionBox box)
        : this(new HashSet<JunctionBox>([box]))
    {
    }

    public bool Contains(JunctionBox box)
        => Boxes.Contains(box);

    public void Connect(Circuit circuit)
    {
        foreach (var box in circuit.Boxes)
        {
            Boxes.Add(box);
        }
    }
}

With the list of junction boxes by distance and a circuit, here’s the full solution,

var allBoxes = new[]
{
    new JunctionBox(162,817,812),
    new JunctionBox(57,618,57),
    new JunctionBox(906,360,560),
    new JunctionBox(592,479,940),
    new JunctionBox(352,342,300),
    new JunctionBox(466,668,158),
    new JunctionBox(542,29,236),
    new JunctionBox(431,825,988),
    new JunctionBox(739,650,466),
    new JunctionBox(52,470,668),
    new JunctionBox(216,146,977),
    new JunctionBox(819,987,18),
    new JunctionBox(117,168,530),
    new JunctionBox(805,96,715),
    new JunctionBox(346,949,466),
    new JunctionBox(970,615,88),
    new JunctionBox(941,993,340),
    new JunctionBox(862,61,35),
    new JunctionBox(984,92,344),
    new JunctionBox(425,690,689),
};

var circuits = allBoxes.Select(box => new Circuit(box)).ToList();

var distances = allBoxes.Pairs()
    .Select(p => (p.First, p.Second, Distance: Distance(p.First, p.Second)))
    .OrderBy(p => p.Distance);
foreach (var pair in distances.Take(1_000))
{
    var hasFirstBox = circuits.First(c => c.Contains(pair.First));
    var hasSecondBox = circuits.First(c => c.Contains(pair.Second));
    if (hasFirstBox != hasSecondBox)
    {
        hasFirstBox.Connect(hasSecondBox);
        circuits.Remove(hasSecondBox);
    }
}

foreach (var circuit in circuits.OrderBy(c => c.Boxes.Count).Take(3))
{
    Console.WriteLine(circuit.Boxes.Count);
}
Console.ReadKey();

static int Distance(JunctionBox p1, JunctionBox p2)
{
    var d2 = (p1.X - p2.X) * (p1.X - p2.X)
                + (p1.Y - p2.Y) * (p1.Y - p2.Y)
                + (p1.Z - p2.Z) * (p1.Z - p2.Z);
    return d2;
}

record Circuit(HashSet<JunctionBox> Boxes)
{
    public Circuit(JunctionBox box)
        : this(new HashSet<JunctionBox>([box]))
    {
    }

    public bool Contains(JunctionBox box)
        => Boxes.Contains(box);

    public void Connect(Circuit circuit)
    {
        foreach (var box in circuit.Boxes)
        {
            Boxes.Add(box);
        }
    }
}
record JunctionBox(int X, int Y, int Z)
{
    public override string ToString() => $"{X},{Y},{Z}";
}

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 7: Splitting Tachyon Beams

On Day 7 of Advent of Code, we’re studying tachyon beams while we’re stuck in a teleporter.

I imagine this puzzle as a game where each iteration moves the beam until it hits the end of the manifold.

Moving a beam

A Beam is a list of positions in the manifold at a given iteration.

record Position(int X, int Y);
record Beam(IEnumerable<Position> Locations);

Here’s the method to move a beam,

static Beam Move(string[][] manifold, Beam beam)
{
    var newLocations = new HashSet<Position>();

    foreach (var current in beam.Locations)
    {
        var downward = manifold[current.X + 1][current.Y];
        if (downward == ".")
        {
            newLocations.Add(new Position(current.X + 1, current.Y));
        }
        else if (downward == "^")
        {
            newLocations.Add(new Position(current.X + 1, current.Y - 1));
            newLocations.Add(new Position(current.X + 1, current.Y + 1));
        }
    }

    return new Beam(newLocations);
}

After moving a beam, I need the entry position of a beam,

static Beam Start(string[][] manifold)
{
    for (int i = 0; i < manifold[0].Length; i++)
    {
        if (manifold[0][i] == "S")
        {
            return new Beam([new Position(0, i)]);
        }
    }

    return new Beam([]);
}

With those two methods, a beam enters and moves downward,

var start = Start(manifold);
var newPosition = Move(manifold, start);
newPosition = Move(manifold, newPosition);

All that’s left is to count splits and move the beam to the end.

Counting splits

Next, I add the number of splits to Beam and create HasReacheTheEnd().

Here’s my full solution,

var manifold = new string[][]
{
    [ ".", ".", ".", ".", ".", ".", ".", "S", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", "^", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", "^", ".", "^", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", "^", ".", "^", ".", "^", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", "^", ".", "^", ".", ".", ".", "^", ".", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", ".", "^", ".", "^", ".", ".", ".", "^", ".", "^", ".", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", ".", "^", ".", ".", ".", "^", ".", ".", ".", ".", ".", "^", ".", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ],
    [ ".", "^", ".", "^", ".", "^", ".", "^", ".", "^", ".", ".", ".", "^", "." ],
    [ ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "." ]
};

var beam = Start(manifold);
while (!HasReachedTheEnd(manifold, beam))
{
    beam = Move(manifold, beam);
}

Console.WriteLine(beam.SplitCount);
Console.ReadLine();

static Beam Start(string[][] manifold)
{
    for (int i = 0; i < manifold[0].Length; i++)
    {
        if (manifold[0][i] == "S")
        {
            return new Beam(0, [new Position(0, i)]);
        }
    }

    return new Beam(0, []);
}

static bool HasReachedTheEnd(string[][] manifold, Beam beam)
{
    var anyBeam = beam.Locations.First();
    return anyBeam.X >= manifold.Length - 1;
}

static Beam Move(string[][] manifold, Beam beam)
{
    var splits = 0;
    var newLocations = new HashSet<Position>();

    foreach (var current in beam.Locations)
    {
        var downward = manifold[current.X + 1][current.Y];
        if (downward == ".")
        {
            newLocations.Add(new Position(current.X + 1, current.Y));
        }
        else if (downward == "^")
        {
            splits++;

            newLocations.Add(new Position(current.X + 1, current.Y - 1));
            newLocations.Add(new Position(current.X + 1, current.Y + 1));
        }
    }

    return new Beam(beam.SplitCount + splits, newLocations);
}

record Position(int X, int Y);
record Beam(int SplitCount, IEnumerable<Position> Locations);

I thought a “tachyon” was a made-up word until I Googled it. TIL what a tachyon is. Win-win.

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

Friday Links: Becoming a senior dev, coding, and phones

Hey, it’s been a busy week. I’ve been preparing my last book for publish on Amazon, so I didn’t read that much.

But I still want to share some interesting reads. Here are my top 3 most read posts from the quarter plus a funny weekend project.

#1. I always joke that I don’t know when “Senior” made it into my job title. But I know it was thanks to good mentors. Here are 5 lessons from one of my mentors (4min). I’ve been following lesson #1 since I met him…Well, not in the past week.

#2. What’s the first thing that comes to mind when you hear “get good at coding”? Younger me only thought about learning programming languages. But here are 7 unconventional ways to improve your coding skills (3min).

#3. I’d do a lot differently if I were a junior again. Here’s one lesson I wish I had known 10 years ago (3min). That would have made me a well-rounded coder.

#4. What if we could slam a phone after a boring Zoom call? That was the side project of someone who turned a rotary phone into a meeting handset (8min).


Speaking of coding, are you following the Advent of Code? I have. I missed a couple of days, but I’m catching up this weekend. Here’s my last solution: Advent of Code Day 6: Doing Cephalopods’ Math (3min).


(Bzzz…Radio voice) This email was brought to you by… my new book, Street-Smart Coding: 30 lessons to help you code like a pro. From Googling to clear communication, it covers the lessons you don’t learn in tutorials. It’s now out on Kindle and paperback on Amazon.

See you next time,

Cesar

Advent of Code Day 6: Doing Cephalopods' Math

On Day 6 of Advent of Code, we’re helping some cephalopods with their kids’ math homework…while they open a trash compactor.

First, I define a problem and function to solve it. A problem is an operation and some numbers.

static int Perform(Problem p)
    => p.Operation switch
    {
        Operation.Add => p.Numbers.Sum(),
        Operation.Multiply => p.Numbers.Aggregate((a, b) => a*b),
        _ => throw new NotImplementedException()
    };

abstract record Operation
{
    public record Add : Operation;
    public record Multiply : Operation;
}
record Problem(Operation Operation, IEnumerable<int> Numbers);

Cephalopods’ math is weird with operations and operands organized vertically, not horizontally. Parsing is the trick part: the input is a 2D array where each column is a problem. Numbers above and operator at the bottom.

I loop through the array of arrays column-wise. The last element becomes the operation, and the rest become the numbers,

static IEnumerable<Problem> Parse(string[][] problems)
{
    var parsedProblems = new List<Problem>();

    for (int j = 0; j < problems[0].Length; j++)
    {
        var numbers = Enumerable.Range(0, problems.Length - 1)
                                .Select(i => int.Parse(problems[i][j]))
                                .ToList();
        Operation op = problems[^1][j] == "+"
                            ? new Operation.Add()
                            : new Operation.Multiply();
        parsedProblems.Add(new Problem(op, numbers));
    }
    return parsedProblems;
}

With the parsing in place, Day 6 is almost done. Here’s my final solution,

var problems = new[]
{
    new[]{ "123", "328", "51", "64" },
    new[]{ "45", "64", "387", "23" },
    new[]{ "6", "98", "215", "314" },
    new[]{ "*", "+", "*", "+" }
};

var total = Parse(problems).Sum(Perform);
Console.WriteLine(total);
Console.ReadKey();

static IEnumerable<Problem> Parse(string[][] problems)
{
    var parsedProblems = new List<Problem>();

    for (int j = 0; j < problems[0].Length; j++)
    {
        var numbers = Enumerable.Range(0, problems.Length - 1)
                                .Select(i => int.Parse(problems[i][j]))
                                .ToList();
        Operation op = problems[^1][j] == "+"
                            ? new Operation.Add()
                            : new Operation.Multiply();
        parsedProblems.Add(new Problem(op, numbers));
    }
    return parsedProblems;
}

static int Perform(Problem p)
    => p.Operation switch
    {
        Operation.Add => p.Numbers.Sum(),
        Operation.Multiply => p.Numbers.Aggregate((a, b) => a*b),
        _ => throw new NotImplementedException()
    };

abstract record Operation
{
    public record Add : Operation;
    public record Multiply : Operation;
}
record Problem(Operation Operation, IEnumerable<int> Numbers);

Two one-off bugs got me this time, but I finally cracked it. The parsing part isn’t that functional in style, but “it works on my machine.” 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 5: Counting Fresh Ingredients

On Day 5 of Advent of Code, we’re helping the elves to find which ingredients are fresh or spoiled.

To find if an ingredient is fresh, I create a FreshIngredientRange with a method IsFresh(),

record FreshIngredientRange(int IngredientId1, int IngredientId2)
{
    public bool IsFresh(int productId)
        => productId >= IngredientId1 && productId <= IngredientId2;
}

Then, to count all the fresh ingredients, I write a method that receives all product ranges and all candidate products. A single LINQ query will do the counting,

static int CountFreshIngredients(
    IEnumerable<FreshIngredientRange> ranges,
	IEnumerable<int> productIds)
        => productIds.Count(productId => ranges.Any(r => r.IsFresh(productId)));

Here’s my final solution,

var ranges = new FreshIngredientRange[]
{
    new FreshIngredientRange(3, 5),
    new FreshIngredientRange(10, 14),
    new FreshIngredientRange(16, 20),
    new FreshIngredientRange(12, 18)
};

var productIds = new int[] { 1, 5, 8, 11, 17, 32 };

var count = CountFreshIngredients(ranges, productIds);
Console.WriteLine(count);
Console.ReadKey();

static int CountFreshIngredients(IEnumerable<FreshIngredientRange> ranges, IEnumerable<int> productIds)
    => productIds.Count(productId => ranges.Any(r => r.IsFresh(productId)));

record FreshIngredientRange(int IngredientId1, int IngredientId2)
{
    public bool IsFresh(int productId)
        => productId >= IngredientId1 && productId <= IngredientId2;
}

That’s an easy one with LINQ. Just one single line. 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