SteveDinn

joined 2 years ago
[โ€“] SteveDinn 2 points 3 months ago

This is the way. Web client to MPV Shim for local playback.

[โ€“] SteveDinn 2 points 4 months ago

C#

Part 2 was pretty much the same as Part 2 except we can't short-circuit when we find the first match. So, implement a cache of each sub-pattern and the number of ways to form it from the towels, and things get much faster.

using System.Collections.Immutable;
using System.Diagnostics;
using Common;

namespace Day19;

static class Program
{
    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var sampleInput = ReceiveInput("sample.txt");
        var programInput = ReceiveInput("input.txt");

        Console.WriteLine($"Part 1 sample: {Part1(sampleInput)}");
        Console.WriteLine($"Part 1 input: {Part1(programInput)}");

        Console.WriteLine($"Part 2 sample: {Part2(sampleInput)}");
        Console.WriteLine($"Part 2 input: {Part2(programInput)}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static object Part1(Input input)
    {
        return input.Patterns
            .Select(p => AnyTowelMatches(p, input.Towels) ? 1 : 0)
            .Sum();
    }

    static object Part2(Input input)
    {
        var matchCache = new Dictionary<string, long>();
        return input.Patterns
            .Select(p => CountTowelMatches(p, input.Towels, matchCache))
            .Sum();
    }

    private static bool AnyTowelMatches(
        string pattern,
        ImmutableArray<string> towels)
    {
        return towels
            .Where(t => t.Length <= pattern.Length)
            .Select(t =>
                !pattern.StartsWith(t) ? false :
                (pattern.Length == t.Length) ? true :
                AnyTowelMatches(pattern.Substring(t.Length), towels))
            .Any(r => r);
    }

    private static long CountTowelMatches(
        string pattern,
        ImmutableArray<string> towels,
        Dictionary<string, long> matchCache)
    {
        if (matchCache.TryGetValue(pattern, out var count)) return count;

        count = towels
            .Where(t => t.Length <= pattern.Length)
            .Select(t => 
                !pattern.StartsWith(t) ? 0 :
                (pattern.Length == t.Length) ? 1 :
                CountTowelMatches(pattern.Substring(t.Length), towels, matchCache))
            .Sum();

        matchCache[pattern] = count;
        return count;
    }

    static Input ReceiveInput(string file)
    {
        using var reader = new StreamReader(file);

        var towels = reader.ReadLine()!.SplitAndTrim(',').ToImmutableArray();
        var patterns = new List<string>();
        reader.ReadLine();
        var line = reader.ReadLine();
        while (line is not null)
        {
            patterns.Add(line);
            line = reader.ReadLine();
        }

        return new Input()
        {
            Towels = towels,
            Patterns = [..patterns],
        };
    }

    public class Input
    {
        public required ImmutableArray<string> Towels { get; init; }
        public required ImmutableArray<string> Patterns { get; init; }
    }
}
[โ€“] SteveDinn 2 points 4 months ago

C#

Part 1 was straight forward Dykstra with a cost of 1 for each move. Part 2 was a binary search from the number of corrupted bytes given to us for Part 1 (where we know a path can be found) to the total number of corrupted bytes.

using System.Collections.Immutable;
using System.Diagnostics;
using Common;

namespace Day18;

static class Program
{
    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var sampleInput = ReceiveInput("sample.txt");
        var sampleBounds = new Point(7,7);
        
        var programInput = ReceiveInput("input.txt");
        var programBounds = new Point(71, 71);

        Console.WriteLine($"Part 1 sample: {Part1(sampleInput, 12, sampleBounds)}");
        Console.WriteLine($"Part 1 input: {Part1(programInput, 1024, programBounds)}");

        Console.WriteLine($"Part 2 sample: {Part2(sampleInput, 12, sampleBounds)}");
        Console.WriteLine($"Part 2 input: {Part2(programInput, 1024, programBounds)}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static int Part1(ImmutableArray<Point> input, int num, Point bounds) => FindBestPath(
        new Point(0, 0),
        new Point(bounds.Row - 1, bounds.Col - 1),
        input.Take(num).ToImmutableHashSet(),
        bounds);

    static object Part2(ImmutableArray<Point> input, int num, Point bounds)
    {
        var start = num;
        var end = input.Length;
        
        while (start != end)
        {
            var check = (start + end) / 2;
            if (Part1(input, check, bounds) < 0) end = check;
            else start = check + 1;
        }

        var lastPoint = input[start - 1];
        return $"{lastPoint.Col},{lastPoint.Row}";
    }

    record struct State(Point Location, int Steps);

    static int FindBestPath(Point start, Point end, ISet<Point> corruptedBytes, Point bounds)
    {
        var seenStates = new Dictionary<Point, int>();

        var queue = new Queue<State>();
        queue.Enqueue(new State(start, 0));
        while (queue.TryDequeue(out var state))
        {
            if (state.Location == end) return state.Steps;
            
            if (seenStates.TryGetValue(state.Location, out var bestSteps))
            {
                if (state.Steps >= bestSteps) continue;
            }
            
            seenStates[state.Location] = state.Steps;
            queue.EnqueueRange(state.Location.GetCardinalMoves()
                .Where(p => p.IsInBounds(bounds) && !corruptedBytes.Contains(p))
                .Select(p => new State(p, state.Steps + 1)));
        }

        return -1;
    }

    static ImmutableArray<Point> ReceiveInput(string file) => File.ReadAllLines(file)
        .Select(l => l.Split(','))
        .Select(p => new Point(int.Parse(p[1]), int.Parse(p[0])))
        .ToImmutableArray();
}
[โ€“] SteveDinn 3 points 4 months ago

C#

This one is mostly thanks to reading @mykl@[email protected]'s code to understand WTF was going on for part 2, and then once I understood the basics, finally got to solving it myself. Instructions were read in as long because I didn't want to deal with int vs. long all the time.

using System.Collections.Immutable;
using System.Diagnostics;
using Common;

namespace Day17;

static class Program
{
    public record State(long A, long B, long C, int InstPtr, ImmutableList<long> Output);

    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var (sampleReg, sampleInst) = ReceiveInput("sample.txt");
        var (inputReg, inputInst) = ReceiveInput("input.txt");

        Console.WriteLine($"Part 1 sample: {Part1(sampleReg, sampleInst)}");
        Console.WriteLine($"Part 1 input: {Part1(inputReg, inputInst)}");

        (sampleReg, sampleInst) = ReceiveInput("sample2.txt");
        Console.WriteLine($"Part 2 sample: {Part2(sampleReg, sampleInst)}");
        Console.WriteLine($"Part 2 input: {Part2(inputReg, inputInst)}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static object Part1(State state, ImmutableArray<long> instructions) =>
        Execute(instructions, state).Output.StringifyAndJoin(",");

    static object Part2(State state, ImmutableArray<long> instructions) =>
        RecursiveSolve(instructions, state with { A = 0 }, []).First();

    static IEnumerable<long> RecursiveSolve(ImmutableArray<long> instructions, State state, ImmutableList<long> soFar) =>
        (soFar.Count == instructions.Length) ? [state.A] :
        Enumerable.Range(0, 8)
            .Select(a => state with { A = (state.A << 3) + a })
            .Where(newState => newState.A != state.A)
            .Select(newState => new { newState, Execute(instructions, newState).Output, })
            .Where(states => states.Output.SequenceEqual(instructions.TakeLast(states.Output.Count)))
            .SelectMany(states => RecursiveSolve(instructions, states.newState, states.Output));

    static State Execute(ImmutableArray<long> instructions, State state)
    {
        while (state.InstPtr < instructions.Length)
        {
            var opcode = instructions[state.InstPtr];
            var operand = instructions[state.InstPtr + 1];
            state = Operations[opcode](state, operand);
        }

        return state;
    }

    static long ComboOperand(long operand, State state) => operand switch
    {
        >= 0 and <= 3 => operand,
        4 => state.A,
        5 => state.B,
        6 => state.C,
        _ => throw new Exception("Invalid operand."),
    };

    static long Adv(long op, State state) => state.A / (long)Math.Pow(2, ComboOperand(op, state));

    static readonly Func<State, long, State>[] Operations =
    [
        (s, op) => s with { InstPtr = s.InstPtr + 2, A = Adv(op, s) },
        (s, op) => s with { InstPtr = s.InstPtr + 2, B = s.B ^ op },
        (s, op) => s with { InstPtr = s.InstPtr + 2, B = ComboOperand(op, s) % 8 },
        (s, op) => s with { InstPtr = (s.A == 0) ? (s.InstPtr + 2) : (op <= int.MaxValue) ? (int)op : throw new ArithmeticException("Integer overflow!") },
        (s, _) => s with { InstPtr = s.InstPtr + 2, B = s.B ^ s.C },
        (s, op) => s with { InstPtr = s.InstPtr + 2, Output = s.Output.Add(ComboOperand(op, s) % 8) },
        (s, op) => s with { InstPtr = s.InstPtr + 2, B = Adv(op, s) },
        (s, op) => s with { InstPtr = s.InstPtr + 2, C = Adv(op, s) },
    ];


    static (State, ImmutableArray<long> instructions) ReceiveInput(string file)
    {
        var input = File.ReadAllLines(file);

        return
        (
            new State(
                long.Parse(input[0].Substring("Register A: ".Length)),
                long.Parse(input[1].Substring("Register B: ".Length)),
                long.Parse(input[2].Substring("Register C: ".Length)),
                0,
                []),
            input[4].Substring("Program: ".Length)
                .Split(",")
                .Select(long.Parse)
                .ToImmutableArray()
        );
    }
}
[โ€“] SteveDinn 3 points 4 months ago (1 children)

I'm confused reading the buildQuine() method. It reads to me that when you call it from the top level with top = true, A0 will be set to 0, and then when we get to the 0 to 8 loop, the 'A' register will be 0 * 8 + 0 for the first iteration, and then recurse with top = false, but with a0 still ending up 0, causing infinite recursion.

Am I missing something?

I got it to work with a check that avoids the recursion if the last state's A register value is the same as the new state's value.

[โ€“] SteveDinn 3 points 4 months ago

This is giving me Punch Out vibes. Little Mac vs King Hippo.

[โ€“] SteveDinn 5 points 4 months ago

I guess I'll never know what the kids are saying ever again because there's no way I'm installing either of those apps.

[โ€“] SteveDinn 2 points 4 months ago* (last edited 4 months ago)

C#

Ended up modifying part 1 to do part 2 and return both answers at once.

using System.Collections.Immutable;
using System.Diagnostics;
using Common;

namespace Day16;

static class Program
{
    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var smallInput = Input.Parse("smallsample.txt");
        var sampleInput = Input.Parse("sample.txt");
        var programInput = Input.Parse("input.txt");

        Console.WriteLine($"Part 1 small: {Solve(smallInput)}");
        Console.WriteLine($"Part 1 sample: {Solve(sampleInput)}");
        Console.WriteLine($"Part 1 input: {Solve(programInput)}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static (int part1, int part2) Solve(Input i)
    {
        State? endState = null;
        Dictionary<(Point, int), int> lowestScores = new();

        var queue = new Queue<State>();
        queue.Enqueue(new State(i.Start, 1, 0, ImmutableHashSet<Point>.Empty));
        while (queue.TryDequeue(out var state))
        {
            if (ElementAt(i.Map, state.Location) is '#')
            {
                continue;
            }

            if (lowestScores.TryGetValue((state.Location, state.DirectionIndex), out var lowestScoreSoFar))
            {
                if (state.Score > lowestScoreSoFar) continue;
            }

            lowestScores[(state.Location, state.DirectionIndex)] = state.Score;

            var nextStatePoints = state.Points.Add(state.Location);

            if (state.Location == i.End)
            {
                if ((endState is null) || (state.Score < endState.Score))
                    endState = state with { Points = nextStatePoints };
                else if (state.Score == endState.Score)
                    endState = state with { Points = nextStatePoints.Union(endState.Points) };
                continue;
            }

            // Walk forward
            queue.Enqueue(state with
            {
                Location = state.Location.Move(CardinalDirections[state.DirectionIndex]),
                Score = state.Score + 1,
                Points = nextStatePoints,
            });

            // Turn clockwise
            queue.Enqueue(state with
            {
                DirectionIndex = (state.DirectionIndex + 1) % CardinalDirections.Length,
                Score = state.Score + 1000,
                Points = nextStatePoints,
            });

            // Turn counter clockwise
            queue.Enqueue(state with
            {
                DirectionIndex = (state.DirectionIndex + CardinalDirections.Length - 1) % CardinalDirections.Length,
                Score = state.Score + 1000,
                Points = nextStatePoints, 
            });
        }

        if (endState is null) throw new Exception("No end state found!");
        return (endState.Score, endState.Points.Count);
    }

    public static void DumpMap(Input i, ISet<Point>? points, Point current)
    {
        for (int row = 0; row < i.Bounds.Row; row++)
        {
            for (int col = 0; col < i.Bounds.Col; col++)
            {
                var p = new Point(row, col);
                Console.Write(
                    (p == current) ? 'X' :
                    (points?.Contains(p) ?? false) ? 'O' :
                    ElementAt(i.Map, p));
            }

            Console.WriteLine();
        }

        Console.WriteLine();
    }

    public static char ElementAt(string[] map, Point location) => map[location.Row][location.Col];

    public record State(Point Location, int DirectionIndex, int Score, ImmutableHashSet<Point> Points);

    public static readonly Direction[] CardinalDirections =
        [Direction.Up, Direction.Right, Direction.Down, Direction.Left];
}

public class Input
{
    public string[] Map { get; init; } = [];
    public Point Start { get; init; } = new(-1, -1);
    public Point End { get; init; } = new(-1, -1);
    public Point Bounds => new(this.Map.Length, this.Map[0].Length);

    public static Input Parse(string file)
    {
        var map = File.ReadAllLines(file);
        Point start = new(-1, -1), end = new(-1, -1);
        foreach (var p in map
            .SelectMany((line, i) => new []
            {
                 new Point(i, line.IndexOf('S')),
                 new Point(i, line.IndexOf('E')),
            })
            .Where(p => p.Col >= 0)
            .Take(2))
        {
            if (map[p.Row][p.Col] is 'S') start = p;
            else end = p;
        }

        return new Input()
        {
            Map = map,
            Start = start,
            End = end,
        };
    }
}
[โ€“] SteveDinn 1 points 4 months ago

It's a simple record type I use for (x,y) coordinate problems:

record struct Point(int X, int Y);

It's defined in a separate project containing things I use in multiple problems.

Maybe I could have done it that way, but this was the first thing I thought of, and it worked :)

[โ€“] SteveDinn 2 points 4 months ago

C#

Thank goodness for high school algebra!

using System.Diagnostics;
using Common;

namespace Day13;

static class Program
{
    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var sampleInput = Input.ParseInput("sample.txt");
        var programInput = Input.ParseInput("input.txt");

        Console.WriteLine($"Part 1 sample: {Part1(sampleInput)}");
        Console.WriteLine($"Part 1 input: {Part1(programInput)}");

        Console.WriteLine($"Part 2 sample: {Part2(sampleInput)}");
        Console.WriteLine($"Part 2 input: {Part2(programInput)}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static object Part1(Input i) => i.Machines
        .Select(m => CalculateCoins(m, 100))
        .Where(c => c > 0)
        .Sum();

    static object Part2(Input i) => i.Machines
        .Select(m => m with { Prize = new XYValues(
            m.Prize.X + 10000000000000,
            m.Prize.Y + 10000000000000) })
        .Select(m => CalculateCoins(m, long.MaxValue))
        .Where(c => c > 0)
        .Sum();

    static long CalculateCoins(Machine m, long limit)
    {
        var bBottom = (m.A.X * m.B.Y) - (m.A.Y * m.B.X);
        if (bBottom == 0) return -1;

        var bTop = (m.Prize.Y * m.A.X) - (m.Prize.X * m.A.Y);
        var b = bTop / bBottom;
        if ((b <= 0) || (b > limit)) return -1;
        
        var a = (m.Prize.X - (b * m.B.X)) / m.A.X;
        if ((a <= 0) || (a > limit)) return -1;

        var calcPrizeX = (a * m.A.X) + (b * m.B.X);
        var calcPrizeY = (a * m.A.Y) + (b * m.B.Y);
        if ((m.Prize.X != calcPrizeX) || (m.Prize.Y != calcPrizeY)) return -1;

        return (3 * a) + b;
    }
}

public record struct Machine(XYValues A, XYValues B, XYValues Prize);
public record struct XYValues(long X, long Y);

public class Input
{
    private Input()
    {
    }

    public List<Machine> Machines { get; init; }

    public static Input ParseInput(string file)
    {
        var machines = new List<Machine>();

        var lines = File.ReadAllLines(file);
        for(int l=0; l<lines.Length; l+=4)
        {
            machines.Add(new Machine(
                ParseXYValues(lines[l + 0]),
                ParseXYValues(lines[l + 1]),
                ParseXYValues(lines[l + 2])));
        }

        return new Input()
        {
            Machines = machines,
        };
    }

    private static readonly char[] XYDelimiters = ['X', 'Y', '=', '+', ',', ' '];

    private static XYValues ParseXYValues(string s)
    {
        var parts = s
            .Substring(s.IndexOf(':') + 1)
            .SplitAndTrim(XYDelimiters)
            .Select(long.Parse)
            .ToArray();
        
        return new XYValues(parts[0], parts[1]);
    }
}
[โ€“] SteveDinn 2 points 4 months ago (2 children)

C#

There is probably a more efficient way of finding the sides, but this way was what came to me.

using System.Diagnostics;
using Common;

namespace Day12;

static class Program
{
    static void Main()
    {
        var start = Stopwatch.GetTimestamp();

        var sampleInput = Input.ParseInput("sample.txt");
        var programInput = Input.ParseInput("input.txt");

        var (samplePart1, samplePart2) = Solve(sampleInput);
        Console.WriteLine($"Part 1 sample: {samplePart1}");
        Console.WriteLine($"Part 1 input: {samplePart2}");

        var (inputPart1, inputPart2) = Solve(programInput);
        Console.WriteLine($"Part 2 sample: {inputPart1}");
        Console.WriteLine($"Part 2 input: {inputPart2}");

        Console.WriteLine($"That took about {Stopwatch.GetElapsedTime(start)}");
    }

    static (int part1, int part2) Solve(Input i)
    {
        var retail = 0;
        var bulk = 0;
        var allPlotPoints = new Dictionary<char, HashSet<Point>>();
        foreach (var p in Grid.EnumerateAllPoints(i.Bounds))
        {
            var plant = i.ElementAt(p);

            if (!allPlotPoints.TryGetValue(plant, out var previousPlotPoints))
            {
                previousPlotPoints = new();
                allPlotPoints[plant] = previousPlotPoints;
            }
            else if (previousPlotPoints.Contains(p)) continue;

            var plotPoints = new HashSet<Point>();
            var perimeter = SearchPlot(i, plotPoints, plant, p);
            var area = plotPoints.Count;
            var sides = CountSides(plotPoints);
            retail += area * perimeter;
            bulk += area * sides;

            previousPlotPoints.AddRange(plotPoints);
        }

        return (retail, bulk);
    }

    static int CountSides(HashSet<Point> plot)
    {
        var sides = 0;

        // Track the points we've visited searching for sides
        HashSet<Point> visitedDownRight = new(),
            visitedDownLeft = new(),
            visitedRightDown = new(),
            visitedRightUp = new();

        // Sort the points in the plot from upper-left to lower-right, so we can
        // go through them in reading order
        foreach (var p in plot.OrderBy(p => (p.Row * 10000) + p.Col))
        {
            // Move right while looking up
            sides += GetSideLength(p, plot, visitedRightUp, Direction.Right, Direction.Up) > 0 ? 1 : 0;
            
            // Move right while looking down
            sides += GetSideLength(p, plot, visitedRightDown, Direction.Right, Direction.Down) > 0 ? 1 : 0;
            
            // Move down while looking right
            sides += GetSideLength(p, plot, visitedDownRight, Direction.Down, Direction.Right) > 0 ? 1 : 0;
            
            // Move down while looking left
            sides += GetSideLength(p, plot, visitedDownLeft, Direction.Down, Direction.Left) > 0 ? 1 : 0;
        }

        return sides;
    }

    static int GetSideLength(Point p, HashSet<Point> plotPoints, HashSet<Point> visited, Direction move, Direction look)
    {
        if (!plotPoints.Contains(p)) return 0;
        if (!visited.Add(p)) return 0;
        if (plotPoints.Contains(p.Move(look))) return 0;
        return 1 + GetSideLength(p.Move(move), plotPoints, visited, move, look);
    }

    static int SearchPlot(Input i, HashSet<Point> plotPoints, char plant, Point p)
    {
        if (!plotPoints.Add(p)) return 0;
        return p
            .GetCardinalMoves()
            .Select(move =>
            {
                if (!i.IsInBounds(move) || (i.ElementAt(move) != plant)) return 1;
                return SearchPlot(i, plotPoints, plant, move);
            })
            .Sum();
    }
}

public class Input
{
    public required string[] Map { get; init; }
    
    public Point Bounds => new Point(this.Map.Length, this.Map[0].Length);
    public char ElementAt(Point p) => this.Map[p.Row][p.Col];
    public bool IsInBounds(Point p) => p.IsInBounds(this.Map.Length, this.Map[0].Length);
    
    public static Input ParseInput(string file) => new Input()
    {
        Map = File.ReadAllLines(file),
    };
}
[โ€“] SteveDinn 3 points 4 months ago (1 children)

I had a very similar take on this problem, but I was not caching the results of a blink for a single stone, like youre doing with subtree_pointers. I tried adding that to my solution, but it didn't make an appreciable difference. I think that caching the lengths is really the only thing that matters.

C#

    static object Solve(Input i, int numBlinks)
    {
        // This is a cache of the tuples of (stoneValue, blinks) to
        // the calculated count of their child stones.
        var lengthCache = new Dictionary<(long, int), long>();
        return i.InitialStones
            .Sum(stone => CalculateUltimateLength(stone, numBlinks, lengthCache));
    }

    static long CalculateUltimateLength(
        long stone,
        int numBlinks,
        IDictionary<(long, int), long> lengthCache)
    {
        if (numBlinks == 0) return 1;
        
        if (lengthCache.TryGetValue((stone, numBlinks), out var length)) return length;

        length = Blink(stone)
            .Sum(next => CalculateUltimateLength(next, numBlinks - 1, lengthCache));
        lengthCache[(stone, numBlinks)] = length;
        return length;
    }

    static long[] Blink(long stone)
    {
        if (stone == 0) return [1];

        var stoneText = stone.ToString();
        if (stoneText.Length % 2 == 0)
        {
            var halfLength = stoneText.Length / 2;
            return
            [
                long.Parse(stoneText.Substring(0, halfLength)),
                long.Parse(stoneText.Substring(halfLength)),
            ];
        }

        return [stone * 2024];
    }
view more: โ€น prev next โ€บ