138 lines
4.8 KiB
Chapel
138 lines
4.8 KiB
Chapel
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() {
|
|
const trimmedLine = line.strip();
|
|
if trimmedLine == "" {
|
|
yield current;
|
|
current = 0;
|
|
} else {
|
|
current += trimmedLine : int;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
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;
|
|
for param idx in (0..<3 by -1) {
|
|
if x[ptr1] > state[ptr2] {
|
|
result[idx] = x[ptr1];
|
|
ptr1 -= 1;
|
|
} else {
|
|
result[idx] = state[ptr2];
|
|
ptr2 -= 1;
|
|
}
|
|
}
|
|
state = result;
|
|
}
|
|
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`. A
|
|
variable like this can be set when running the program from the command
|
|
line as follows:
|
|
|
|
```bash
|
|
./my-program --part=1
|
|
```
|
|
*/
|
|
config const part = 1;
|
|
|
|
/* 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 {
|
|
// Need to read all the numbers into memory to make sure we can distribute
|
|
var elfList = elves();
|
|
writeln(+ reduce (MaxThree reduce elfList));
|
|
}
|
|
|