Welcome to day 1 of Chapel’s Advent of Code 2022 series! If you’re wondering what we’re doing here, check out our introductory Advent of Code 2022: Twelve Days of Chapel blog article for context or instructions on compiling this code.

The Task at Hand and My Approach

In brief, the challenge for today is to read in a series of numbers—one per line—representing the calories in snack items owned by elves. A blank line (or the end of the file) represents the logical end of the current elf’s items. Part one of today’s task is to determine the maximum number of calories owned by any single elf.

This article will walk through a solution I wrote in Chapel in detail, introducing language concepts as we go.

For those who like to jump to the end of books first, here is my solution:

aoc2022-day01-calories.chpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use IO;

var line: string;

var currentCalories, maxCalories = 0;

do {

  const more = readLine(line),
        foundItem = (line.size > 1);

  // If we found an item, update our running tally
  if foundItem then
    currentCalories += (line: int);

  // If we are at the end of an elf's item list, update our maximum
  // value if appropriate and reset our tally for the next elf.
  if !more || !foundItem {
    if currentCalories > maxCalories {
      maxCalories = currentCalories;
    }
    currentCalories = 0;
  }

} while more;

writeln(maxCalories);

There are some clever/cute ways to approach this problem in Chapel using iterators and arrays, but for the sake of focusing on the basics of Chapel for this first day, I stuck with a more naive solution that simply uses scalar variables and traditional control flow constructs. Knowing Advent of Code, we’ll almost certainly get to those other features in the next day or two.

At a high level, the approach I’ve taken is to:

The IO module (and modules in general)

This exercise, like most in AoC, requires reading input from the console or a file. As a result, we will be using Chapel’s standard IO module, which supports a wide variety of routines for reading and writing data. You can read about its features on its page in Chapel’s online documentation or just keep reading this blog series and we’ll teach you several key routines as we go.

One way to make use of a module’s features in Chapel is the use statement:

1
use IO;

The use statement ensures that a module is initialized and makes all of its public symbols available to the current scope.

(More on modules in Chapel…)

Chapel also supports an import statement, which is a far safer and more precise way of accessing a module’s symbols. In these AoC exercises, our goal is to write code sketches quickly, so we’ll tend to rely on use for simplicity and brevity.

For additional context, all Chapel programs are defined in terms of modules, which serve as a way to organize code into distinct units or namespaces. As a simple example, if the code in this article was stored in a file named day01.chpl, it would define an implicit module named day01 containing all of the file’s code. Modules can also be declared explicitly using the module keyword.

Any executable code defined at the top-level of a module’s scope (like the code in this article) will be executed when that module is initialized, as the program begins executing.

For further information on modules, see the related primer or section of the language specification.

Before going on, it’s worth noting that:

Variable Declarations

Let’s start by declaring some variables. Chapel variables can be declared in terms of a type and/or initialization expression (initializer). If no type is specified, the variable will still have a single, static type, which the compiler will infer from its initializer.

First, I’ll define a variable line of type string that will be used to store the lines from the input:

3
var line: string;

Variables are declared using the var keyword in Chapel, which can be used to define one or more variables. The colon operator (:) seen here is used to specify the type of a symbol or expression in Chapel. For example, when declaring a variable, like line here, the expression after : specifies the variable’s type. In other contexts, : serves as Chapel’s cast operator, which we’ll see an example of below.

Chapel’s variable declarations are designed to be read left-to-right, so this statement might be read as:

“Define a variable named line whose type is string.”

Next, we’ll declare a pair of variables to represent our running tally of calories for the current elf’s snacks (currentCalories) and the maximum count we’ve seen so far for any elf (maxCalories):

5
var currentCalories, maxCalories = 0;

Here, you can see that I haven’t specified the types of these variables, just an initializer of 0. Since 0 is an integer value in Chapel (an int), the compiler infers that maxCalories has type int.

When a variable doesn’t have a declared type or initializer, like currentCalories here, but is followed by another variable that does, it shares that information. So in this case, currentCalories is also an int variable initialized to 0.

Control Flow: Loops

If you’re familiar with languages like C/C++, Python, Java, Fortran, etc., Chapel’s features for control flow will probably look familiar, though potentially using slightly different syntax.

For this computation, we want to iterate until we run out of input data, so we’ll use a do...while loop to drive the computation. Chapel also supports other typical serial loop styles—such as for loops—in addition to a variety of parallel loop forms. We’ll likely be using several of these in the days ahead.

Here’s the do statement that marks the start of our loop over the lines in the input:

7
do {

Compound statements in Chapel are enclosed by curly brackets, as in C and C-like languages. The bodies of loops and conditionals are often defined using compound statements, as in this case.

Constant Declarations and Console Input

The next few lines declare a pair of constants using the const keyword. Constants are like variables except that they cannot be modified once they are initialized. Chapel programmers are encouraged to use const when appropriate to enable compiler optimizations and prevent themselves from modifying things that they didn’t intend to.

 9
10
  const more = readLine(line),
        foundItem = (line.size > 1);

The first constant here is initialized by a call to the readLine(), routine, provided by the IO module. We pass it our string variable line as an argument, and it will attempt to read a line from the console into that string. readLine() returns a Boolean value (a bool) indicating whether we have reached the end of the file or not. Here, I’m storing that in a constant named more to indicate whether there is potentially more data to be read.

The second constant, also a bool, will be set to true if the length of the string we read is greater than 1. I’m using this test to determine whether or not the line contained a calorie count. By default, readLine() stores the line’s newline character (\n) into the string argument, which is why I’m comparing against 1 rather than 0 to find empty lines.

I was tempted to read integer values directly from the file rather than using strings, but the methods for reading integers that I’m most familiar with ignore whitespace; and since we need to be sensitive to blank lines in today’s challenge, reading them as strings seemed more straightforward, particularly for this first lesson.

Control Flow: Conditionals and Computation

The next chunks of code implement the main logic of this computation using a series of conditionals.

The first conditional is used to increment our running total if we found an item on the line:

12
13
14
  // If we found an item, update our running tally
  if foundItem then
    currentCalories += (line: int);

If the body of a conditional is a single statement, as in this case, the keyword then can be used to specify that body.

The body of this conditional uses the aforementioned cast operator (:) to turn the string value we read into an integer. It also uses Chapel’s += operator, which serves as shorthand for currentCalories = currentCalories + (line: int);.

The logic in the following conditionals essentially just updates our maximum value if appropriate and resets our running total to set up for the next elf.

16
17
18
19
20
21
22
23
  // If we are at the end of an elf's item list, update our maximum
  // value if appropriate and reset our tally for the next elf.
  if !more || !foundItem {
    if currentCalories > maxCalories {
      maxCalories = currentCalories;
    }
    currentCalories = 0;
  }

If a conditional’s body contains multiple statements, as in the outer conditional here, curly brackets must be used, similar to the do...while loop containing all this code. Even for single-statement conditionals, like the inner one here, it’s often considered good practice to use curly brackets, as a means of improving clarity and/or reducing the potential for errors to be introduced if new statements are added to the body over time.

(Here’s another way to update the max…)

The inner conditional here could also be replaced by a call to Chapel’s max() routine, which returns the largest of its arguments, like this:

maxCalories = max(currentCalories, maxCalories);

At this point, I want to go on to reading the next line if there was one, so I wrap up my do...while loop using:

25
} while more;

Once more becomes false, we’ll exit the loop and continue on to the next statement.

Console Output

An easy way to print to the console in Chapel is the writeln() routine, which takes an arbitrary number of expressions and prints them out one after the other to the console. Nearly any Chapel expression can be written out in this way, regardless of type, making it a useful way to check the values of arbitrary expressions when debugging.

27
writeln(maxCalories);

Summary and Tips for Part Two

That wraps up this first day of introducing Chapel through AoC 2022. As a reminder, the full code for my solution can be viewed at the top of this article, or browsed and downloaded from GitHub.

If you choose to go on to part two of the day 1 challenge, it asks you to track the three elves with the most calories and sum their values. This can be achieved using the concepts introduced above by juggling some additional scalar variables.

Alternatively, you could choose to use an array to track the three maximum values seen so far. Such an array can be declared using:

var maxCalories: [1..3] int;

or

var maxCalories: [0..<3] int;

depending on whether you prefer 1- or 0-based indexing.

If you use arrays, for-loops over integer sequences can be very useful, and they can be written in Chapel as

for i in 1..3 { ... }
for i in 0..<3 { ... }

or the like. Another tip is that the elements of an array can be summed into a single value using a reduction of the form:

total = + reduce MyArray

That said, these are just teasers, and we’ll almost certainly return to these concepts in more detail in the coming days, since they tend to be very important for lots of Chapel computations and AoC exercises.

See you tomorrow!