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 IO; | ||||||
| use List; | 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() { | iter linesWithEnding() { | ||||||
|   for line in stdin.lines() do yield line; |   for line in stdin.lines() do yield line; | ||||||
|   yield ""; |   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() { | iter elves() { | ||||||
|   var current = 0; |   var current = 0; | ||||||
|   for line in linesWithEnding() { |   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 { | 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; |   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; |   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 { |   proc identity { | ||||||
|     var val: value.type; |     var val: value.type; | ||||||
|     return val; |     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 accumulate(x: eltType) { accumulateOntoState(value, x); } | ||||||
|   proc accumulateOntoState(ref state: 3*eltType, x: eltType) { accumulateOntoState(state, (0, 0, x)); } |   proc accumulateOntoState(ref state: 3*eltType, x: eltType) { accumulateOntoState(state, (0, 0, x)); } | ||||||
|   proc accumulate(x: 3*eltType) { accumulateOntoState(value, 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) { |   proc accumulateOntoState(ref state: 3*eltType, x: 3*eltType) { | ||||||
|     var result: state.type; |     var result: state.type; | ||||||
|     var ptr1, ptr2: int = 3-1; |     var ptr1, ptr2: int = 3-1; | ||||||
| @ -48,23 +105,40 @@ class MaxThree : ReduceScanOp { | |||||||
|   proc combine(other: MaxThree(eltType)) { |   proc combine(other: MaxThree(eltType)) { | ||||||
|     accumulate(other.value); |     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 clone() return new unmanaged MaxThree(eltType=eltType); | ||||||
|   proc generate() return value; |   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 part = 1; | ||||||
| config const parallel = false; | config const parallel = false; | ||||||
| 
 | 
 | ||||||
|  | /* Here's how we use our solution. */ | ||||||
| if part == 1 { | 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()); |   writeln(max reduce elves()); | ||||||
| } else if part == 2 { | } 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())); |     writeln(+ reduce (MaxThree reduce elves())); | ||||||
|   } else { |   } 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); |     var max3 = (0,0,0); | ||||||
|     // Need to read all the numbers into memory to make sure we can distribute |     // 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 |     // To make a reduction parallel, we use a forall loop with a reduce intent | ||||||
|     forall elf in elfList with (MaxThree(int) reduce max3) { |     forall elf in elfList with (MaxThree(int) reduce max3) { | ||||||
|       max3 reduce= elf; |       max3 reduce= elf; | ||||||
| @ -72,3 +146,4 @@ if part == 1 { | |||||||
|     writeln(+ reduce max3); |     writeln(+ reduce max3); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user