AoCHelper icon indicating copy to clipboard operation
AoCHelper copied to clipboard

Helper .NET library for solving Advent of Code puzzles

AoCHelper

GitHub Actions Nuget

AoCHelper is a support library for solving Advent of Code puzzles, available for .NET and .NET Standard 2.x.

It provides a 'framework' so that you only have to worry about solving the problems, and measures the performance of your solutions.

Problem example:

using AoCHelper;
using System.Threading.Tasks;

namespace AdventOfCode
{
    public class Day_01 : BaseDay
    {
        public override ValueTask<string> Solve_1() => new("Solution 1");

        public override ValueTask<string> Solve_2() => new("Solution 2");
    }
}

Output example:

aochelper

AdventOfCode.Template

Creating your Advent of Code repository from AdventOfCode.Template is the quickest way to get up and running with AoCHelper.

Simple usage

  • Add AoCHelper NuGet package to your project.
  • Create one class per day/problem, using one of the following approaches:
    • Name them DayXX or Day_XX and make them inherit BaseDay.
    • Name them ProblemXX or Problem_XXand make them inherit BaseProblem.
  • Put your input files under Inputs/ directory and follow XX.txt naming convention for day XX. Make sure to copy those files to your output folder.
  • Choose your solving strategy in your Main() method, adjusting it with your custom SolverConfiguration if needed:
    • Solver.SolveAll();
    • Solver.SolveLast();
    • Solver.SolveLast(new SolverConfiguration { ClearConsole = false });
    • Solver.Solve<Day_05>();
    • Solver.Solve(new List<uint>{ 5, 6 });
    • Solver.Solve(new List<Type> { typeof(Day_05), typeof(Day_06) });

Customization

A custom SolverConfiguration instance can be provided to any of the Solver methods. These are the configurable parameters (false or null by default unless otherwise specified).

  • bool ClearConsole: Clears previous runs information from the console. True by default.
  • bool ShowOverallResults: Shows a panel at the end of the run with aggregated stats of the solved problems. True by default when solving multiple problems, false otherwise.
  • bool ShowConstructorElapsedTime: Shows the time elapsed during the instantiation of a BaseProblem. This normally reflects the elapsed time while parsing the input data.
  • bool ShowTotalElapsedTimePerDay: Shows total elapsed time per day. This includes constructor time + part 1 + part 2.
  • string? ElapsedTimeFormatSpecifier: Custom numeric format strings used for elapsed milliseconds. See Standard numeric format strings.

Advanced usage

You can also:

  • Create your own abstract base class tha(t inherits BaseProblem, make all your problem classes inherit it and use this custom base class to:
    • Override ClassPrefix property, to be able to follow your own $(ClassPrefix)XX or $(ClassPrefix)_XX convention in each one of your problem classes.
    • Override InputFileDirPath to change the input files directory
    • Override InputFileExtension to change the input files extension.
    • Override CalculateIndex() to follow a different XX or _XX convention in your class names.
    • Override InputFilePath to follow a different naming convention in your input files. Check the current implementation to understand how to reuse all the other properties and methods.
  • [Not recommended] Override InputFilePath in any specific problem class to point to a concrete file. This will make the values of ClassPrefix, InputFileDirPath and InputFileExtension and the implementation of CalculateIndex() irrelevant (see the current implementation).

Testing

  • Example of simple AoC solutions testing: SampleTests
  • Example of advanced AoC solutions testing by providing a custom input test filepath: ModifyInputFilePathTests_SampleTests
  • Example of advanced AoC solutions testing by providing a custom input test dir path: ModifyInputFileDirPath_SampleTests

Usage examples

Example projects can be found at:

🆕 v0.x to v1.x migration

BaseProblem.Solve_1() and BaseProblem.Solve_2() signature has changed: they must return ValueTask<string> now.

ValueTask<T> has constructors that accept both T and Task<T>, so:

v0.x:

public class Day_01 : BaseDay
{
    public override string Solve_1() => "Solution 2";

    public override string Solve_2() => FooAsync().Result;

    private async Task<string> FooAsync()
    {
        await Task.Delay(1000);
        return "Solution 2";
    }
}

becomes now in v1.x:

public class Day_01 : BaseDay
{
    public override ValueTask<string> Solve_1() => new("Solution 2");

    public override ValueTask<string> Solve_2() => new(FooAsync());

    private async Task<string> FooAsync()
    {
        await Task.Delay(1000);
        return "Solution 2";
    }
}

or in case we prefer async/await over returning the task, as recommended here:

public class Day_01 : BaseDay
{
    public override ValueTask<string> Solve_1() => new("Solution 2");

    public override async ValueTask<string> Solve_2() => new(await FooAsync());

    private async Task<string> FooAsync()
    {
        await Task.Delay(1000);
        return "Solution 2";
    }
}

Tips

Your problem classes are instantiated only once, so parsing the input file (InputFilePath) in your class constructor allows you to:

  • Avoid executing parsing logic twice per problem.
  • Measure more accurately your part 1 and part 2 solutions performance*.

* Consider enabling ShowConstructorElapsedTime and ShowTotalElapsedTimePerDay in SolverConfiguration.