2424//!
2525//! This means that we can store each snailfish number as an implicit data structure in a fixed-size
2626//! array. This is faster, smaller and more convenient than using a traditional struct with pointers.
27- //! The root node is stored at index 1 (index 0 is unused). For a node at index `i` its left child
28- //! is at index `2i`, right child at index `2i + 1` and parent at index `i / 2`. As leaf nodes are
29- //! always greater than or equal to zero, `-1` is used as a special sentinel value for non-leaf nodes.
27+ //! The root node is stored at index 1 (index 0 is unused by the tree, but see below). For a
28+ //! node at index `i` its left child is at index `2i`, right child at index `2i + 1` and parent
29+ //! at index `i / 2`. As leaf nodes are always greater than or equal to zero, `-1` is used as a
30+ //! special sentinel value for non-leaf nodes.
31+ //!
32+ //! Another optimization is realizing that all of the explode actions before the first split can
33+ //! be pre-computed. Instead of parsing a line `[1,[2,[3,[4,5]]]]` as written, we instead parse it
34+ //! it as if it had been `[[1,[2,[3,[4,5]]]],0]`, then perform the initial explode actions for that
35+ //! line up front, such that node 3 contains the value that must be added to the first leaf of a
36+ //! right-hand value in summation. Node 0 is not used by the implicit tree structure, so we instead
37+ //! use it as a tri-state value:
38+ //!
39+ //! - -2 means this Snailfish number is the result of a sum, for the left side during part one.
40+ //! When passed to `add()`, we must shuffle contents one level lower.
41+ //! - -1 means this Snailfish number was just parsed, but did not explode left. When used on the
42+ //! right side of add, any spillover from the left is added to the first leaf on the right.
43+ //! - Non-negative means this Snailfish number was just parsed, and had an explode that spilled
44+ //! left. When used as the right side of an add, any spill from the left is combined with
45+ //! this value, then added to the last leaf on the left.
3046use crate :: util:: parse:: * ;
3147use crate :: util:: thread:: * ;
3248
@@ -49,8 +65,12 @@ pub fn parse(input: &str) -> Vec<Snailfish> {
4965 input
5066 . lines ( )
5167 . map ( |line : & str | {
68+ // Treat the line as if it had been `[line,0]`, then perform explode until it is back
69+ // at depth 4. This allows later add() operations to do less work. Index 0 and 3
70+ // then track the amount spilled left or right from those explodes.
5271 let mut tree = [ -1 ; 64 ] ;
53- let mut i = 1 ;
72+ tree[ 3 ] = 0 ;
73+ let mut i = 2 ;
5474
5575 for b in line. bytes ( ) {
5676 match b {
@@ -60,6 +80,11 @@ pub fn parse(input: &str) -> Vec<Snailfish> {
6080 b => tree[ i] = b. to_decimal ( ) as i32 ,
6181 }
6282 }
83+ for pair in ( 32 ..48 ) . step_by ( 2 ) {
84+ if tree[ pair] >= 0 {
85+ explode ( & mut tree, pair) ;
86+ }
87+ }
6388
6489 tree
6590 } )
@@ -100,29 +125,56 @@ fn worker(iter: ParIter<'_, (&Snailfish, &Snailfish)>) -> Option<i32> {
100125/// The initial step creates a new root node then makes the numbers the left and right children
101126/// of this new root node, by copying the respective ranges of the implicit trees.
102127///
103- /// We can optimize the rules a little. This initial combination is the only time that more than one
104- /// pair will be 4 levels deep simultaneously, so we can sweep from left to right on all possible
105- /// leaf nodes in one pass.
128+ /// We can optimize the rules a little. The parse step already ensured that there are no pairs
129+ /// deeper than 4 levels, and precomputed any explode values to spill between the two halves
130+ /// of the joined value. All that remains is checking for splits, where each split also takes
131+ /// care of any additional explodes needed.
106132fn add ( left : & Snailfish , right : & Snailfish ) -> Snailfish {
107133 let mut tree = [ -1 ; 64 ] ;
108134
109- tree[ 4 ..6 ] . copy_from_slice ( & left[ 2 ..4 ] ) ;
110- tree[ 8 ..12 ] . copy_from_slice ( & left[ 4 ..8 ] ) ;
111- tree[ 16 ..24 ] . copy_from_slice ( & left[ 8 ..16 ] ) ;
112- tree[ 32 ..48 ] . copy_from_slice ( & left[ 16 ..32 ] ) ;
113-
114- tree[ 6 ..8 ] . copy_from_slice ( & right[ 2 ..4 ] ) ;
115- tree[ 12 ..16 ] . copy_from_slice ( & right[ 4 ..8 ] ) ;
116- tree[ 24 ..32 ] . copy_from_slice ( & right[ 8 ..16 ] ) ;
117- tree[ 48 ..64 ] . copy_from_slice ( & right[ 16 ..32 ] ) ;
135+ if left[ 0 ] == -2 {
136+ // Left comes from a running sum during part one. We need to increase the depth, which
137+ // in turn might cause some depth 5 leaves that need explode.
138+ tree[ 3 ] = 0 ;
139+ tree[ 4 ..6 ] . copy_from_slice ( & left[ 2 ..4 ] ) ;
140+ tree[ 8 ..12 ] . copy_from_slice ( & left[ 4 ..8 ] ) ;
141+ tree[ 16 ..24 ] . copy_from_slice ( & left[ 8 ..16 ] ) ;
142+ tree[ 32 ..48 ] . copy_from_slice ( & left[ 16 ..32 ] ) ;
143+
144+ for pair in ( 32 ..48 ) . step_by ( 2 ) {
145+ if tree[ pair] >= 0 {
146+ explode ( & mut tree, pair) ;
147+ }
148+ }
149+ } else {
150+ // We are adding two just-parsed numbers; the left is already rooted at 2 and has no depth 5
151+ // leaves, making it ready to copy into place.
152+ tree[ 3 ..24 ] . copy_from_slice ( & left[ 3 ..24 ] ) ;
153+ }
118154
119- for pair in ( 32 ..64 ) . step_by ( 2 ) {
120- if tree[ pair] >= 0 {
121- explode ( & mut tree, pair) ;
155+ // Copy the right into place. This value is always just-parsed, with no depth 5 leaves.
156+ tree[ 6 ..8 ] . copy_from_slice ( & right[ 4 ..6 ] ) ;
157+ tree[ 12 ..16 ] . copy_from_slice ( & right[ 8 ..12 ] ) ;
158+ tree[ 24 ..32 ] . copy_from_slice ( & right[ 16 ..24 ] ) ;
159+
160+ // Adjust by the explode spillover between sides. We ensured that tree[3] contains any
161+ // value to spill right, but must check right[0] to see if that sum then spills back left.
162+ let ( mut i, spill) = if right[ 0 ] == -1 { ( 24 , tree[ 3 ] ) } else { ( 23 , tree[ 3 ] + right[ 0 ] ) } ;
163+ loop {
164+ if tree[ i] >= 0 {
165+ tree[ i] += spill;
166+ break ;
122167 }
168+ i /= 2 ;
123169 }
170+ tree[ 3 ] = -1 ;
124171
172+ // Now we process all split operations; any further explode actions are done during the split
173+ // that creates a temporary depth 5.
125174 while split ( & mut tree) { }
175+
176+ // Mark this tree as a sum before returning it.
177+ tree[ 0 ] = -2 ;
126178 tree
127179}
128180
@@ -145,6 +197,9 @@ fn explode(tree: &mut Snailfish, pair: usize) {
145197 }
146198 i /= 2 ;
147199 }
200+ } else {
201+ // Store the left spill-out for later use by add().
202+ tree[ 0 ] = tree[ pair] ;
148203 }
149204
150205 if pair < 62 {
0 commit comments