1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
#![feature(associated_type_defaults)]
#![allow(dead_code)]
#![deny(missing_docs)]
//! Algorithms used to solve [Andy's Morning Stroll](https://www.janestreet.com/puzzles/current-puzzle/):
//!
//! > Andy the ant has spent most of his days living on a strange land consisting of white hexagons
//! > that are surrounded by alternating black pentagons and white hexagons (three of each), and
//! > black pentagons surrounded by five white hexagons. To us this land is familiar as the classic
//! > soccer ball we see above on the left. Due to Andy’s tiny size and terrible eyesight, he doesn’t
//! > notice the curvature of the land and avoids the black pentagons because he suspects they may be
//! > bottomless pits.
//! >  
//! > Every morning he wakes up on a white hexagon, leaves some pheromones to mark it has his special
//! > home space, and starts his random morning stroll. Every step on this stroll takes him to one of
//! > the three neighboring white hexagons with equal probability. He ends his stroll as soon has he
//! > first returns to his home space. As an example, on exactly 1/3 of mornings Andy’s stroll is 2
//! > steps long, as he randomly visits one of the three neighbors, and then has a 1/3 probability
//! > of returning immediately to the home hexagon.
//! >
//! > This morning, his soccer ball bounced through a kitchen with an infinite (at least practically
//! > speaking…) regular hexagonal floor tiling consisting of black and white hexagons, a small part
//! > of which is shown above on the right. In this tiling every white hexagon is surrounded by
//! > alternating black and white hexagons, and black hexagons are surrounded by six white hexagons.
//! > Andy fell off the ball and woke up on a white hexagon. He didn’t notice any change in his
//! > surroundings, and goes about his normal morning routine.
//! >  
//! > Let $p$ be the probability that his morning stroll on this new land is strictly more steps than
//! > the expected number of steps his strolls on the soccer ball took. Find $p$, rounded to seven
//! > significant digits.
//!
//! # First part
//!
//! Initially, I wrote the [RandomWalk](crate::RandomWalk) trait and the [Football](crate::Football)
//! struct to implement the walk on a football. The algorithm seemed to strongly hint that the answer
//! was 20, which interestingly is the number of nodes in the graph.
//!
//! It's actually not difficult to prove this by taking advantage of the symmetry of the graph and
//! down some relations between the various expected values. Let $E_k$ be the expected length of a
//! starting at node $k$ and walking until we first reach node $0$. Then we wish to find $E_0$.
//! Drawing out the graph, we can see that, by symmetry, there are only 5 distinct types of nodes,
//! where each type must have the same value of $E_k$, so let's just use $k$ to denote the node,
//! rather than the specific node (i.e. the equivalance class). Then it's easy to see that the
//! relationships between the $E_k$ are:
//!
//! * $E_0 = E_1 + 1$
//! * $3E_1 = 3 + 2E_2$
//! * $3E_2 = E_1 + E_2 + E_3 + 3$
//! * $3E_3 = E_2 + E_3 + E_4 + 3$
//! * $3E_4 = 2E_3 + E_5 + 3$
//! * $E_5 = E_4 + 1$
//!
//! Starting with $E_4$ we can progressively substitute out the next highest $E_k$ from each
//! equation to ultimately solve for $E_0 = 20$.
//!
//! # Second part
//!
//! Having solved the first half I then created a new struct [KitchenFloor](crate::KitchenFloor)
//! which implements the [RandomWalk](crate::RandomWalk) trait, to stochastically measure how
//! frequently Andy has a walk that is longer than 20 steps. This approach gives an answer of
//! roughly 0.448, but it wasn't converging quickly event with a large number of runs, so even after
//! implementing a multithreaded version to get the total number of runs up to 8 billion, I still
//! needed a different approach.
//!
//! I came up two, both of which are implemented below:
//!
//! 1. Enumerate every possible walk from the possible $3^{20}$ available (3 choices at each
//!    step), then iterate through them (approx. 3.5 billion), counting which ones terminate by
//!    arriving back at the home hexagon on or before the 20th step. This is not too bad
//!    computationally, but 3.5 billion possibilities take several minutes to calculate on a fast
//!    machine.
//! 2. Build the graph in-memory, and iterate through the 20 steps, tracking how many paths have
//!    arrived at each node on the $n$th step by summing surrounding nodes from the previous step.
//!    This is certainly the smartest and most computationally efficient solution, yielding a
//!    solution in c.1 millisecond on the same machine.
//!
//! Both approaches above gave the same answer, which also matched the stochastic estimate of
//! 0.448. So the solution is
//!
//! $$p = 0.4480326 \text{ (7 s.f.)}$$

use rand::{
    distributions::{Distribution, Uniform},
    Rng,
};
use std::collections::HashMap;

/// Implement random walks on a state machine.
pub trait RandomWalk {
    /// The representation of state in this state machine.
    type State: PartialEq + Clone;

    /// Make a random move on the internal state machine.
    fn make_move<R: Rng>(&mut self, rng: &mut R);

    /// Return the current state of the machine.
    fn get_state(&self) -> Self::State;

    /// Set the state of the internal state machine.
    fn set_state(&mut self, state: Self::State);

    /// Perform a random walk, starting at `src`, and making random moves until the `tgt` state is
    /// reached. This does not terminate at zero steps if `src` and `tgt` are the same, a move is
    /// always made first before continuing until `tgt`.
    ///
    /// Returns the number of steps it took. This method could block forever if the state
    /// diverges somehow and never arrives at `tgt`. See also `walk_until_limit`.
    fn walk<R: Rng>(&mut self, src: Self::State, tgt: Self::State, rng: &mut R) -> u32 {
        self.set_state(src);

        self.make_move(rng);
        let mut cnt = 1u32;

        while self.get_state() != tgt {
            self.make_move(rng);
            cnt += 1;
        }
        cnt
    }

    /// Same as `walk_until`, but also takes a `limit` parameter, specifying the maximum length of
    /// the walk we should allow before bailing out. Returns `Ok(num_steps)` if `tgt` is reached at or
    /// before the limit, and `Err(limit)` otherwise.
    ///
    /// # Panics
    ///
    /// Panics if `limit` is zero.
    fn walk_until_limit<R: Rng>(
        &mut self,
        src: Self::State,
        tgt: Self::State,
        rng: &mut R,
        limit: u32,
    ) -> Result<u32, u32> {
        if limit == 0 {
            panic!("limit should be > 0");
        }
        self.set_state(src);

        self.make_move(rng);
        let mut cnt = 1u32;

        while self.get_state() != tgt && cnt < limit {
            self.make_move(rng);
            cnt += 1;
        }

        if cnt == limit {
            if self.get_state() == tgt {
                Ok(limit)
            } else {
                Err(limit)
            }
        } else {
            Ok(cnt)
        }
    }
}

/// A representation of the football Andy the Ant lives on.
///
/// A football is a [truncated icosahedron](https://en.wikipedia.org/wiki/Truncated_icosahedron).
/// Andy lives on the hexagons, of which there are 20. The pentagon-centered stereographic projection
/// from the Wikipedia page is useful to look at to see the graph structure of Andy's universe (he
/// lives on the yellow faces in that diagram).
///
/// In order to model his universe, we can simply use integers from 0 to 20 for each possible face,
/// and define the available transitions manually.
pub struct Football {
    curr: i32,
    transitions: HashMap<i32, [i32; 3]>,
    dist: Uniform<usize>,
}

impl RandomWalk for Football {
    type State = i32;

    fn make_move<R: Rng>(&mut self, rng: &mut R) {
        let possibles = self.transitions.get(&self.curr).unwrap();
        let random_idx = self.dist.sample(rng);
        self.curr = *possibles.get(random_idx).unwrap();
    }

    fn get_state(&self) -> Self::State {
        self.curr
    }

    fn set_state(&mut self, state: Self::State) {
        self.curr = state;
    }
}

impl Football {
    /// Create a football.
    pub fn new() -> Self {
        // Generated by randomly labelling the hexagons on the stereographic projection and manually
        // hardcoding the transition matrix. It would be interesting to think about ways to
        // programmatically generate things like this, but for now, this is quicker.
        let transitions: HashMap<i32, [i32; 3]> = HashMap::from([
            (1, [2, 6, 5]),
            (2, [1, 7, 3]),
            (3, [4, 8, 2]),
            (4, [3, 9, 5]),
            (5, [4, 10, 1]),
            (6, [1, 11, 12]),
            (7, [2, 12, 13]),
            (8, [3, 13, 14]),
            (9, [4, 14, 15]),
            (10, [5, 11, 15]),
            (11, [6, 10, 20]),
            (12, [6, 7, 16]),
            (13, [7, 8, 17]),
            (14, [8, 9, 18]),
            (15, [9, 10, 19]),
            (16, [12, 17, 20]),
            (17, [13, 16, 18]),
            (18, [14, 17, 19]),
            (19, [15, 18, 20]),
            (20, [11, 16, 19]),
        ]);

        Self {
            transitions,
            curr: 1,
            dist: Uniform::from(0..3),
        }
    }
}

/// An implementation of the infinite hexagonally tiled kitchen floor Andy unwittingly found
/// himself walking around on this morning.
///
/// To implement this, we can use a coordinate system (x, y), where each hexagon, including the
/// black ones, has a coordinate assigned.
///
/// There are two distinct types of white hexagon, which we can characterise by the compass
/// directions we can move to adjacent white hexagons:
///  A. (NW, SW, E)
///  B. (W, NE, SE)
///
///  We can map the co-ordinates of any given white hexagon to ascertain its type by:
///  ```
///  hex_type: bool = (y % 3) == (3 - x) % 3
///  ```
///
///  The available moves in each case are:
///  * `hex_type == 0`: `(x, y) -> [(x+1, y+1), (x, y-1), (x-1, y)]`
///  * `hex_type == 1`: `(x, y) -> [(x, y+1), (x-1, y-1), (x+1, y)]`
pub struct KitchenFloor {
    coords: (i32, i32),
}

impl KitchenFloor {
    /// Create a new kitchen floor.
    fn new() -> Self {
        Self { coords: (0, 0) }
    }

    fn coord_hex_type(coord: (i32, i32)) -> bool {
        let x = coord.0;
        let y = coord.1;
        y.rem_euclid(3) == (3 - x).rem_euclid(3)
    }

    fn coord_neighbours(coord: (i32, i32)) -> [(i32, i32); 3] {
        if Self::coord_hex_type(coord) {
            [
                (coord.0 + 1, coord.1 + 1),
                (coord.0, coord.1 - 1),
                (coord.0 - 1, coord.1),
            ]
        } else {
            [
                (coord.0, coord.1 + 1),
                (coord.0 + 1, coord.1),
                (coord.0 - 1, coord.1 - 1),
            ]
        }
    }

    /// For simplicity, we use a boolean to encode the two types of hexagon we could be on.
    fn hex_type(&self) -> bool {
        Self::coord_hex_type(self.coords)
    }
    fn neighbours(&self) -> [(i32, i32); 3] {
        Self::coord_neighbours(self.coords)
    }

    fn move_from_idx(&mut self, idx: usize) {
        if self.hex_type() {
            match idx {
                0 => {
                    self.coords.0 += 1;
                    self.coords.1 += 1
                }
                1 => self.coords.1 -= 1,
                2 => self.coords.0 -= 1,
                _ => unreachable!(),
            };
        } else {
            match idx {
                0 => self.coords.1 += 1,
                1 => {
                    self.coords.0 -= 1;
                    self.coords.1 -= 1;
                }
                2 => self.coords.0 += 1,
                _ => unreachable!(),
            }
        }
    }
}

impl RandomWalk for KitchenFloor {
    type State = (i32, i32);

    fn make_move<R: Rng>(&mut self, _rng: &mut R) {
        let random_idx = fastrand::usize(..3);
        self.move_from_idx(random_idx);
    }

    fn get_state(&self) -> Self::State {
        self.coords
    }

    fn set_state(&mut self, state: Self::State) {
        self.coords = state;
    }
}

/// A struct to calculate the expected length of a random walk, for any type `T: RandomWalk`. We
/// will use this to calculate the expected values of walks on our [Football](crate::Football) and
/// [KitchenFloor](crate::KitchenFloor) types.
pub struct Expectation<T: RandomWalk> {
    /// The model of our random walk.
    walker: T,

    /// A map from walk lengths to the frequency of occurences of walks of that length.
    pub freq_map: HashMap<u32, u32>,

    /// Count of the number of runs executed so far.
    pub cnt: u32,
}

impl<T: RandomWalk> Expectation<T> {
    /// Create a new expectation calculator.
    pub fn new(walker: T) -> Self {
        Self {
            walker,
            freq_map: HashMap::new(),
            cnt: 0,
        }
    }

    /// Run the expectation computation. Internally, this calls `walker.walk` which does not take a
    /// limit cut-off for walk lengths. Therefore, this function could take a long time if walks
    /// can be extremely long or even diverege to infinity.
    pub fn calculate(&mut self, src: T::State, tgt: T::State, runs: u32) -> f32 {
        let mut rng = rand::thread_rng();
        while self.cnt < runs {
            let steps = self.walker.walk(src.clone(), tgt.clone(), &mut rng);
            *self.freq_map.entry(steps).or_insert(0) += 1;
            self.cnt += 1;
        }

        self.finish()
    }

    /// Same as [calculate](Expectation::calculate) but takes a `limit` argument which is passed to
    /// [RandomWalk::walk_until_limit](RandomWalk::walk_until_limit) in order to ensure the function
    /// terminates, ideally in a reasonable time.
    pub fn calculate_with_limit(
        &mut self,
        src: T::State,
        tgt: T::State,
        runs: u32,
        limit: u32,
    ) -> f32 {
        let mut rng = rand::thread_rng();
        while self.cnt < runs {
            let steps =
                match self
                    .walker
                    .walk_until_limit(src.clone(), tgt.clone(), &mut rng, limit)
                {
                    Ok(t) => t,
                    Err(t) => t,
                };
            *self.freq_map.entry(steps).or_insert(0) += 1;
            self.cnt += 1;
        }

        self.finish()
    }

    fn finish(&self) -> f32 {
        self.freq_map
            .iter()
            .fold(0, |acc, (walk_length, frequency)| {
                acc + walk_length * frequency
            }) as f32
            / self.cnt as f32
    }
}

/// Run this to get the answer to the first part of the question.
///
/// Interestingly, switching to [calculate_with_limit](Expectation::calculate_with_limit) and running
/// this with a walk-length limit even as high as 80 or 90 seems to
/// give the wrong picture. The expected value appears to vary significantly even as we
/// continue increasing the walk length limit to high values, so very long walks are
/// contributing to the result (it is a fat-tailed distribution). This is corroborated when we print
/// out the frequency map and see that we are getting plenty of walks which are several hundred steps
/// long.
///
/// Using the [calculate](Expectation::calculate) method which imposes no limitation on walk-lengths, but takes longer
/// to run, we seem to immediately be converging on an expected walk-length of 20 (which is of
/// course the total number of nodes in the graph). This is an interesting result, and one we later
/// proved rigorously (see [crate-level documentation](./index.html#first-part)).
pub fn expected_walk_length_on_football() {
    let football = Football::new();
    let mut exp = Expectation::new(football);
    let runs = 100_000_000;
    let exp_walk_length = exp.calculate(1, 1, runs);
    println!("E(length of walk to return home): {}", exp_walk_length);
    println!("cnt: {}", exp.cnt);
    for (k, v) in &exp.freq_map {
        println!("{}: {}", k, v);
    }
}

/// Find the probability that the random walk on the kitchen floor is strictly more than 20 steps.
///
/// This is the first attempt to solve the second part of the question. We discovered that 20 steps
/// is the expected length of a walk on the football, so we want want to know what proportion of
/// walks on the kitchen floor last longer than 20 steps.
///
/// We will perform a large number of runs, counting how many reach 21 steps without
/// terminating.
///
/// With increasing runs, we seem to be converging towards about 0.448.
pub fn prob_of_longer_walk_in_the_kitchen() -> (u64, u64) {
    let mut kitchen_floor = KitchenFloor::new();
    let mut rng = rand::thread_rng();
    let runs: u64 = 10_000_000;
    let progress_unit = runs / 20;
    let mut progress_cnt = 0;
    let mut progress = 0;
    let mut cnt: u64 = 0;
    let mut longer_walk_cnt: u64 = 0;
    while cnt < runs {
        match kitchen_floor.walk_until_limit((0, 0), (0, 0), &mut rng, 20) {
            Ok(_) => {
                // We terminated on or before the 20th step. So this does not contribute to our count
                // of longer walks.
            }
            Err(_) => {
                // We had not terminated by the 20th step, so this does contribute to our count of
                // longer walks.
                longer_walk_cnt += 1;
            }
        };
        cnt += 1;
        progress_cnt += 1;
        if progress_cnt == progress_unit {
            progress += 1;
            println!(
                "{:2}% complete, {} runs, current prob: {}",
                progress as f32 * 5 as f32 as f32,
                cnt,
                longer_walk_cnt as f64 / cnt as f64
            );
            progress_cnt = 0;
        }
    }
    println!("runs longer than 20: {}", longer_walk_cnt);
    println!("total runs: {}", runs);
    println!(
        "probability of a longer than 20 walk: {}",
        longer_walk_cnt as f64 / runs as f64
    );
    (longer_walk_cnt, runs)
}

/// Multithreaded version of [prob_of_longer_walk_in_the_kitchen](prob_of_longer_walk_in_the_kitchen).
///
/// I've got a computer with lots of cpus, and running a monte carlo with indpendent trials is
/// silly to do single-threaded. So we can split this across a bunch of threads to do more
/// trials.
///
/// Running this with about 1 billion iterations per threads over 8 threads, we get to about an
/// estimate of our probability that the random walk is longer than 20 steps of: ~0.448
pub fn multithreaded() {
    let cpus = 8;
    let idx = 0..cpus;
    let mut join_handles: Vec<std::thread::JoinHandle<(u64, u64)>> = Vec::with_capacity(cpus);
    for _ in idx {
        join_handles.push(std::thread::spawn(|| prob_of_longer_walk_in_the_kitchen()));
    }
    let mut results: Vec<(u64, u64)> = Vec::with_capacity(cpus);
    join_handles
        .into_iter()
        .for_each(|jh| results.push(jh.join().unwrap()));

    let grand_total: (u64, u64) = results
        .iter()
        .fold((0, 0), |acc, e| (acc.0 + e.0, acc.1 + e.1));
    println!("grand total: {:?}", grand_total);
    println!(
        "probability of a longer than 20 walk: {}",
        grand_total.0 as f64 / grand_total.1 as f64
    );
}

/// A new approach to part 2. Enumerating every possible walk.
///
/// Our montecarlo approach doesn't seem to be converging on the correct answer fast enough for us
/// to believe in the 7 decimal places we need to get this right. However, we are only looking for
/// the number of terminating walks of less than 20 steps. We have a choice of three directions at
/// each step, so perhaps we can just enumerate every one of the $2^{30}$ = c. 3.5 billion possible
/// paths and get a precise answer.
///
/// Running this function (it's only single threaded so takes a few minutes - it could be made faster by
/// splitting the enumerable range across a few CPUs, as we have done elsewhere) we get:
///
/// $$
/// P(\text{random walk is longer than 20 steps}) = 0.4480326 \text{ (7 s.f.)}
/// $$
///
/// This figure closely matches the answer we were getting stochastically, so presume we have got
/// everything right.
pub fn enumerate_every_walk() {
    let max: usize = 3_usize.pow(20) - 1;
    let progress_unit = max / 20;
    let mut progress_cnt = 0;
    let mut progress = 0;

    // Count the walks which terminate in our first 20 steps.
    let mut terminated_cnt: usize = 0;

    let mut kitchen_floor = KitchenFloor::new();
    let mut decisions = Decisions::new();

    for i in 0..max {
        decisions.inc();
        kitchen_floor.set_state((0, 0));

        for dec in decisions.curr() {
            kitchen_floor.move_from_idx(*dec);
            if kitchen_floor.get_state() == (0, 0) {
                terminated_cnt += 1;
                break;
            }
        }

        progress_cnt += 1;

        if progress_cnt == progress_unit {
            progress += 1;
            println!(
                "{:2}% complete, {} runs",
                progress as f32 * 5 as f32 as f32,
                i,
            );
            progress_cnt = 0;
        }
    }

    println!("terminated walks (<= 20 steps): {}", terminated_cnt);
    println!(
        "non-terminated walks (> 20 steps): {}",
        max - terminated_cnt
    );
    println!(
        "probability > 20: {:10}",
        (max as f64 - terminated_cnt as f64) / max as f64
    );
}

/// A helper struct to assist with iterating through the possible choices of path.
///
/// We are essentially counting in base 3. For simplicity, we're using an array of 20 `usize`
/// integers.
///
/// To be cleaner, this should probably implement `Iterator` from the standard library.
pub struct Decisions {
    curr: [usize; 20],
}

impl Decisions {
    /// Create a new decision iterator.
    pub fn new() -> Self {
        Self { curr: [0; 20] }
    }

    /// Return the current value of the array representation of our path.
    pub fn curr(&self) -> &[usize; 20] {
        &self.curr
    }

    /// Increment to the next path.
    pub fn inc(&mut self) {
        for idx in 0..20 {
            if self.curr[idx] == 2 {
                self.curr[idx] = 0;
            } else {
                self.curr[idx] += 1;
                // As soon as we increment something, rather than reseting, we bail.
                return;
            }
        }
    }
}

/// A representation of a coordinate on our [KitchenFloor](KitchenFloor) plane.
pub type Coord = (i32, i32);

/// Stores a representation of the underlying graph, tracking how many paths have reached each node
/// at current time step `self.step`.
///
/// Calling [next](Self::next) steps the [KitchenFloor](KitchenFloor) steps the graph
/// representation forward by:
/// a) introducing any new nodes to the graph which haven't don't already exist from previous steps
/// b) iterating every node in the graph and setting its new value to the be the sum of the values
///    in the surrounding nodes from the previous step (but not counting any contribution from the
///    origin, because any paths which reached this on the previous step would have terminated
///    there).
pub struct GraphPathCounter {
    /// The kitchen floor model.
    kf: KitchenFloor,

    /// Tracks the total number of paths which can arrive at a given coord by a certain time step.
    pub cells: std::cell::RefCell<HashMap<Coord, usize>>,

    /// Tracks which time step we are currently at.
    step: usize,
}

impl GraphPathCounter {
    /// Create a new graph counter.
    pub fn new() -> Self {
        let counter = Self {
            kf: KitchenFloor::new(),
            cells: std::cell::RefCell::new(HashMap::new()),
            step: 0,
        };

        counter.cells.borrow_mut().entry((0, 0)).or_insert(1);
        counter
    }

    /// Step the internal graph representation forward.
    ///
    /// In summary, this function does works by:
    /// a) introducing any new nodes to the graph which haven't don't already exist from previous steps
    /// b) iterating every node in the graph and setting its new value to the be the sum of the values
    ///    in the surrounding nodes from the previous step (but not counting any contribution from the
    ///    origin, because any paths which reached this on the previous step would have terminated
    ///    there).
    pub fn next(&mut self) {
        self.step += 1;

        let cells: Vec<Coord> = self.cells.borrow().iter().map(|(c, _)| c.clone()).collect();
        for cell in cells {
            let neighbours = KitchenFloor::coord_neighbours(cell);

            for n in neighbours.iter() {
                // Ensure that the neighbour actually has an entry in the table.
                let _cell = self.cells.borrow_mut().entry(*n).or_insert(0);
            }
        }

        let mut new_values = Vec::new();

        // Now, we iterate over everything that appears in the table so far, and add to the counts
        // of each cell the sum of the counts of its neighbouring cells.
        for (cell, _) in self.cells.borrow().iter() {
            let mut new_cnt = 0;
            let cell_neighbours = KitchenFloor::coord_neighbours(*cell);

            for n in cell_neighbours.iter() {
                if *n != (0, 0) || self.step == 1 {
                    match self.cells.borrow().get(n) {
                        Some(n_cnt) => new_cnt += *n_cnt,
                        None => {}
                    };
                }
            }

            new_values.push((*cell, new_cnt));
        }

        for (cell, cnt) in new_values {
            *self.cells.borrow_mut().entry(cell).or_insert(0) = cnt;
        }
    }

    /// Run the analysis for a given number of steps.
    ///
    /// This function tracks how many paths return to the origin (0, 0) in total across all the
    /// steps. We need to be careful to upscale each such number by a factor of $3^k$ where $k$ is
    /// the number of remaining steps. This accounts for the fact that we stop counting the paths
    /// once they have returned home. If we kept counting them each one would diverge into
    /// $3^k$ paths over the remaining $k$ steps. We need to apportion the probability mass
    /// correctly in order to divide by $3^{20}$ total paths at the end.
    pub fn calculate(&mut self, steps: u32) {
        let start = std::time::Instant::now();
        let mut returning_paths = 0;
        let mut returned_paths = 0;
        for i in 0..steps {
            self.next();
            let returned_paths_at_step = self.cells.borrow().get(&(0, 0)).unwrap().clone();

            returned_paths += returned_paths_at_step * 3_usize.pow(steps - i - 1);
            returning_paths += returned_paths_at_step;
        }
        let returning_paths_at_final_step = self.cells.borrow().get(&(0, 0)).unwrap().clone();
        println!(
            "Number of returning paths on the {}th step: {}",
            steps, returning_paths_at_final_step
        );
        println!(
            "Number of returning paths within {} steps: {:?}",
            steps, returning_paths
        );

        let total_paths_at_final_step = self
            .cells
            .borrow()
            .iter()
            .fold(0, |acc, (_, cnt)| acc + cnt);
        println!("Total paths at final step: {}", total_paths_at_final_step);

        let total_paths =
            total_paths_at_final_step + returning_paths - self.cells.borrow().get(&(0, 0)).unwrap();
        println!("Total paths: {}", total_paths);

        println!(
            "Total paths inc. {}",
            returned_paths + total_paths_at_final_step - returning_paths_at_final_step
        );
        let max_paths = 3_usize.pow(steps);
        println!("3^{}: {}", steps, max_paths);

        println!(
            "p = {} / {} = {:7}",
            max_paths - returned_paths,
            max_paths,
            (max_paths - returned_paths) as f64 / max_paths as f64
        );
        println!("took {}ms", start.elapsed().as_micros());
    }
}

/// The most efficient way to calculate the solution to the second part of the question.
///
/// Running this we retrieve the final answer of $p = 0.4480326$ in c. 1ms.
pub fn path_counting_on_graph() {
    let mut counter = GraphPathCounter::new();
    counter.calculate(20);
}

#[cfg(test)]
mod tests {
    use crate::KitchenFloor;

    #[test]
    fn kitchen_floor_traversal() {
        #[rustfmt::skip]
    assert_eq!(KitchenFloor::coord_neighbours((0, 0)), [(1, 1), (0, -1), (-1, 0)]);

        #[rustfmt::skip]
    assert_eq!(KitchenFloor::coord_neighbours((-1, 1)), [(0, 2), (-1, 0), (-2, 1)]);

        #[rustfmt::skip]
    assert_eq!(KitchenFloor::coord_neighbours((-1, 0)), [(-1, 1), (0, 0), (-2, -1)]);

        #[rustfmt::skip]
    assert_eq!(KitchenFloor::coord_neighbours((-2, -1)), [(-1, 0), (-2, -2), (-3, -1)]);
    }
}