<file_sep>﻿
namespace Aoc.Day8
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading.Tasks;

    using Aoc.Misc;

    using static Aoc.Misc.Tools;

    public static class Part1
    {
        private record Instruction(string Action, int Value, bool Done = false);
        private record ExecutionResult(int Jumps, int Accumulator);

        private static (string, string) ParseLine(string line) => (line[0..3], line[3..^0]);
        private static Instruction ToDomain((string action, string value) parsedData) =>
            new(parsedData.action, int.Parse(parsedData.value));

        private static List<Instruction> MarkInstructionDone(List<Instruction> instructions, int index) =>
            instructions.Select((inst, i) => index == i ? inst with { Done = true } : inst).ToList();

        private static ExecutionResult Execute(Instruction instruction, int currentResult) =>
            instruction switch
            {
                ("nop", _, _) => new(1, currentResult),
                ("acc", _, _) => new(1, currentResult + instruction.Value),
                ("jmp", _, _) => new(instruction.Value, currentResult),
                _ => throw new InvalidOperationException($"Unhandled action {instruction.Action}")
            };

        private static int Next(int index, List<Instruction> instructions, int result) =>
            instructions[index] switch
            {
                (_, _, true) => result,
                _ => Execute(instructions[index], result)
                       .Apply(x => Next(index + x.Jumps, MarkInstructionDone(instructions, index), x.Accumulator))
            };

        private static int Start(IEnumerable<Instruction> instructions) => Next(0, instructions.ToList(), 0);

        public static int Solve(string path) =>
            path
                .Apply(ReadLines)
                .Select(ParseLine)
                .Select(ToDomain)
                .Apply(Start);
    }
}
<file_sep>﻿
namespace Aoc.Day7
{
    using System;

    using Aoc.Misc;

    class Program
    {
        static void Main(string[] args) =>
            "input.txt"
                .Apply(Part1.Solve)
                .Apply(Console.WriteLine)
                .Apply(_ => "input.txt")
                .Apply(Part2.Solve)
                .Apply(Console.WriteLine);
    }
}
<file_sep>﻿
namespace Aoc.Day7
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    using static Aoc.Misc.Tools;

    public static class Data
    {
        static List<string> Parse(string rule) =>
            rule
                .Split(Arr("bags contain", "bags,", "bag,", "bags.", "bag."), StringSplitOptions.TrimEntries)
                [0..^1]
                .Select(x => x.Trim())
                .ToList();

        static int GetNbRules(List<string> parsedRule) => parsedRule.Count - 1;

        static BagRule GetBagRule(string parsedRule) =>
            parsedRule == "no other" 
                ? null
                : new BagRule(
                    Color: parsedRule[1..^0].Trim(),
                    Count: int.Parse(parsedRule[0].ToString()));

        static Rule ToRule(List<string> parsedRule) =>
            parsedRule.ToList()
                .Apply(GetNbRules)
                .Apply(nbRules => new Rule(
                    Color: parsedRule[0],
                    Rules: Enumerable.Range(1, nbRules)
                            .Select(x => GetBagRule(parsedRule[x]))
                            .Where(NotNull)
                            .ToList()
                    )
                );

        public static List<Rule> Get(string path) =>
            path
                .Apply(ReadLines)
                .Select(Parse)
                .Select(ToRule)
                .ToList();
    }
}
<file_sep>﻿
namespace Aoc.Day7
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    using static Aoc.Misc.Tools;

    public static class Part1
    {
        static bool CanHoldThisBagRec(string color, List<BagRule> bagRules, List<Rule> rules) =>
            bagRules.IsNullOrEmpty()
                ? false
                : bagRules.Any(r =>
                    r.Color == color || CanHoldThisBagRec(color, rules.FirstOrDefault(x => x.Color == r.Color)?.Rules, rules));

        static Func<Rule, bool> CanHoldThisBag(string color, List<Rule> rules) =>
            rule => CanHoldThisBagRec(color, rule.Rules, rules);

        public static int Solve(string path) =>
            Data.Get(path)
                .Apply(rules => rules.Select(CanHoldThisBag("shiny gold", rules)))
                .Where(IsTrue)
                .Count();
    }
}
<file_sep>﻿
namespace Aoc.Day10
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    using LanguageExt;

    using static LanguageExt.Prelude;


    public static class Part1
    {
        record AdaptersCount(int Ones, int Twos, int Threes);

        static Func<int, Func<IEnumerable<IGrouping<int, int>>, int>> Count =
            number =>
            diffs =>
            diffs.FirstOrDefault(x => x.Key == number).Apply(Optional)
                .Match(
                    x => x.Count() + 1,
                    () => 0);

        static AdaptersCount CountAdapters(IEnumerable<IGrouping<int, int>> diffs) =>
            new(
                Ones:   diffs.Apply(Count(1)),
                Twos:   diffs.Apply(Count(2)),
                Threes: diffs.Apply(Count(3)));

        static int GetResult(AdaptersCount adaptersCount) => adaptersCount.Ones * adaptersCount.Threes;

        public static int Solve(IEnumerable<int> adapters) =>
            adapters
                .PairWise()
                .Select(x => x.Item2 - x.Item1)
                .GroupBy(identity)
                .Apply(CountAdapters)
                .Apply(GetResult);
    }
}
<file_sep>﻿
namespace Aoc.Misc
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Text.Json;

    using LanguageExt;

    public static class Extensions
    {
        public static (B result, TimeSpan elapsedTime) Benchmark<A, B>(this A source, Func<A, B> f)
        {
            var sw = Stopwatch.StartNew();
            var result= f(source);
            sw.Stop();
            return (result, sw.Elapsed);
        }
        public static bool IsContainedIn<T>(this T candidate, IEnumerable<T> potentialContainer) => potentialContainer.Contains(candidate);
        public static bool IsNullOrEmpty<T>(this IEnumerable<T> source) => source == null || source.Any() == false;
        public static A Printn<A>(this A source) { Console.WriteLine(source); return source; }
        public static A Printfn<A, B>(this A source, Func<A, B> f) { Console.WriteLine(f(source)); return source; }
        public static A LogSerialize<A>(this A source) { Console.WriteLine(JsonSerializer.Serialize(source)); return source; }
        public static A LogSerialize<A, B>(this A source, Func<A, B> f) { Console.WriteLine(JsonSerializer.Serialize(f(source))); return source; }
        public static IEnumerable<T> Order<T>(this IEnumerable<T> source) => source.OrderBy(x=> x);
        public static IEnumerable<(T, T)> PairWise<T>(this IEnumerable<T> source) => source.Zip(source.Skip(1), (a, b) => (a, b));
        public static IEnumerable<IEnumerable<T>> SplitBy<T>(this IEnumerable<T> source, Func<T, bool> f)
        {
            var result = new List<T>();

            foreach (var item in source)
            {
                if (f(item))
                {
                    yield return result;

                    result = new List<T>();
                }
                else
                {
                    result.Add(item);
                }
            }

            yield return result;
        }
    }
}
<file_sep>﻿
namespace Aoc.Day7
{
    using System.Collections.Generic;

    public record BagRule(string Color, int Count);
    public record Rule(string Color, List<BagRule> Rules);
}
<file_sep>﻿
namespace Aoc.Misc
{
    using System.Collections;
    using System.Collections.Generic;

    public static class Tools
    {
        public static IEnumerable<string> ReadLines(string path) => System.IO.File.ReadLines(path);

        // Functions

        // Constructors
        public static T[] Arr<T>(params T[] items) => new List<T>(items).ToArray();
        public static List<T> Lst<T>(params T[] items) => new(items);
        public static KeyValuePair<A, B> Kvp<A, B>(A key, B value) => new(key, value);

        // Validators
        public static bool NotNull(object o) => o != null;
        public static bool IsTrue(bool b) => b;
    }
}
<file_sep>﻿
namespace Aoc.Day10
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using LanguageExt;
    using Aoc.Misc;
    using System.Net.Http.Headers;

    public static class Part2
    {
        static readonly Dictionary<string, long> cache = new();

        static long GetArrangements(List<int> adapters)
        {
            if (adapters.Count == 1) return 1;

            long arrangements = 0;
            int index = 1;
            int current = adapters[0];

            while (adapters.Count > index && adapters[index] - current < 4)
            {
                var span = adapters.Skip(index).Take(adapters.Count - 1).ToList();
                var spanStr = string.Join(',', span);

                if (cache.ContainsKey(spanStr))
                {
                    arrangements += cache[spanStr];
                }
                else
                {
                    long spanArrangements = GetArrangements(span);
                    cache[spanStr] = spanArrangements;
                    arrangements += spanArrangements;
                }

                index++;
            }

            return arrangements;
        }

        public static long Solve(IEnumerable<int> adapters) =>
            adapters
                .Prepend(0).Append(adapters.Last() + 3)
                .ToList()
                .Benchmark(GetArrangements)
                .Apply(x => x.elapsedTime.Printfn(x => x.TotalMilliseconds + " ms").Apply(_ => x.result));

    }
}
<file_sep>﻿
namespace Aoc.Day6
{
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    using static Misc.Tools;

    public static class Part1
    {
        private static string Distinct(IEnumerable<string> source) =>
            string.Join("", source)
                .Distinct()
                .Select(x => x.ToString())
                .Aggregate((acc, val) => $"{acc}{val}");

        public static int Solve(string path) =>
            ReadLines(path)
                .SplitBy(x => x == "")
                .Select(Distinct)
                .Select(x => x.Length)
                .Sum();
    }
}
<file_sep>﻿
namespace Aoc.Day6
{
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    using static Misc.Tools;

    public static class Part2
    {
        private static int GroupAnswers(IEnumerable<string> source) =>
            string.Join("", source)
                .GroupBy(x => x)
                .Select(x => x.Count() == source.Count() ? 1 : 0)
                .Sum();

        public static int Solve(string path) =>
            ReadLines(path)
                .SplitBy(x => x == "")
                .Select(GroupAnswers)
                .Sum();
    }
}
<file_sep>﻿
namespace Aoc.Day7
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Aoc.Misc;

    public static class Part2
    { 
        private static Func<Rule, IEnumerable<int>> CountBagsContained(List<Rule> rules) =>
            rule =>
                rule == null ? Enumerable.Empty<int>() :
                rule.Rules
                    .Select(r => r.Count + r.Count * CountBagsContained(rules)(rules.FirstOrDefault(x => x.Color == r.Color)).Sum());

        public static int Solve(string path) =>
            Data.Get(path)
                .Apply(rules =>
                    rules.FirstOrDefault(r => r.Color == "shiny gold")
                    .Apply(CountBagsContained(rules)))
                .Sum();
    }
}
<file_sep>﻿
namespace Aoc.Day8
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Linq;

    using Aoc.Misc;

    using static Aoc.Misc.Tools;

    public static class Part2
    {
        private enum Status { None, Looped, OutOfRange, Terminated }

        private record Instruction(string Action, int Value, Status Status = Status.None)
        {
            public Instruction Done()   => this with { Status = Status.Looped };
            public Instruction Fix()    => this with { Action = Action is "jmp" ? "nop" : "jmp" };

            public static Instruction OutOfRange() => new("", 0, Status.OutOfRange);
            public static Instruction Terminated() => new("", 0, Status.Terminated);
        }

        private record State(
            ImmutableList<Instruction> Instructions, 
            int Result, 
            int Index, 
            ImmutableList<int> FixedIndices, 
            ImmutableList<Instruction> InitialInstructions)
        {
            public Instruction CurrentInstruction =>
                Index < Instructions.Count ? Instructions[Index] :
                Index > Instructions.Count ? Instruction.OutOfRange() :
                                             Instruction.Terminated();

            public State Accumulate()   => this with { Result = Result + CurrentInstruction.Value };
            public State Jump()         => this with { Index = Index + CurrentInstruction.Value };
            public State Next()         => this with { Index = Index + 1 };
            public State Restart()      => this with { Index = 0, Result = 0, Instructions = InitialInstructions };
            public State Fix(int index) => this with { Instructions = Instructions.Select((inst, i) => i == index ? inst.Fix() : inst).ToImmutableList(), FixedIndices = FixedIndices.Add(index) };
            public State Done()         => this with { Instructions = Instructions.Select((inst, i) => Index == i ? inst.Done() : inst).ToImmutableList() };
            public State NextFix()      => Instructions.FindIndex(FixedIndices.Max() + 1, i => i.Action is "jmp" or "nop" && i.Value != 0).Apply(Restart().Fix);

            public static State Init(IEnumerable<Instruction> instructions) =>
                new(
                    Instructions: instructions.ToImmutableList(),
                    Result: 0,
                    Index: 0,
                    FixedIndices: Lst(-1).ToImmutableList(),
                    InitialInstructions: instructions.ToImmutableList());
        }
        private static (string, string) ParseLine(string line) => (line[..3], line[4..]);
        private static Instruction ToDomain((string action, string value) parsedData) =>
            new(parsedData.action, int.Parse(parsedData.value));

        // Doesn't work for large input because c# does not optimize tail recursion
        private static int RunRec(State state) =>
            state.CurrentInstruction switch
            {
                ("nop", _, Status.None)         => state.Done().Next().Apply(RunRec),
                ("acc", _, Status.None)         => state.Done().Accumulate().Next().Apply(RunRec),
                ("jmp", _, Status.None)         => state.Done().Jump().Apply(RunRec),
                (_    , _, Status.OutOfRange)   => state.NextFix().Apply(RunRec),
                (_    , _, Status.Looped)       => state.NextFix().Apply(RunRec),
                (_    , _, Status.Terminated)   => state.Result,
                _ => throw new InvalidOperationException($"Unhandled status {state.CurrentInstruction.Status}")
            };

        // It works but it uses mutating variables and loop
        private static int Run(State initialState)
        {
            var state = initialState;

            while (state.CurrentInstruction.Status != Status.Terminated)
            {
                state = state.CurrentInstruction switch
                {
                    ("nop", _, Status.None)         => state.Done().Next(),
                    ("acc", _, Status.None)         => state.Done().Accumulate().Next(),
                    ("jmp", _, Status.None)         => state.Done().Jump(),
                    (_    , _, Status.OutOfRange)   => state.NextFix(),
                    (_    , _, Status.Looped)       => state.NextFix(),
                    (_    , _, Status.Terminated)   => state,
                    _ => throw new InvalidOperationException($"Unhandled status {state.CurrentInstruction.Status}")
                };
            }

            return state.Result;
        }

        private static int Start(IEnumerable<Instruction> instructions) => State.Init(instructions).Apply(Run);

        public static int Solve(string path) =>
            path
                .Apply(ReadLines)
                .Select(ParseLine)
                .Select(ToDomain)
                .Apply(Start);
    }
}
<file_sep>﻿
namespace Aoc.Day8
{
    using System;

    using Aoc.Misc;

    using LanguageExt;

    class Program
    {
        static void Main(string[] _) =>
            "input.txt"
                .Apply(Part1.Solve)
                .Printn()
                .Apply(_ => "input.txt")
                .Apply(Part2.Solve)
                .Printn();
    }
}
<file_sep>﻿
namespace Aoc.Day9
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Linq;

    using Aoc.Misc;

    using static System.Linq.Enumerable;
    using static Aoc.Misc.Tools;

    public class Part2
    {
        private enum Status { Lower, Greater, Terminated, OnlyOneNumber }

        private record State(
            ImmutableList<long> Numbers,
            long InvalidNumber,
            ImmutableList<int> Indices,
            long Result,
            int Index)
        {
            public Status Status =>
                Result < InvalidNumber ? Status.Lower :
                Result > InvalidNumber ? Status.Greater :
                Indices.Count == 1     ? Status.OnlyOneNumber :
                                         Status.Terminated;

            public State AddIndice() => this with { Indices = Indices.Add(Index) };
            public State SetResult() => this with { Result = Numbers[Index] + Result };
            public State Next() => this with { Index = Index + 1 };
            public State Reset() => this with { Index = Index - Indices.Count + 1, Indices = ImmutableList<int>.Empty, Result = 0 };

            public static State Init(IEnumerable<long> numbers, long invalidNumber) =>
                new(numbers.ToImmutableList(), invalidNumber, ImmutableList<int>.Empty, 0, 0);
        }

        // Recursive
        private static State FindContiguousSetRec(State state) =>
            state.Status switch
            {
                    Status.Lower            => state.AddIndice().SetResult().Next().Apply(FindContiguousSetRec),
                    Status.Greater          => state.Reset().Next().Apply(FindContiguousSetRec),
                    Status.OnlyOneNumber    => state.Reset().Next().Apply(FindContiguousSetRec),
                    Status.Terminated       => state,
                    _ => throw new Exception($"Unhandled status {state.Status}")
            };

        // Mutation + loop
        private static State FindContiguousSet(State state)
        {
            while(state.Status != Status.Terminated)
            {
                state = state.Status switch
                {
                    Status.Lower            => state.AddIndice().SetResult().Next(),
                    Status.Greater          => state.Reset().Next(),
                    Status.OnlyOneNumber    => state.Reset().Next(),
                    Status.Terminated       => state,
                    _ => throw new Exception($"Unhandled status {state.Status}")
                };
            }

            return state;
        }


        public static Func<long, long> Solve(string path) =>
            invalidNumber =>
            path
                .Apply(ReadLines)
                .Select(long.Parse)
                .Apply(lines => FindContiguousSet(State.Init(lines, invalidNumber)))
                .Apply(AddSmallestAndGreatest);

        private static long AddSmallestAndGreatest(State state) => 
            state.Indices
                .Select(i => state.Numbers[i])
                .Apply(ns => ns.Min() + ns.Max());
    }
}
<file_sep>﻿
namespace Aoc.Day9
{
    using System;

    using Aoc.Misc;

    using LanguageExt;

    class Program
    {
        static void Main(string[] _) =>
            "input.txt"
                .Apply(Part1.Solve(25))
                .Printn()
                .Apply(Part2.Solve("input.txt"))
                .Printn();
    }
}
<file_sep>﻿using System;
using System.Linq;

using Aoc.Misc;

using LanguageExt;

using static LanguageExt.Prelude;

namespace Aoc.Day10
{
    class Program
    {
        static void Main(string[] args)
        {
            var input =
                "input.txt"
                .Apply(Tools.ReadLines)
                .Select(int.Parse)
                .Order();

            input
                .Apply(Part1.Solve)
                .Printn();

            input
                .Apply(Part2.Solve)
                .Printn();
        }
    }
}<file_sep>﻿
namespace Aoc.Day9
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Immutable;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    using Aoc.Misc;
    using static Aoc.Misc.Tools;
    using static System.Linq.Enumerable;

    public class Part1
    {
        private record State(ImmutableList<long> Numbers, int Preamble);

        private static IEnumerable<long> GetSums(State state, int index) =>
            state.Numbers
                .Skip(index)
                .Take(state.Preamble)
                .SelectMany(x => state.Numbers.Skip(index).Take(state.Preamble).Where(y => y != x).Select(y => x + y));

        private static bool IsValid(State state, long number, int index) =>
            GetSums(state, index)
                .Contains(number);

        private static long Run(State state) =>
            state.Numbers.Skip(state.Preamble)
                .Where((num, i) => IsValid(state, num, i) == false)
                .FirstOrDefault();

        public static Func<string, long> Solve(int preamble) =>
            path =>
            path
                .Apply(ReadLines)
                .Select(long.Parse)
                .Apply(lines => Run(new(lines.ToImmutableList(), preamble)));
    }
}
