There is a yearly software development challenge called the Advent of Code. It is 25 two-part problems where the answer is a number. It is platform agnostic and is a great challenge for developers at all levels.

I’ve completed the challenge in previous years in several different programming languages including C++, C#, and Python. This year I’m going to attempt it in Business Central AL.

While it would be interesting to see all 50 challenges solved in AL, I will document the first 8 days. Why stop there? With each day the challenges become more difficult to solve in AL and the code created moves from having educational value to an example of bad code forced to solve a problem. What I don’t want to create is a library of examples of code you shouldn’t use.

Here are my notes on the challenges presented each day. Please open the GitHub repository and follow along. AardvarkMan/AOC2024: Advent of Code in BC AL. Each header will link you directly to the Code Unit where the challenge is resolved.

Each day is a Code Unit. Each Code Unit has a Calculate Results 1 and Calculate Results 2 procedure for me to code the solutions to the first and second part of the challenge. These code units share a common interface. This allows me to create a factory Code Unit to kick out the right processing code unit for any given day.

Here is the factory that returns the Code Unit based on which day I say I’m processing.

procedure GetProcessor(Day: Integer): interface ARD_AdventOfCodeProcessor
    var
        Day1: CodeUnit ARD_AOC202401;
        Day2: CodeUnit ARD_AOC202402;
        Day3: Codeunit ARD_AOC202403;
        Day4: Codeunit ARD_AOC202404;
        Day5: Codeunit ARD_AOC202405;
        Day6: Codeunit ARD_AOC202406;
        Day7: Codeunit ARD_AOC202407;
        Day8: Codeunit ARD_AOC202408;
    begin
        case Day of
        1:
            exit(Day1);
        2:
            exit(Day2);
        3:
            exit(Day3);
        4:
            exit(Day4);
        5:
            exit(Day5);
        6:
            exit(Day6);
        7:
            exit(Day7);
        8:
            exit(Day8);
        else
            error('No processor available for day %1.', Day);
        end;
    end;

I can now create a procedure that passes the day number, and I can process the result.

    Procedure CalculateResult1(Rec: record ARD_AOCChallenge; RunExample: Boolean): Decimal
    var 
        AOCFactory: Codeunit ARD_AOCProcessorFactory;
        Results: Decimal;
    begin
        Results := 0;
        Results := AOCFactory.GetProcessor(Rec.ARD_Day).CalculateResult1(Rec, RunExample);
        exit(Results);
    end;

Because of the interface, I know that whatever Code Unit I get back from this factory, I can call CalculateResults1 and it will return a decimal result.

I’m going to document part one of each challenge. The complete challenge will be in the GitHub as AOC Day X.txt if you want to read the second part of the challenge and see how I solved it. This post is going to be long enough already!

Day 1

The first day is always a softball, it gets you started, teaches you how to participate in the challenges and helps you get your process sorted out. Each day the inputs are lists of strings that need parsing, so I have a basic Media Blob of text to list of text processor.

    procedure SplitLines(Input: Text): List of [Text]
    var
        DirtyList: List of [Text];
        Results: List of [Text];
        NewLineCharacter: Char;
        LineText: Text;
    begin
        NewLineCharacter := 10;

        DirtyList := Input.Split(NewLineCharacter);

        foreach LineText in DirtyList do
            if StrLen(LineText.Trim()) > 0 then
                Results.Add(LineText.Trim());

        exit(Results);
    end;

This code splits the text on a carriage return (character 10) and then splits it into a list. It then runs through that list and does a little cleanup on the lines by removing empty lines and trimming white space. I’m going to needs this a lot, so it is in a supporting Code Unit.

This challenge started with a list of number:

3   4
4   3
2   5
1   3
3   9
3   3

This is a sort and compare challenge. We need to find the difference between the lowest number in the left column with the lowest number in the right column, then the next lowest in left, with the next lowest in the right, and so on.

Sounds like a great application of the List data type – Business Central | Microsoft Learn. However, the list data type can’t sort. I created a Table that has a key and 2 integer values. We can load the data into the table and use the SetCurrentKey procedure to set the key to the first value. This will sort the table on this value ascending. I can then pop this data into a new Sorted List of values.

We do the same trick again and sort the data on the second integer column. Now I have a sorted column in the table and a sorted list. We can go row by row and calculate the difference between the two values and generate our result for the challenge.

Day 2

This challenge introduces iterative data evaluations. We are provided with a dataset and some simple rules to evaluate each data component. This is interesting because in Business Central AL we are almost always playing with a record somewhere. In this case all the processing is completed in lists. We have a list of lines, and each line is a list of numbers.

I added an additional routine to split values based on a space character. This is common for Advent of Code, so I added it to the support Code Unit.

    procedure SplitValues(Input: Text): List of [Text]
    var
        DirtyList: List of [Text];
        Results: List of [Text];
        NewLineCharacter: Char;
        LineText: Text;
    begin
        NewLineCharacter := 32;

        DirtyList := Input.Split(NewLineCharacter);

        foreach LineText in DirtyList do
            if StrLen(LineText.Trim()) > 0 then
                Results.Add(LineText.Trim());

        exit(Results);
    end;

The Day 2 narrative has you searching for a problem, part one of the challenge has you identify the problem.

We start with a matrix of numbers.

7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9

We can evaluate this list from Left to Right and test if it meets a set of rules. A pair of nested Foreach loops walks us through the data neatly and we can do our analysis as needed.

Part two of the challenge has you fixing the problem. This has you looking forwards and backwards through the data. Here is where a more complex data object structure would be useful, a class where I can encapsulate functions and values would have been nice. It is only Day 2, we can solve it in a list.

Day3

I knew this day would come, and it came early this year, the RegEx puzzle! There is always a puzzle that can be solved with a hundred lines of code, or a single RegEx statement. The RegEx handlers in Business Central are very capable and made short work of this challenge.

We are given a block of text, something like this, but 1000’s of characters.

xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))

We need to find the command mul(x,x) and multiply the numbers.

This can be done with string parsing or we can use RegEx like this.

Pattern := 'mul\(\d+,\d+\)';

Regex.Match(Inputs, Pattern, TempMatches);

if TempMatches.FindSet() then
    repeat
        MatchText := TempMatches.ReadValue();
        MatchText := MatchText.Remove(1, 4);
        MatchText := MatchText.Replace(')', '');
        Values := MatchText.Split(',');
        System.Evaluate(Value1, Values.get(1));
        System.Evaluate(Value2, Values.get(2));
        Result := Result + (Value1 * Value2);
    until TempMatches.Next() = 0;

The Regex.Match procedure pulls the matching statements out of the text and places them neatly in a Matches temporary table. We can then iterate through the table and process the results.

Regex is a super powerful way to handle text data parsing. I use Regex Hero to create and test my patterns. .NET Regular Expression Tester and Reference

Day 4

This challenge presents itself as a word-search challenge. In Business Central this is a list containing a list of characters building out a matrix. The solution was a matter of crawling through the matrix and testing specific conditions.

MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX

In this search we can find XMAS written out 18 times.

While a data matrix of characters is not something I would expect to see in Business Central, the list of lists of characters was sufficient to solve the challenge.

To solve this problem, we start with a search through the table for a starting point, an ‘X’. We then search one of the 8 possible directions for the next characters in the string. If we get to the end of the string without missing a character, then we found a match.

Day 5

Here is the first day that I feel a little pain and I feel like the solution is a bit ham fisted. The BC Record is the only thing I can create that contains data and procedures as an encapsulated unit. What I want is a class, a structure that contains data, and methods/functions/procedures.

I have rows of data of arbitrary length and a set of inputs to drive functions within that data set. I would have loved to create a class to hold a single data element and accept a set of rules. Then I would have a collection of these classes and have them all process away and collect the results when completed.

As opposed to that, I have a table, a bunch of lists, and some code I’m not proud of.

The input is also unique this week in that we have rules and data in a single text block. Rules and Data are separated by a blank line, and they are distinct enough that parsing them out into two data sets isn’t too much of a challenge.

61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13

75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47

The structure of Business Central tends to be focused on a single record or a record set, and working on datasets for problem solving is going to be a challenge. I was able to place each rule into the Integer table we created for Day 1 and use a list of values for test data.

This worked fine for Part 1 where you identify records that break the rules, but Part 2 you try to fix the data. Part 2 would have really benefited from a class where I could have encapsulated the data and processing values. I do believe that if you can do it with Object Oriented Programing, you can do it without Object Oriented Programing. I’m also a spoiled brat that knows there are “better” ways to do things.

Day 6

I knew this day would come, a problem that is best solved through recursion. This is a non-branching problem, but it is deep and will take a lot of loops to complete it.

In this challenge you, starting at the ^ character, move in an up direction until you hit a #, turn 90, move until you hit a #, turn 90 degrees and repeat until you exit the matrix.

....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...

We are attempting to identify how many unique places we visit, and in the example, we visit 47 locations. Solving this with recursion where are call a move method procedure for each step worked great.

The larger data set is 130×130 matrix and may steps are repeated requiring a lot of recursions.

It went exactly how I expected it would:

If requesting support, please provide the following details to help troubleshooting:

Error message: 
There is insufficient memory to execute this function. This can be caused by recursive function calls. Contact your system administrator.

Internal session ID: 
f15b30d3-0d6d-4424-b352-cc6716b78d2d

Application Insights session ID: 
743c8211-02de-4243-a052-069e3494dfde

Client activity id: 
bc686532-867b-aa4e-7804-c7c1c11d7a9f

Time stamp on error: 
2024-12-06T18:45:19.0530156Z

User telemetry id: 
88049e18-72a8-45d7-9c13-2e6fab7663d7

AL call stack: 
ARD_AOC202406(CodeUnit 82033).navigatemap line 49 - Aardvark Labs Advent of Code 2024 by Aardvark Labs
ARD_AOC202406(CodeUnit 82033).navigatemap line 49 - Aardvark Labs Advent of Code 2024 by Aardvark Labs
ARD_AOC202406(CodeUnit 82033).navigatemap line 49 - Aardvark Labs Advent of Code 2024 by Aardvark Labs
ARD_AOC202406(CodeUnit 82033).navigatemap line 49 - Aardvark Labs Advent of Code 2024 by Aardvark Labs
ARD_AOC202406(CodeUnit 82033).navigatemap line 49 - Aardvark Labs Advent of Code 2024 by Aardvark Labs

Part 1 was able to be completed in a while loop, and it hurt my soul a little to do it, but it works.

Part 2 adds a twist in that I can solve the sample challenges in a loop, but there are edge cases that the AOC community found that aren’t in the sample data, and they aren’t well handled in a while loop. I’m going to take the loss on this one and revisit later.

This sounds like a failure, but the use case is crazy. There is no reason to do this kind of work in Business Central AL. The number of resources it is going to take would be detrimental to other processes running on the system. While it is a failure for the challenge, I’m call it a win for the stability and operations of Business Central.

For most of us Business Central is SaaS. There are times where we should kick a process out to an Azure Function or other external processing service to do the work. It is prudent to do the work in the right location with the right tools.

Day 7

Recursion shows its face again, but this time depth is managed, and Business Central AL lets me get the work done! While it requires either a two or three branch process, the scope is only a few hundred recursions and AL handles it like a champ!

In this challenge we have a value followed by a series of numbers like this.

190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20

The challenge is to test if the numbers to the right of the “:” can be added or multiplied together to produce the value on the left. Strictly left to right operation, no PEMDAS order of operations.

This is a great utilization of a recursive function.

local procedure CheckResult(Expected: decimal; Current: decimal; var Values: List of [decimal]; Index: decimal): boolean
var
    Result: boolean;
begin
    if Current > Expected then exit(false);

    if Index > Values.Count then
        Exit(Current = Expected);

    Result := CheckResult(Expected, Current + Values.Get(Index), Values, Index + 1) or CheckResult(Expected, Current * Values.Get(Index), Values, Index + 1);

    exit(Result);
end;

By calling the CheckResult procedure with the Expected value, Current value, Index of the data point we are testing we can evaluate for the + and * results of each test with a single procedure called recursively.

The astute among you may have noticed a switch from Integer values to Decimal values. The result from this calculation was 4,122,618,559,853 which is larger than the max integer size of 2,147,483,647. This is a reasonable integer limit for an accounting system, I have no notes.

Day 8

This challenge has us doing Vector math in a Matrix. In this challenge for every pair of matching characters in the matrix, we calculate out a point double the distance between the two points. The answer is how many points are within the matrix.

............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............

This matrix generates 14 points within the grid.

The calculation requires you to find a non “.” value, search the matrix for matches and given the two coordinates, calculate a value and test if it is in the bounds of the matrix. Sounds easy!

Really what I need is a Hash Set, and this would be a piece of cake. Add support for HashSet · Issue #1668 · microsoft/AL Oh, well I guess I’ll do it the hard way.

My code works great on the sample but fails on the larger 50×50 dataset. The debugging tools simply don’t have what I need to trace the error down. The other issues are that the kids are home from college, and I smell cookies being made.

In Closing

This is where I’m going to end the challenge for now. Reviewing the next few days following Day 8 and there is a lot more matrix math, more scientific calculations, and a lot more demand for objects and recursion. Publishing solutions to those challenges in AL would be publishing “bad code”.

Here are a few things to take away from the solutions to the first 8 days:

  • Example implementation of an Interface.
  • Example implementation of an Interface Factory.
  • Examples of string parsing.
  • Utilization of the List object.
  • Using a List of Lists to generate a Matrix.
  • List and Matrix navigation techniques.
  • Use of the Regular Expressions Code Unit.
  • Recursion!
  • When to stop and move on to an external processor like an Azure Function.

I had a lot of fun with this, and I hope you take a moment to give the Advent of Code Challenge a try. It is a great way to practice a language and learn some new programming concepts.

I hope you had a great holiday season!

Leave a comment

Trending