Thorouhgly comment day 1 solution in Chapel
This commit is contained in:
		
							parent
							
								
									1dcecf5dd4
								
							
						
					
					
						commit
						cf78dc036c
					
				
							
								
								
									
										81
									
								
								day1.chpl
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								day1.chpl
									
									
									
									
									
								
							| @ -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); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user