Thorouhgly comment day 1 solution in Chapel

This commit is contained in:
Danila Fedorin 2022-12-01 00:01:54 -08:00
parent 1dcecf5dd4
commit cf78dc036c
1 changed files with 78 additions and 3 deletions

View File

@ -1,11 +1,28 @@
use IO;
use List;
/*
The numbers come to us in blank-line-separated groups. The easiest way to
process all of these groups is to keep an intermediate accumulator
that represents the total number within a group, record that accumulator
each time we hit an empty line. On the other hand, the last group is
not terminated by an empty line, so we'd need special logic to handle
that case. Unless, of course, we just pretended there's an empty line
at the end, too. We can do this with a custom iterator, `linesWithEnding`.
*/
iter linesWithEnding() {
for line in stdin.lines() do yield line;
yield "";
}
/*
On to the actual intermediate accumulator logic described above. The
`current` variable will keep the "up-to-this-point" total within a group.
Whenever we hit an empty line, we know we've finished processing a group,
so we report the value of `current`. Once again we'll make this logic
an iterator; each time it finishes up with a group, it will yield the
group's sum.
*/
iter elves() {
var current = 0;
for line in linesWithEnding() {
@ -19,18 +36,58 @@ iter elves() {
}
}
/*
At this point, part 1 can be solved simply as:
```Chapel
writeln(max reduce elves());
```
*/
/*
For part 2, I'm going to do something a bit more unusual. Chapel has support
for reduction expressions, which can even be run in parallel over many
threads. I'll implement picking the top three elements as a
custom reduction. If I implement all the methods on this reduction
class, I'll be able to automatically make my code run on multuple threads!
*/
class MaxThree : ReduceScanOp {
/* Reductions have an element type, the thing-that's-being-processed.
This element type is left generic to support reductions over different
types of things. */
type eltType;
/* The value our reduction is building up is a top-three list of the largest
numbers. This top-three list is represented by a three-element tuple
of `eltType`, written as `3*eltType`. */
var value: 3*eltType;
/* Reductions need an identity element. This is an element that doesn't
do anything when processed. For instance, for summing, the identity
element is zero (adding zero to a sum doesn't change the sum). For
finding a product, the identity element is one (multiplying by one
leaves the product intact). When finding the _largest_ three numbers
in a list, the identity element is three [infinums](https://en.wikipedia.org/wiki/Infimum_and_supremum)
of that list. We'll assume that the default value of the `eltType`
is its infinum, which means default-initializing a tuple of three
`eltTypes` will give us such a three-infinum tuple.
*/
proc identity {
var val: value.type;
return val;
}
/*
Next are accumulation functions. These describe how to combine partial
results from substs of the list of numbers, or how to update the top
three given a new number. We only need to _really_ implement one version of
these functions - one that combines two 3-tuples. The rest can be defined
in terms of that function.
*/
proc accumulate(x: eltType) { accumulateOntoState(value, x); }
proc accumulateOntoState(ref state: 3*eltType, x: eltType) { accumulateOntoState(state, (0, 0, x)); }
proc accumulate(x: 3*eltType) { accumulateOntoState(value, x); }
/* The accumulation function uses a standard algorithm for merging two sorted
lists. */
proc accumulateOntoState(ref state: 3*eltType, x: 3*eltType) {
var result: state.type;
var ptr1, ptr2: int = 3-1;
@ -48,23 +105,40 @@ class MaxThree : ReduceScanOp {
proc combine(other: MaxThree(eltType)) {
accumulate(other.value);
}
/* The Chapel reduction feature requires a couple of other methods,
which we implement below. */
proc clone() return new unmanaged MaxThree(eltType=eltType);
proc generate() return value;
}
/*
Let's make it possible to select which part we want to solve from the
command line. This can be easily achieved via a `config const`. We'll
also add a `config const` to enable/disable parallel computation. */
config const part = 1;
config const parallel = false;
/* Here's how we use our solution. */
if part == 1 {
/* For part 1, the code remains the same, since we're still just finding
the one maximum number. */
writeln(max reduce elves());
} else if part == 2 {
if parallel {
if !parallel {
/* For the non-parallel version, we can just use `MaxThree` in the
reduce expression, which gives us our top-3 tuple. To solve
the puzzle, all that's left is to sum the elements of that tuple,
which we achieve via another `+ reduce`. */
writeln(+ reduce (MaxThree reduce elves()));
} else {
// Parallel
/* For the parallel case, we have to use a `forall` loop, which is
Chapel's way of expressing parallelism. `forall` loops have
support for `reduce expression`. In all, the code looks like the
following. */
var max3 = (0,0,0);
// Need to read all the numbers into memory to make sure we can distribute
var elfList = new list(elves());
var elfList = elves();
// To make a reduction parallel, we use a forall loop with a reduce intent
forall elf in elfList with (MaxThree(int) reduce max3) {
max3 reduce= elf;
@ -72,3 +146,4 @@ if part == 1 {
writeln(+ reduce max3);
}
}