Advent of Code 2024 day 2, RPG edition

This is the second day of Advent of Code, let’s solve today’s puzzles with RPG.

The puzzle as well as the input data are available here.

The code is available here.

Part 1

We are provided with a (stream) file containing unusual data. Each line is a report. Each report contains multiple levels, separated by spaces.

Each report can be safe or unsafe. To be safe, the following conditions must be met:

  • the levels are either all increasing or all decreasing
  • any two adjacent levels differ by at least one and at most three

We need to calculate how many reports are safe.

The IFS file can easily be read using QSYS2.IFS_READ_UTF8 table function.

Each report can be devided into levels with the %SPLIT RPG built-in function. We can iterate over the levels extracted by %SPLIT with the for-each instruction.

On the first level, we don’t need to do any check.

On the second level, we determine if the report is increasing or decreasing and we check if it differs from the first one by a value of 1, 2 or 3.

Starting at the third level, we check if the direction of the report is correct and if the difference from the previous level is between 1 and 3.

As soon as a level is incorrect, the report is considered unsafe.

If we go over all levels without any incorrect check, the report is safe.

Here is the RPG code for part 1:

**free
ctl-opt dftactgrp(*no);  // We're using procedure so we can't be in the default activation group

dcl-pi *n;
  input char(50);
end-pi;

dcl-pr isSafe ind;
  data varchar(255);
end-pr;

dcl-s fileName varchar(50);

dcl-s data varchar(255);
dcl-s safeReports int(10) inz(0);

fileName = %trim(input);

// Read the input data from the IFS one line at a time
exec sql declare c1 cursor for select cast(line as varchar(255)) from table(qsys2.ifs_read_utf8(path_name => :fileName));

exec sql open c1;

// Read first line
exec sql fetch from c1 into :data;

// Loop until the end of the file
dow sqlcode = 0;

  // If the report is safe we increment the safe counter
  if isSafe(data);
    safeReports += 1;
  endif;

  // Read next line
  exec sql fetch from c1 into :data;
enddo;

exec sql close c1;

snd-msg *info 'Safe reports: ' + %char(safeReports) %target(*pgmbdy:1); // Send message with answer

*inlr = *on;
return;

dcl-proc isSafe;

dcl-pi *n ind;
  data varchar(255);
end-pi;

dcl-s value char(5);        // Current value as a string
dcl-s previous int(10);     // Previous value
dcl-s current int(10);      // Current value as an integer
dcl-s index int(10) inz(1); // Current index of the value in the report (starts at 1)
dcl-s increasing ind;       // *on if the reports values are increasing, *off if decreasing

// Split the report into values and loop over each value
for-each value in %split(data);
  current = %int(value); // Convert the current value to integer

  if index > 1; // We only start testing at the second value
    if index = 2; // On the second value, we determine if the report is increasing or decreasing
      increasing = current > previous;
    else;         // After the second value, we check if the report keeps increasing or decreasing
      if increasing <> (current > previous); // If the current value doesn't conform to the report direction, the report is unsafe
        return *off;
      endif;
    endif;
    if not (%abs(current - previous) in %range(1:3)); // If the difference from the previous value is not between 1 and 3, the report is unsafe
      return *off;
    endif;
  endif;

  index += 1;          // Increase the index
  previous = current;  // The current value is stored as the previous value for the next iteration
endfor;

return *on; // If we get here, the report isn't unsafe, therefore it is safe

end-proc;

Part 2

In part 2, we’ll use the same input as in part 1.

We still need to calculate how many reports are safe but this time, we tolerate, at most, one incorrect level in a report (meaning that by removing one level, we get a report with only correct levels).

We modify the isSafe procedure by adding a second parameter that is the index of the level we want to skip. If this index is 0, it means that we don’t skip any level.

When we encounter an incorrect level, instead of declaring the report as unsafe, we call the isSafe procedure again but skipping the current index (and the n-1 and n-2 in some cases) if we are not already skipping a level. If by skipping a level, the report is safe then we consider the report safe.

**free
ctl-opt dftactgrp(*no);

dcl-pi *n;
  input char(50);
end-pi;

dcl-pr isSafe ind;
  data varchar(255);
  skip int(10) value;
end-pr;

dcl-s fileName varchar(50);

dcl-s data varchar(255);
dcl-s safeReports int(10) inz(0);

fileName = %trim(input);

exec sql declare c1 cursor for select cast(line as varchar(255)) from table(qsys2.ifs_read_utf8(path_name => :fileName));

exec sql open c1;

exec sql fetch from c1 into :data;

dow sqlcode = 0;

  if isSafe(data:0);
    safeReports += 1;
  endif;

  exec sql fetch from c1 into :data;
enddo;

exec sql close c1;

snd-msg *info 'Safe reports: ' + %char(safeReports) %target(*pgmbdy:1);

*inlr = *on;
return;

dcl-proc isSafe;

dcl-pi *n ind;
  data varchar(255);
  skip int(10) value;  // if skip is 0, there is no skip. If skip > 0, this is the index to skip. Passed by value for ease of calling
end-pi;

dcl-s value char(5);
dcl-s previous int(10);
dcl-s current int(10);
dcl-s index int(10) inz(1);
dcl-s realIndex int(10) inz(1); // This is the real index that doesn't take skipping into account
dcl-s increasing ind;

for-each value in %split(data);
  if realIndex <> skip;             // If the current index is the one to be skipped, then we skip
  current = %int(value);

  if index > 1;
    if index = 2;
      increasing = current > previous;
    else;
      if increasing <> (current > previous);
        if skip > 0;                 // If we are already skipping and still have a bad level, the report is unsafe since the Problem Dampener can only tolerate one bad level
          return *off;
        else;
          if index = 3;              // The 3rd value is a special case, the bad level can be fixed by removing the first, second or third value
            return isSafe(data:3) or isSafe(data:2) or isSafe(data:1);
          else;                      // After the third value, the direction can only be fixed by removing the current value
            return isSafe(data:index);
          endif;
        endif;
      endif;
    endif;
    if not (%abs(current - previous) in %range(1:3));
      if skip >0;         // If we are already skipping and still have a bad level, the report is unsafe since the Problem Dampener can only tolerate one bad level
        return *off;
      else;               // We try to fix the report by removing the current or the previous value
        return isSafe(data:index) or isSafe(data:index-1);
      endif;
    endif;
  endif;

  index += 1;
  previous = current;
  endif;
  realIndex += 1;   // realIndex is incremented even if we are skipping this value
endfor;

return *on;

end-proc;