The document discusses using iterator combinators in Rust to solve an Advent of Code puzzle in a more functional style compared to a classic imperative approach. It shows code to parse input data into batches, map lines to values, sum the values, and take the maximum. It then extends this to sort batches descending and take the top 3 sums to solve part 2 of the puzzle in a flexible way using combinators.
5. Always re-imagining
We are a pioneering technology consultancy focused
on AWS and serverless
| |
Accelerated Serverless AI as a Service Platform Modernisation
loige
âReach out to us at
đWe are always looking for talent:
hello@fourTheorem.com
fth.link/careers
5
7. Why am I learning Rust đŠ
Full-stack Web Developer turning Cloud Architect
loige
Experience with dynamic scripting languages (JavaScript, Python, Php)
Looked into Go for performance-sensitive applications
Rust seemed like the natural next step
... and it's fun!
7
8. How it is going
loige
error[E0382]: borrow of moved value: `v`
--> src/main.rs:6:19
|
4 | let v = vec![2, 3, 5, 7, 11, 13, 17];
| - move occurs because `v` has type `Vec<i32>`, whic
5 | hold_my_vec(v);
| - value moved here
6 | let element = v.get(3);
| ^^^^^^^^ value borrowed here after move
|
1
2
3
4
5
6
7
8
9
10
#[stable(feature = "future_poll_fn", since = "1.64.0")]
impl<T, F> Future for PollFn<F>
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
{
type Output = T;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> P
// SAFETY: We are not moving out of the pinned field
(unsafe { &mut self.get_unchecked_mut().f })(cx)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
How it started
8
9. How it started -> How it is going
loige
2019: Ported some JS code to Rust (resulted in )
jwtinfo
2020-2022: Live streamed solving Advent of Code in Rust
+ some other small side-projects and coding challenges
Nothing "production ready" as of today đ€·
Excited about Serverless Rust on AWS
9
18. loige
Valve NV has flow rate=5; tunnels lead to valves ZV, CG, YB, HX, OY
Valve NU has flow rate=6; tunnels lead to valves DA, MA, OA, DK
Valve VU has flow rate=0; tunnels lead to valves PS, FX
Valve JW has flow rate=0; tunnels lead to valves AA, MD
Valve RI has flow rate=0; tunnels lead to valves OY, DG
Valve DG has flow rate=9; tunnels lead to valves TG, RI, DF, EV, KW
Valve PH has flow rate=7; tunnels lead to valves KW, OW, LT, LZ
Valve KZ has flow rate=12; tunnels lead to valves ET, QV, CK, MS
Valve IX has flow rate=0; tunnels lead to valves TS, DO
Valve MS has flow rate=0; tunnels lead to valves LZ, KZ
Valve IL has flow rate=0; tunnels lead to valves DO, ET
Valve EJ has flow rate=20; tunnels lead to valves AV, JY
Valve DK has flow rate=0; tunnels lead to valves NU, CG
Valve YB has flow rate=0; tunnels lead to valves NV, PS
Valve OA has flow rate=0; tunnels lead to valves YA, NU
Valve DA has flow rate=0; tunnels lead to valves NU, RG
Valve KO has flow rate=0; tunnels lead to valves AA, TG
Valve RG has flow rate=4; tunnels lead to valves DF, DA, ZV, MD, LB
Valve MA has flow rate=0; tunnels lead to valves AA, NU
Valve OW has flow rate=0; tunnels lead to valves DO, PH
Valve KW has flow rate=0; tunnels lead to valves DG, PH
Valve DO has flow rate=14; tunnels lead to valves IX, IL, CZ, OW
Valve DF has flow rate=0; tunnels lead to valves RG, DG
Valve TG has flow rate=0; tunnels lead to valves DG, KO
Valve LB has flow rate=0; tunnels lead to valves RG, FX
Valve HX has flow rate=0; tunnels lead to valves AA, NV
Valve GB has flow rate=0; tunnels lead to valves AV, XK
Valve CG has flow rate=0; tunnels lead to valves DK, NV
Valve LT has flow rate=0; tunnels lead to valves AO, PH
Valve FX has flow rate=23; tunnels lead to valves LB, HY, VU
Valve ET has flow rate=0; tunnels lead to valves IL, KZ
Valve CK has flow rate=0; tunnels lead to valves UX, KZ
Valve LZ has flow rate=0; tunnels lead to valves PH, MS
Valve YA has flow rate=17; tunnels lead to valves JY, OA
Valve TS has flow rate=0; tunnels lead to valves NO, IX
Valve NO has flow rate=8; tunnel leads to valve TS
Valve XK has flow rate=24; tunnel leads to valve GB
1. Read & understand the puzzle
2. Parse the input
3. Write some code
4. Find solution
5. Submit your solution (unlocks part 2)
6. Repeat from point 1 for part 2
18
21. fn classic(input: &str) -> u64 {
let mut max = 0;
let batches = input.split("nn");
for batch in batches {
let lines = batch.lines();
let mut total = 0;
for line in lines {
let value = line.parse::<u64>().unwrap();
total += value;
}
if total > max {
max = total;
}
}
max
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let mut max = 0;
max
fn classic(input: &str) -> u64 {
1
2
let batches = input.split("nn");
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
}
10
if total > max {
11
max = total;
12
}
13
}
14
15
}
16
let batches = input.split("nn");
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
}
10
if total > max {
11
max = total;
12
}
13
}
14
max
15
}
16
for batch in batches {
}
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
}
10
if total > max {
11
max = total;
12
}
13
14
max
15
}
16
let lines = batch.lines();
let mut total = 0;
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
for batch in batches {
4
5
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
}
10
if total > max {
11
max = total;
12
}
13
}
14
max
15
}
16
for line in lines {
}
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
10
if total > max {
11
max = total;
12
}
13
}
14
max
15
}
16
let value = line.parse::<u64>().unwrap();
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
8
total += value;
9
}
10
if total > max {
11
max = total;
12
}
13
}
14
max
15
}
16
total += value;
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
9
}
10
if total > max {
11
max = total;
12
}
13
}
14
max
15
}
16
if total > max {
max = total;
}
fn classic(input: &str) -> u64 {
1
let mut max = 0;
2
let batches = input.split("nn");
3
for batch in batches {
4
let lines = batch.lines();
5
let mut total = 0;
6
for line in lines {
7
let value = line.parse::<u64>().unwrap();
8
total += value;
9
}
10
11
12
13
}
14
max
15
}
16
fn classic(input: &str) -> u64 {
let mut max = 0;
let batches = input.split("nn");
for batch in batches {
let lines = batch.lines();
let mut total = 0;
for line in lines {
let value = line.parse::<u64>().unwrap();
total += value;
}
if total > max {
max = total;
}
}
max
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Classic noob approach
loige
21
44. loige
100
22
44
1
120
top3 = [120,100,44]
This is O(n*m) vs sorting O(n*logn) âĄ
* indulge me and let's ignore we could have used radix sort here...
** O(n) for very small m, top100 would be expensive!
đĄIDEA:
Consume the iterator and, as you go,
keep the current top 3 in a vec...
44
45. trait TopN {
fn top_n(self, n: usize) -> Vec<u64>;
}
1
2
3
loige
That's what we want for now,
but can it be more "generic"? đ§
45
46. trait TopN<T> {
fn top_n(self, n: usize) -> Vec<T>;
}
1
2
3
loige
Makes the trait "generic"
over any type T
T is used as part of the
return type of the function
46
47. That's a cool trait indeed... but I am
sure we cannot implement it for "all"
iterators, right?! đ€·
loige 47
48. Of course, we can! đȘ
loige
It's called a "blanket implementation".
48
49. impl<T, U: Iterator<Item = T>> TopN<T> for U {
fn top_n(self, n: usize) -> Vec<T> {
}
}
1
2
// TODO:
3
unimplemented!();
4
5
6
loige
All types U that implements
the Iterator trait producing
Items of type T
We implement TopN<T> for all
types U (iterators producing T)!
49
50. impl<T, U: Iterator<Item = T>> TopN<T> for U {
fn top_n(self, n: usize) -> Vec<T> {
let mut top = Vec::with_capacity(n);
for value in self {
for i in 0..n {
if let Some(top_value) = top.get(i) {
if value > *top_value {
top[i..].rotate_right(1);
top[i] = value;
break;
}
} else {
top.push(value);
break;
}
}
}
top
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let mut top = Vec::with_capacity(n);
impl<T, U: Iterator<Item = T>> TopN<T> for U {
1
fn top_n(self, n: usize) -> Vec<T> {
2
3
for value in self {
4
for i in 0..n {
5
if let Some(top_value) = top.get(i) {
6
if value > *top_value {
7
top[i..].rotate_right(1);
8
top[i] = value;
9
break;
10
}
11
} else {
12
top.push(value);
13
break;
14
}
15
}
16
}
17
top
18
}
19
}
20
if value > *top_value {
impl<T, U: Iterator<Item = T>> TopN<T> for U {
1
fn top_n(self, n: usize) -> Vec<T> {
2
let mut top = Vec::with_capacity(n);
3
for value in self {
4
for i in 0..n {
5
if let Some(top_value) = top.get(i) {
6
7
top[i..].rotate_right(1);
8
top[i] = value;
9
break;
10
}
11
} else {
12
top.push(value);
13
break;
14
}
15
}
16
}
17
top
18
}
19
}
20 loige
50
51. if value > *top_value {
impl<T, U: Iterator<Item = T>> TopN<T> for U {
1
fn top_n(self, n: usize) -> Vec<T> {
2
let mut top = Vec::with_capacity(n);
3
for value in self {
4
for i in 0..n {
5
if let Some(top_value) = top.get(i) {
6
7
top[i..].rotate_right(1);
8
top[i] = value;
9
break;
10
}
11
} else {
12
top.push(value);
13
break;
14
}
15
}
16
}
17
top
18
}
19
}
20 loige
51
52. impl<T: PartialOrd, U: Iterator<Item = T>> TopN<T> for U {
1
fn top_n(self, n: usize) -> Vec<T> {
2
let mut top = Vec::with_capacity(n);
3
for value in self {
4
for i in 0..n {
5
if let Some(top_value) = top.get(i) {
6
if value > *top_value {
7
top[i..].rotate_right(1);
8
top[i] = value;
9
break;
10
}
11
} else {
12
top.push(value);
13
break;
14
}
15
}
16
}
17
top
18
}
19
}
20 loige
Restricting T only to types that
can be compared!
đ 52
62. impl<T: Default + Copy + PartialOrd, U: Iterator<Item = T>> Top<T> for
let mut top = [Default::default(); N];
trait Top<T> {
1
fn top<const N: usize>(self) -> [T; N];
2
}
3
4
5
fn top<const N: usize>(self) -> [T; N] {
6
7
// ...
8
top
9
}
10
}
11
loige
Do we really need this?
62
63. impl<T: Default + PartialOrd, U: Iterator<Item = T>> Top<T> for U {
let mut top = core::array::from_fn(|_| Default::default());
trait Top<T> {
1
fn top<const N: usize>(self) -> [T; N];
2
}
3
4
5
fn top<const N: usize>(self) -> [T; N] {
6
7
// ...
8
top
9
}
10
}
11
loige
Allows us to avoid Copy
63
64. impl<T: Default + PartialOrd, U: Iterator<Item = T>> Top<T> for U {
let mut top = core::array::from_fn(|_| Default::default());
trait Top<T> {
1
fn top<const N: usize>(self) -> [T; N];
2
}
3
4
5
fn top<const N: usize>(self) -> [T; N] {
6
7
// ...
8
top
9
}
10
}
11
loige
Note: This works for now, but it's not a perfect solution đ„
(e.g. what if there are fewer than N items?)
64
67. Which one do you think is faster? đ§
loige
Itertools
no_sort_vec
no_sort_array
1.
2.
3.
67
68. Which one do you think is faster? đ§
loige
Itertools
no_sort_vec
no_sort_array
1.
2.
3.
37.517 ”s
33.284 ”s
32.957 ”s
BTW, is awesome!
cargo bench
â
68
69. But let's talk more about
const generics, what else can you do
with them?
loige 69
71. let p5d = Point([1, 2, 3, 4, 5]);
fn some_func() {
1
// ...
2
3
// ...
4
}
5
y2020 - Day 17: Conway Cubes
loige
No need to specify the type, Rust will infer Point<5>
based on the length of the passed array!
71
87. let mask: Instr = "mask = 00000000000000X1001X".try_into().unwrap();
let mem: Instr = "mem[42] = 100".try_into().unwrap();
1
assert_eq!(mask, Instr::Mask("00000000000000X1001X"));
2
3
4
assert_eq!(mem, Instr::Mem(42, 100));
5
loige
Implementing the TryFrom trait, will make the
try_into() method available!
87
88. Sensor at x=2, y=18: closest beacon is at x=-2, y=15
Sensor at x=9, y=16: closest beacon is at x=10, y=16
Sensor at x=13, y=2: closest beacon is at x=15, y=3
Sensor at x=12, y=14: closest beacon is at x=10, y=16
...
y2022 - Day 15: Beacon Exclusion Zone
loige
struct Pos {
x: i64,
y: i64,
}
let sensor = Pos {
x: 13,
y: 2,
}
let beacon = Pos {
x: 15,
y: 3,
}
88
101. Which one do you think is faster? đ§
loige
regex
regex_lazy
nom
1.
2.
3.
101
102. Which one do you think is faster? đ§
loige
regex
regex_lazy
nom
1.
2.
3.
102
103. Which one do you think is faster? đ§
loige
regex
regex_lazy
nom
1.
2.
3.
3063.9 ”s
9.3 ”s
1.8 ”s
â
+170117%
+417%
103
104. The Iterator & the FromIterator traits
Destructuring with if let Some(x) / while let Some(x)...
Powerful pattern-matching syntax and the matches!() macro
Newtype pattern + Deref & DerefMut trait
The Display & Debug traits
unreachable!() & todo!() macros
defaultdict a-la-python using HashMap and the entry API
Copy On Write (CoW)
Helper methods of the Result and the Option types
I would have also loved to tell you about... đ€Ż
104
105. Started with a very imperative style and ended up with a more
functional style
"if you look back at your code from 2 years ago and you are not
ashamed you are not growing"
Noticed that rust has been improving a lot in these 3 years
Often needed helpers but they were in nightly (Vec::retain)
Rust Analyser & Clippy have been suggesting new
improvements over time!
Rust is fantastic for Advent of Code!
đŠIn conclusion...
105
107. Huge thanks to , ,
Cover photo by on
@gbinside @AlleviTommaso @giufus
Redaviqui Davilli Unsplash
TNX
loige
nodejsdp.link
Sorry, I have a contractual obligation to put this here đ
107