Day 1: Not Quite Lisp
Rust solution to AoC|2015|1.
Advent of Code begins ;)
Star 1
A simple fold
1
2
3
4
pub fn star_1(data: &&str) -> SolType1 {
data.bytes()
.fold(0, |f, b| if b == b'(' { f + 1 } else { f - 1 })
}
Star 2
Since we need intermediate results, the fold is replaced by a scan.
1
2
3
4
5
6
7
8
9
10
pub fn star_2(data: &&str) -> SolType2 {
data.bytes()
.scan(0isize, |f, b| {
*f = if b == b'(' { *f + 1 } else { *f - 1 };
Some(*f)
})
.position(|f| f < 0)
.unwrap()
+ 1
}
Day 2: I Was Told There Would Be No Math
Rust solution to AoC|2015|2.
How much paper and ribbon is required to nicely wrap all gifts?
Input
Parse input in a vec of tuples containing width, length and height of each present.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pub mod input {
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<(SolType, SolType, SolType)>);
impl From<&str> for PuzzleData {
/// parse the puzzle input
fn from(s: &str) -> Self {
Self(
s.lines()
.map(|l| l.split('x').map(|v| v.parse().unwrap()))
.map(|mut it| (it.next().unwrap(), it.next().unwrap(), it.next().unwrap()))
.collect(),
)
}
}
}
Star 1
First iter-map-sum
1
2
3
4
5
6
pub fn star_1(PuzzleData(boxes): &PuzzleData) -> SolType {
boxes
.iter()
.map(|&(l, w, h)| 2 * l * w + 2 * w * h + 2 * h * l + (l * w).min(w * h).min(h * l))
.sum()
}
Star 2
Second iter-map-sum
1
2
3
4
5
6
pub fn star_2(PuzzleData(boxes): &PuzzleData) -> SolType {
boxes
.iter()
.map(|&(l, w, h)| l * w * h + 2 * (l + w).min(w + h).min(h + l))
.sum()
}
Day 3: Perfectly Spherical Houses in a Vacuum
Rust solution to AoC|2015|3.
Navigate to the houses that receive gifts.
Star 1
Save the coordinates of the houses seen so far in a HashSet
and return the number of elements in that set.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn star_1(dirs: &&str) -> SolType1 {
let mut houses: HashSet<(isize, isize)> = HashSet::from([(0, 0)]);
let mut pos = (0, 0);
for dir in dirs.bytes() {
pos = match dir {
b'>' => (pos.0 + 1, pos.1),
b'^' => (pos.0, pos.1 + 1),
b'<' => (pos.0 - 1, pos.1),
b'v' => (pos.0, pos.1 - 1),
_ => panic!(),
};
houses.insert(pos);
}
houses.len()
}
Star 2
The same as the first part. This time, alternatingly update two positions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn star_2(dirs: &&str) -> SolType2 {
let mut houses: HashSet<(isize, isize)> = HashSet::from([(0, 0)]);
let mut pos = [(0, 0), (0, 0)];
for (k, dir) in dirs.bytes().enumerate() {
pos[k & 1] = match dir {
b'>' => (pos[k & 1].0 + 1, pos[k & 1].1),
b'^' => (pos[k & 1].0, pos[k & 1].1 + 1),
b'<' => (pos[k & 1].0 - 1, pos[k & 1].1),
b'v' => (pos[k & 1].0, pos[k & 1].1 - 1),
_ => panic!(),
};
houses.insert(pos[k & 1]);
}
houses.len()
}
Day 4: The Ideal Stocking Stuffer
Rust solution to AoC|2015|4.
Mine AdventCoins using MD5 hashes
Star 1
Calculate MD5 hashes until one is found whose first 5 half-bytes are 0.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pub fn solve<F: Fn(&[u8]) -> bool>(data: &[u8], k0: usize, f: F) -> SolType {
let mut hash = [0; 16];
let mut hasher = Md5::new();
(k0..)
.find(|&k| {
hasher.update(data);
hasher.update(k.to_string().as_bytes());
hasher.finalize_into_reset((&mut hash).into());
f(&hash)
})
.unwrap()
}
const F1: fn(&[u8]) -> bool = |hash| hash[0] == 0 && hash[1] == 0 && hash[2] >> 4 == 0;
pub fn star_1(data: &&str) -> SolType {
solve(data.trim().as_bytes(), 0, F1)
}
Star 2
Calculate MD5 hashes until one is found whose first 6 half-bytes are 0.
1
2
3
4
5
const F2: fn(&[u8]) -> bool = |hash| hash[0] == 0 && hash[1] == 0 && hash[2] == 0;
pub fn star_2(data: &&str) -> SolType {
solve(data.trim().as_bytes(), 0, F2)
}
Tests
1
2
3
4
5
6
7
8
9
10
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_solve() {
assert_eq!(609_043, solve("abcdef".as_bytes(), 609_043, F1));
assert_eq!(1_048_970, solve("pqrstuv".as_bytes(), 1_048_970, F1));
}
}
Day 5: Doesn’t He Have Intern-Elves For This?
Rust solution to AoC|2015|5.
Which strings are nice, which are naughty?
Star 1
A little bit if iterator magic to determine whether strings are nice.
1
2
3
4
5
6
7
8
9
10
11
pub fn is_nice_1(l: &[u8]) -> bool {
l.iter().filter(|b| b"aeiou".contains(b)).count() >= 3
&& l.iter().zip(l[1..].iter()).all(|p| {
p != (&b'a', &b'b') && p != (&b'c', &b'd') && p != (&b'p', &b'q') && p != (&b'x', &b'y')
})
&& l.iter().zip(l[1..].iter()).any(|(a, b)| a == b)
}
pub fn star_1(input: &&str) -> SolType {
input.lines().filter(|l| is_nice_1(l.as_bytes())).count()
}
Star 2
Some more iterator magic.
1
2
3
4
5
6
7
8
pub fn is_nice_2(l: &[u8]) -> bool {
(0..l.len() - 3).any(|k1| (k1 + 2..l.len() - 1).any(|k2| l[k1..k1 + 2] == l[k2..k2 + 2]))
&& (0..l.len() - 2).any(|k| l[k] == l[k + 2])
}
pub fn star_2(input: &&str) -> SolType {
input.lines().filter(|l| is_nice_2(l.as_bytes())).count()
}
Day 6: Probably a Fire Hazard
Rust solution to AoC|2015|6.
Who has the nicest lights?
Star 1
Count lights on.
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
pub fn range(range: &str) -> impl Iterator<Item = (usize, usize)> {
let (f, t) = range.split_once(" through ").unwrap();
let (f_x, f_y) = f
.split_once(',')
.map(|(x, y)| (x.parse().unwrap(), y.parse().unwrap()))
.unwrap();
let (t_x, t_y) = t
.split_once(',')
.map(|(x, y)| (x.parse().unwrap(), y.parse().unwrap()))
.unwrap();
(f_x..=t_x).flat_map(move |x| (f_y..=t_y).map(move |y| (x, y)))
}
const WIDTH: usize = 1_000;
const PRE_ON: &str = "turn on ";
const PRE_OFF: &str = "turn off ";
const PRE_TOGGLE: &str = "toggle ";
pub fn star_1(instructions: &&str) -> SolType {
let mut lights = vec![false; WIDTH * WIDTH];
let mut count = 0;
for l in instructions.lines() {
if let Some(stripped) = l.strip_prefix(PRE_ON) {
for (x, y) in range(stripped) {
if !lights[x + WIDTH * y] {
count += 1;
lights[x + WIDTH * y] = true;
}
}
} else if let Some(stripped) = l.strip_prefix(PRE_OFF) {
for (x, y) in range(stripped) {
if lights[x + WIDTH * y] {
count -= 1;
lights[x + WIDTH * y] = false;
}
}
} else if let Some(stripped) = l.strip_prefix(PRE_TOGGLE) {
for (x, y) in range(stripped) {
(count, lights[x + WIDTH * y]) = if lights[x + WIDTH * y] {
(count - 1, false)
} else {
(count + 1, true)
};
}
}
}
count
}
Star 2
Sum up total brightness
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
pub fn star_2(instructions: &&str) -> SolType {
let mut lights = vec![0; WIDTH * WIDTH];
let mut bright = 0;
for l in instructions.lines() {
if let Some(stripped) = l.strip_prefix(PRE_ON) {
for (x, y) in range(stripped) {
bright += 1;
lights[x + WIDTH * y] += 1;
}
} else if let Some(stripped) = l.strip_prefix(PRE_OFF) {
for (x, y) in range(stripped) {
if lights[x + WIDTH * y] > 0 {
bright -= 1;
lights[x + WIDTH * y] -= 1;
}
}
} else if let Some(stripped) = l.strip_prefix(PRE_TOGGLE) {
for (x, y) in range(stripped) {
bright += 2;
lights[x + WIDTH * y] += 2;
}
}
}
bright
}
Day 7: Some Assembly Required
Rust solution to AoC|2015|7.
Emulate circuitry…
Input
The input is parsed using two enums Gate
and Source
. The latter can be either a wire or a value, the first represents all kind of logic gates described in the puzzle. The gates are put in a map with the output wire name as key.
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
pub mod input {
use std::collections::HashMap;
use crate::{Gate, Source};
#[derive(Debug)]
pub struct PuzzleData<'a>(pub HashMap<&'a str, Gate<'a>>);
impl<'a> From<&'a str> for Source<'a> {
fn from(s: &'a str) -> Self {
match s.as_bytes()[0] {
b'0'..=b'9' => Self::Value(s.parse().unwrap()),
_ => Self::Wire(s),
}
}
}
impl<'a> From<&'a str> for Gate<'a> {
fn from(s: &'a str) -> Self {
let mut words = s.split_ascii_whitespace();
match (words.next(), words.next(), words.next()) {
(Some(lhs), Some("LSHIFT"), Some(rhs)) => Self::LShift(lhs.into(), rhs.into()),
(Some(lhs), Some("RSHIFT"), Some(rhs)) => Self::RShift(lhs.into(), rhs.into()),
(Some(lhs), Some("AND"), Some(rhs)) => Self::And(lhs.into(), rhs.into()),
(Some(lhs), Some("OR"), Some(rhs)) => Self::Or(lhs.into(), rhs.into()),
(Some("NOT"), Some(rhs), None) => Self::Not(rhs.into()),
(Some(v), None, None) => Self::Value(v.into()),
_ => panic!(),
}
}
}
impl<'a> From<&'a str> for PuzzleData<'a> {
fn from(s: &'a str) -> Self {
Self(
s.lines()
.map(|l| l.split_once(" -> ").unwrap())
.map(|(gate, tar)| (tar, Gate::from(gate)))
.collect(),
)
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Source<'a> {
Value(ValType),
Wire(&'a str),
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Gate<'a> {
Value(Source<'a>),
LShift(Source<'a>, Source<'a>),
RShift(Source<'a>, Source<'a>),
And(Source<'a>, Source<'a>),
Or(Source<'a>, Source<'a>),
Not(Source<'a>),
}
Star 1 & 2
Both enums, Gate
and Source
implement a get function which takes the map of all gates and a cache as values. The cache is used to break cycles which would potentially lead to endless loops.
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
impl<'a> Source<'a> {
pub fn get(
&self,
gates: &HashMap<&'a str, Gate<'a>>,
cache: &mut HashMap<&'a str, ValType>,
) -> ValType {
match *self {
Source::Value(value) => value,
Source::Wire(wire) => match cache.get(wire).copied() {
Some(value) => value,
_ => {
let value = gates[wire].get(gates, cache);
cache.insert(wire, value);
value
}
},
}
}
}
impl<'a> Gate<'a> {
pub fn get(
&self,
gates: &HashMap<&'a str, Gate<'a>>,
cache: &mut HashMap<&'a str, ValType>,
) -> ValType {
match self {
Gate::Value(val) => val.get(gates, cache),
Gate::LShift(lhs, rhs) => lhs.get(gates, cache) << rhs.get(gates, cache),
Gate::RShift(lhs, rhs) => lhs.get(gates, cache) >> rhs.get(gates, cache),
Gate::And(lhs, rhs) => lhs.get(gates, cache) & rhs.get(gates, cache),
Gate::Or(lhs, rhs) => lhs.get(gates, cache) | rhs.get(gates, cache),
Gate::Not(rhs) => !rhs.get(gates, cache),
}
}
}
pub fn star_1_2(PuzzleData(gates): &PuzzleData) -> SolType {
let sol1 = Source::Wire("a").get(gates, &mut HashMap::new());
let mut gates = gates.to_owned();
gates.insert("b", Gate::Value(Source::Value(sol1)));
let sol2 = Source::Wire("a").get(&gates, &mut HashMap::new());
(sol1, sol2).into()
}
Day 8: Matchsticks
Rust solution to AoC|2015|8.
Evaluate difference between string representation in code and in memory.
Star 1
Find occurrences of '\\', '\"', '\x__', and '"' and reduce len accordingly.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pub fn star_1(data: &&str) -> SolType {
data.lines()
.map(|l| l.as_bytes())
.map(|d| {
let (mut k, mut len) = (0, 0);
while k < d.len() {
(k, len) = match (d[k], d.get(k + 1)) {
(b'"', _) => (k + 1, len + 1),
(b'\\', Some(b'x')) => (k + 4, len + 3),
(b'\\', _) => (k + 2, len + 1),
_ => (k + 1, len),
};
}
len
})
.sum()
}
Star 2
This is actually simpler than part 1. We only need to count occurrences of '\' and '"' and add two for leading and trailing '"'.
1
2
3
4
5
pub fn star_2(data: &&str) -> SolType {
data.lines()
.map(|l| l.bytes().filter(|&b| b == b'"' || b == b'\\').count() + 2)
.sum()
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"""
"abc"
"aaa\"aaa"
"\x27"
"#;
#[test]
pub fn test_star_1() {
assert_eq!(12, star_1(&CONTENT));
}
#[test]
pub fn test_star_2() {
assert_eq!(19, star_2(&CONTENT));
}
}
Day 9: All in a Single Night
Rust solution to AoC|2015|9.
Help Santa with routing…
Input
Parse input in a flat matrix.
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
pub mod input {
use std::collections::HashMap;
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<SolType>, pub usize);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
let mut ids = HashMap::new();
let mut temp = Vec::new();
for (n1, n2, dist) in s.lines().map(|l| {
let mut words = l.split_ascii_whitespace();
(
words.next().unwrap(),
words.nth(1).unwrap(),
words.nth(1).unwrap().parse().unwrap(),
)
}) {
let len = ids.len();
let id1 = *ids.entry(n1).or_insert(len);
let len = ids.len();
let id2 = *ids.entry(n2).or_insert(len);
temp.push((id1, id2, dist));
}
let n = ids.len();
let dists = temp.iter().fold(
vec![0; ids.len() * ids.len()],
|mut dists, &(id1, id2, dist)| {
dists[id1 + n * id2] = dist;
dists[id2 + n * id1] = dist;
dists
},
);
Self(dists, n)
}
}
}
Star 1
Minimize over all permutations (using mr-kaffee-utils::permutations::Permutations
).
1
2
3
4
5
6
7
8
9
10
11
pub fn star_1(PuzzleData(dists, n): &PuzzleData) -> SolType {
Permutations::from(0..*n)
.map(|p| {
p.iter()
.zip(p[1..].iter())
.map(|(id1, id2)| dists[id1 + n * id2])
.sum()
})
.min()
.unwrap()
}
Star 2
Maximize over all permutations (using mr-kaffee-utils::permutations::Permutations
).
1
2
3
4
5
6
7
8
9
10
11
pub fn star_2(PuzzleData(dists, n): &PuzzleData) -> SolType {
Permutations::from(0..*n)
.map(|p| {
p.iter()
.zip(p[1..].iter())
.map(|(id1, id2)| dists[id1 + n * id2])
.sum()
})
.max()
.unwrap()
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"London to Dublin = 464
London to Belfast = 518
Dublin to Belfast = 141
"#;
#[test]
pub fn test_from() {
let PuzzleData(dists, n) = PuzzleData::from(CONTENT);
assert_eq!(vec![0, 464, 518, 464, 0, 141, 518, 141, 0], dists);
assert_eq!(3, n);
}
#[test]
pub fn test_star_1() {
assert_eq!(605, star_1(&CONTENT.into()));
}
#[test]
pub fn test_star_2() {
assert_eq!(982, star_2(&CONTENT.into()));
}
}
Day 10: Elves Look, Elves Say
Rust solution to AoC|2015|10.
Simulate elvish games
Input
Don’t forget to trim whitespace from the input string.
1
2
3
4
5
6
7
8
9
10
pub mod input {
#[derive(Debug)]
pub struct PuzzleData(pub Vec<u8>);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(s.trim().bytes().map(|b| b - b'0').collect())
}
}
}
Star 1
Simulate loops.
Note that there will never be more than three elements of the same number in a row (four times the same number, e.g., 1111, can only result from larger groups of that same number, which would then become a single group, e.g, 21.)
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
pub fn update(data: &[u8], upd: &mut Vec<u8>) {
upd.truncate(0);
let mut k = 0;
while k < data.len() {
let a = data[k];
let mut len = 1;
while k + len < data.len() && data[k + len] == a {
len += 1;
}
k += len;
upd.push(len as _);
upd.push(a);
}
}
pub fn star_1(PuzzleData(data): &PuzzleData) -> SolType {
let mut data = data.to_owned();
let mut upd = Vec::with_capacity(data.len());
for _ in 0..40 {
update(&data, &mut upd);
(data, upd) = (upd, data);
}
data.len()
}
Star 2
Simulate more loops.
1
2
3
4
5
6
7
8
9
pub fn star_2(PuzzleData(data): &PuzzleData) -> SolType {
let mut data = data.to_owned();
let mut upd = Vec::with_capacity(data.len());
for _ in 0..50 {
update(&data, &mut upd);
(data, upd) = (upd, data);
}
data.len()
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_update() {
let mut upd = Vec::new();
println!("1");
update(&[1], &mut upd);
assert_eq!(vec![1, 1], upd);
println!("2");
update(&[1, 1], &mut upd);
assert_eq!(vec![2, 1], upd);
println!("3");
update(&[2, 1], &mut upd);
assert_eq!(vec![1, 2, 1, 1], upd);
println!("4");
update(&[1, 2, 1, 1], &mut upd);
assert_eq!(vec![1, 1, 1, 2, 2, 1], upd);
println!("5");
update(&[1, 1, 1, 2, 2, 1], &mut upd);
assert_eq!(vec![3, 1, 2, 2, 1, 1], upd);
println!("Loop");
let mut data = vec![1];
let mut upd = Vec::new();
for _ in 0..5 {
update(&data, &mut upd);
(data, upd) = (upd, data);
}
assert_eq!(vec![3, 1, 2, 2, 1, 1], data);
}
}
Day 11: Corporate Policy
Rust solution to AoC|2015|11.
Generate next valid passwords.
Star 1 & 2
Increment passwords until a valid one is found (twice)
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
pub fn next(a: &mut [u8]) {
for k in (0..a.len()).rev() {
if a[k] < b'z' {
a[k] += 1;
break;
} else {
assert!(k > 0);
a[k] = b'a';
}
}
}
pub fn is_valid(a: &[u8]) -> bool {
match (0..5).find(|&k| a[k] == a[k + 1]) {
Some(k0) => {
(k0 + 2..7).any(|k| a[k] == a[k + 1] && a[k] != a[k0])
&& (0..6).any(|k| a[k] + 1 == a[k + 1] && a[k] + 2 == a[k + 2])
&& a.iter().all(|&b| b != b'i' && b != b'l' && b != b'o')
}
_ => false,
}
}
pub fn star_1_2(pwd: &&str) -> Solutions<String, String> {
let mut a = pwd.trim().bytes().collect::<Vec<_>>();
next(&mut a);
while !is_valid(&a) {
next(&mut a);
}
let sol1 = a.iter().map(|&b| b as char).collect();
next(&mut a);
while !is_valid(&a) {
next(&mut a);
}
let sol2 = a.iter().map(|&b| b as char).collect();
(sol1, sol2).into()
}
Day 12: JSAbacusFramework.io
Rust solution to AoC|2015|12.
JSON parsing.
Input
This is the actual work (if you do not just use an existing JSON parser)
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
mod input {
use std::collections::HashMap;
use crate::SolType;
#[derive(Debug, PartialEq, Eq)]
pub enum Element {
Number(SolType),
String(String),
Array(Vec<Element>),
Object(HashMap<String, Box<Element>>),
}
fn parse_rec(data: &[u8]) -> (Element, usize) {
match data.first().copied() {
Some(b'"') => {
let s = (1..)
.take_while(|&k| k < data.len() && data[k] != b'"')
.map(|k| data[k] as char)
.collect::<String>();
let len = s.len() + 2;
(Element::String(s), len)
}
Some(b'[') => {
let mut p = 1;
let mut values = Vec::new();
while data[p] != b']' {
let (value, len) = parse_rec(&data[p..]);
p += len;
if data[p] == b',' {
p += 1;
}
values.push(value);
}
(Element::Array(values), p + 1)
}
Some(b'{') => {
let mut p = 1;
let mut map = HashMap::new();
while data[p] != b'}' {
let (key, len) = parse_rec(&data[p..]);
let Element::String(key) = key else { panic!() };
p += len + 1;
let (value, len) = parse_rec(&data[p..]);
p += len;
if data[p] == b',' {
p += 1;
}
map.insert(key, Box::new(value));
}
(Element::Object(map), p + 1)
}
Some(a) => {
let (sig, val) = match a {
b'-' => (-1, 0),
b'+' => (1, 0),
_ => (1, (a - b'0') as SolType),
};
let (val, len) = (1..)
.take_while(|&k| k < data.len() && data[k] >= b'0' && data[k] <= b'9')
.fold((val, 1), |(val, len), k| {
(10 * val + (data[k] - b'0') as SolType, len + 1)
});
(Element::Number(sig * val), len)
}
_ => panic!(),
}
}
impl<T: AsRef<[u8]>> From<T> for Element {
fn from(value: T) -> Self {
parse_rec(value.as_ref()).0
}
}
}
Star 1
Sum all the numbers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pub fn sum_numbers(element: &Element, include_red: bool) -> SolType {
match element {
Element::Number(v) => *v,
Element::Array(vec) => vec.iter().map(|e| sum_numbers(e, include_red)).sum(),
Element::Object(map)
if include_red
|| map
.values()
.all(|e| e.as_ref() != &Element::String("red".to_string())) =>
{
map.values()
.map(|e| sum_numbers(e.as_ref(), include_red))
.sum()
}
_ => 0,
}
}
pub fn star_1(element: &Element) -> SolType {
sum_numbers(element, true)
}
Star 2
Sum all the numbers that are not in red objects.
1
2
3
pub fn star_2(element: &Element) -> SolType {
sum_numbers(element, false)
}
Tests
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
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
#[test]
pub fn test_star_1() {
assert_eq!(6, star_1(&r#"[1,2,3]"#.into()));
assert_eq!(6, star_1(&r#"{"a":2,"b":4}"#.into()));
assert_eq!(3, star_1(&r#"[[[3]]]"#.into()));
assert_eq!(0, star_1(&r#"{"a":[-1,1]}"#.into()));
assert_eq!(0, star_1(&r#"[-1,{"a":1}]"#.into()));
assert_eq!(0, star_1(&r#"[]"#.into()));
assert_eq!(0, star_1(&r#"{}"#.into()));
}
#[test]
pub fn test_element_from() {
assert_eq!(
Element::Array(vec![
Element::Number(1),
Element::Number(2),
Element::Number(3)
]),
Element::from(r#"[1,2,3]"#)
);
assert_eq!(
Element::Object(HashMap::from([
("a".to_string(), Element::Number(2).into()),
("b".to_string(), Element::Number(4).into())
])),
Element::from(r#"{"a":2,"b":4}"#)
);
assert_eq!(
Element::Array(vec![Element::Array(vec![Element::Array(vec![
Element::Number(3)
])])]),
Element::from(r#"[[[3]]]"#)
);
assert_eq!(
Element::Object(HashMap::from([(
"a".to_string(),
Element::Array(vec![Element::Number(-1), Element::Number(1)]).into()
)])),
Element::from(r#"{"a":[-1,1]}"#)
);
}
}
Day 13: Knights of the Dinner Table
Rust solution to AoC|2015|13.
Seating arrangements for a happy Christmas.
Input
The input is parsed in a matrix of scores.
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
pub mod input {
use std::collections::HashMap;
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData {
pub len: usize,
pub scores: Vec<SolType>,
}
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
let mut ids = HashMap::new();
let temp = s
.lines()
.map(|l| {
let mut words = l.split_ascii_whitespace();
let len = ids.len();
let n1 = *ids.entry(words.next().unwrap()).or_insert(len);
let v = match (
words.nth(1).unwrap(),
words.next().unwrap().parse::<SolType>().unwrap(),
) {
("gain", v) => v,
(_, v) => -v,
};
let len = ids.len();
let n2 = *ids
.entry(words.nth(6).unwrap().trim_end_matches('.'))
.or_insert(len);
((n1, n2), v)
})
.collect::<Vec<_>>();
let len = ids.len();
let mut scores = vec![0; len * len];
for ((n1, n2), score) in temp {
scores[n1 + len * n2] = score;
}
Self {
len: ids.len(),
scores,
}
}
}
}
Star 1
The solution uses mr_kaffee_util::Permutations
to iterate over all possible seating orders.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pub fn solve(len: usize, scores: &[SolType]) -> SolType {
Permutations::from(0..len)
.map(|seating| {
(0..len)
.map(|k| {
scores[seating[k] + len * seating[(k + 1) % len]]
+ scores[seating[k] + len * seating[(k + len - 1) % len]]
})
.sum()
})
.max()
.unwrap()
}
pub fn star_1(PuzzleData { len, scores }: &PuzzleData) -> SolType {
solve(*len, scores)
}
Star 2
The solution is extended by an additional person not with 0 scores towards and from all other persons. The solution itself is unchanged.
1
2
3
4
5
6
7
pub fn star_2(PuzzleData { len, scores }: &PuzzleData) -> SolType {
let mut temp = vec![0; (len + 1) * (len + 1)];
for k in 0..*len {
temp[k * (len + 1)..k * (len + 1) + len].copy_from_slice(&scores[k * len..k * len + len]);
}
solve(len + 1, &temp)
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"Alice would gain 54 happiness units by sitting next to Bob.
Alice would lose 79 happiness units by sitting next to Carol.
Alice would lose 2 happiness units by sitting next to David.
Bob would gain 83 happiness units by sitting next to Alice.
Bob would lose 7 happiness units by sitting next to Carol.
Bob would lose 63 happiness units by sitting next to David.
Carol would lose 62 happiness units by sitting next to Alice.
Carol would gain 60 happiness units by sitting next to Bob.
Carol would gain 55 happiness units by sitting next to David.
David would gain 46 happiness units by sitting next to Alice.
David would lose 7 happiness units by sitting next to Bob.
David would gain 41 happiness units by sitting next to Carol.
"#;
#[test]
pub fn test_from() {
let PuzzleData { len, scores } = PuzzleData::from(CONTENT);
println!("{}, {:?}", len, scores);
assert_eq!(4, len);
}
#[test]
pub fn test_star_1() {
assert_eq!(330, star_1(&CONTENT.into()));
}
}
Day 14: Reindeer Olympics
Rust solution to AoC|2015|14.
Input
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
pub mod input {
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Reindeer<'a> {
pub name: &'a str,
pub speed: usize,
pub flying_time: usize,
pub resting_time: usize,
}
impl<'a> From<&'a str> for Reindeer<'a> {
/// # Examples
/// ```
/// # use mr_kaffee_2015_14::input::Reindeer;
/// let reindeer = Reindeer::from("Rudolph can fly 100 km/s for 11 seconds, but then must rest for 29 seconds.");
/// assert_eq!(Reindeer { name: "Rudolph", speed: 100, flying_time: 11, resting_time: 29 }, reindeer);
/// ```
fn from(s: &'a str) -> Self {
let mut words = s.split(' ');
let name = words.next().unwrap();
let mut words = words.skip(2); // can fly
let speed = words.next().unwrap().parse().unwrap();
let mut words = words.skip(2); // km/s for
let flying_time = words.next().unwrap().parse().unwrap();
let mut words = words.skip(6); // seconds, but then must rest for
let resting_time = words.next().unwrap().parse().unwrap();
Self {
name,
speed,
flying_time,
resting_time,
}
}
}
#[derive(PartialEq, Eq, Debug)]
pub struct PuzzleData<'a> {
pub reindeers: Vec<Reindeer<'a>>,
}
impl<'a> From<&'a str> for PuzzleData<'a> {
/// parse the puzzle input
fn from(s: &'a str) -> Self {
Self {
reindeers: s.lines().map(Reindeer::from).collect(),
}
}
}
}
Reindeer
For a reindeer, I am interested in the increment it makes at a given time and the total distance traveled until a given time
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
impl Reindeer<'_> {
/// distance increment travel in second t
///
/// # Examples
/// ```
/// # use mr_kaffee_2015_14::input::Reindeer;
/// let speed = 100;
/// let flying_time = 11;
/// let resting_time = 29;
/// let reindeer = Reindeer { name: "Rudolph", speed, flying_time, resting_time };
///
/// assert_eq!(speed, reindeer.inc(0));
/// assert_eq!(speed, reindeer.inc(flying_time - 1));
/// assert_eq!(0, reindeer.inc(flying_time));
/// assert_eq!(0, reindeer.inc(flying_time + resting_time - 1));
/// assert_eq!(speed, reindeer.inc(flying_time + resting_time));
/// ```
pub fn inc(&self, t: usize) -> usize {
if t % (self.flying_time + self.resting_time) < self.flying_time {
self.speed
} else {
0
}
}
/// distance traveled until after second t
///
/// # Examples
/// ```
/// # use mr_kaffee_2015_14::input::Reindeer;
/// let speed = 100;
/// let flying_time = 11;
/// let resting_time = 29;
/// let reindeer = Reindeer { name: "Rudolph", speed, flying_time, resting_time };
///
/// assert_eq!(flying_time * speed, reindeer.distance(flying_time + resting_time));
///
/// let mut distance = 0;
/// for t in 0..100 {
/// distance += reindeer.inc(t);
/// assert_eq!(distance, reindeer.distance(t + 1));
/// }
/// ```
pub fn distance(&self, t: usize) -> usize {
t / (self.flying_time + self.resting_time) * self.flying_time * self.speed
+ (t % (self.flying_time + self.resting_time)).min(self.flying_time) * self.speed
}
}
Star 1
Find the reindeer which traveled the largest distance within T = 2503s
using Reindeer::distance
.
1
2
3
4
5
6
7
8
9
pub const T: usize = 2503;
pub fn star_1(data: &PuzzleData) -> usize {
data.reindeers
.iter()
.map(|reindeer| reindeer.distance(T))
.max()
.unwrap_or(0)
}
Star 2
Calculate scores for all reindeers using Reindeer::inc
and determine max
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
pub fn scores(data: &PuzzleData, t: usize) -> Vec<usize> {
// number of reindeers
let n = data.reindeers.len();
// loop over simulation steps (seconds)
let (scores, _) = (0..t).fold((vec![0; n], vec![0; n]), |(mut s, mut d), t| {
// increment distances (as side effect) and determine max distance
let m = (0..n).fold(0, |m, k| {
d[k] += data.reindeers[k].inc(t);
m.max(d[k])
});
// increment scores for reindeers at max distance
(0..n).filter(|k| d[*k] == m).for_each(|k| s[k] += 1);
// updated scores and distances
(s, d)
});
// return scores
scores
}
/// Calculates max score at `t =` [`T`] using [`scores`]
pub fn star_2(data: &PuzzleData) -> usize {
scores(data, T).into_iter().max().unwrap_or(0)
}
Tests
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
#[cfg(test)]
mod tests {
use mr_kaffee_aoc::err::PuzzleError;
use super::*;
const CONTENT: &str = r#"Comet can fly 14 km/s for 10 seconds, but then must rest for 127 seconds.
Dancer can fly 16 km/s for 11 seconds, but then must rest for 162 seconds."#;
fn exp_data() -> PuzzleData<'static> {
PuzzleData {
reindeers: vec![
Reindeer {
name: "Comet",
speed: 14,
flying_time: 10,
resting_time: 127,
},
Reindeer {
name: "Dancer",
speed: 16,
flying_time: 11,
resting_time: 162,
},
],
}
}
#[test]
pub fn test_parse() -> Result<(), PuzzleError> {
let data = PuzzleData::from(CONTENT);
assert_eq!(exp_data(), data);
Ok(())
}
#[test]
pub fn test_reindeer_distance() {
assert_eq!(
vec![1120, 1056],
exp_data()
.reindeers
.iter()
.map(|r| r.distance(1000))
.collect::<Vec<_>>()
)
}
#[test]
pub fn test_scores() {
assert_eq!(vec![312, 689], scores(&exp_data(), 1000));
}
}
Day 15: Science for Hungry People
Rust solution to AoC|2015|15.
Who creates the best milk-dunking cookies?
Input
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
pub mod input {
use crate::{SolType, PROPS};
#[derive(Debug)]
pub struct PuzzleData(pub Vec<[SolType; PROPS]>);
fn parse_line(l: &str) -> [SolType; PROPS] {
let mut words = l
.split_ascii_whitespace()
.skip(2)
.step_by(2)
.map(|w| w.trim_end_matches(',').parse().unwrap());
[
words.next().unwrap(),
words.next().unwrap(),
words.next().unwrap(),
words.next().unwrap(),
words.next().unwrap(),
]
}
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(s.lines().map(parse_line).collect())
}
}
}
Star 1
A linear search. The tricky (or maybe non-standard) part is the code to update the quantities at the end of each loop.
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
pub const TOTAL: SolType = 100;
pub fn solve(ingredients: &[[SolType; PROPS]], calories: Option<SolType>) -> SolType {
let len = ingredients.len();
let mut q = vec![0; len];
let mut score = 0;
loop {
let s = (0..len - 1).map(|k| q[k]).sum::<SolType>();
q[len - 1] = TOTAL - s;
let sums =
q.iter()
.zip(ingredients.iter())
.fold([0; PROPS], |mut sums, (q, ingredient)| {
for k in 0..PROPS {
sums[k] += q * ingredient[k]
}
sums
});
if calories
.map(|calories| calories == sums[PROPS - 1])
.unwrap_or(true)
{
score = score.max(sums[0..PROPS - 1].iter().map(|&sum| sum.max(0)).product());
}
let mut sum = 0;
for k in (0..len - 1).rev() {
let s = TOTAL - (0..k).map(|k| q[k]).sum::<SolType>();
if q[k] < s {
q[k] += 1;
sum += q[k];
break;
}
q[k] = 0;
}
if sum == 0 {
break;
}
}
score
}
pub fn star_1(PuzzleData(ingredients): &PuzzleData) -> SolType {
solve(ingredients, None)
}
Star 2
Still a linear search. Add filtering for recipes that have the correct number of total calories.
1
2
3
pub fn star_2(PuzzleData(ingredients): &PuzzleData) -> SolType {
solve(ingredients, Some(500))
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"Butterscotch: capacity -1, durability -2, flavor 6, texture 3, calories 8
Cinnamon: capacity 2, durability 3, flavor -2, texture -1, calories 3
"#;
#[test]
pub fn test_from() {
let PuzzleData(data) = PuzzleData::from(CONTENT);
assert_eq!(vec![[-1, -2, 6, 3, 8], [2, 3, -2, -1, 3]], data);
}
#[test]
pub fn test_star_1() {
assert_eq!(62_842_880, star_1(&CONTENT.into()));
}
#[test]
pub fn test_star_2() {
assert_eq!(57_600_000, star_2(&CONTENT.into()));
}
}
Day 16: Aunt Sue
Rust solution to AoC|2015|16.
Is it a good thing to have 500 Aunts named Sue?
Input
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
pub mod input {
use crate::{
Sue, AKITAS, CARS, CATS, CHILDREN, GOLDFISH, PERFUMES, POMERANIANS, SAMOYEDS, TREES,
VIZSLAS,
};
#[derive(Debug)]
pub struct PuzzleData(pub Vec<Sue>);
impl From<&str> for Sue {
fn from(s: &str) -> Self {
let words = s.split_ascii_whitespace().collect::<Vec<_>>();
(0..words.len())
.step_by(2)
.map(|k| (words[k], words[k + 1]))
.map(|(name, value)| (name, value.trim_end_matches([',', ':']).parse().unwrap()))
.fold(Self::default(), |mut sue, (name, value)| {
match name {
"children:" => sue.0[CHILDREN] = Some(value),
"cats:" => sue.0[CATS] = Some(value),
"samoyeds:" => sue.0[SAMOYEDS] = Some(value),
"pomeranians:" => sue.0[POMERANIANS] = Some(value),
"akitas:" => sue.0[AKITAS] = Some(value),
"vizslas:" => sue.0[VIZSLAS] = Some(value),
"goldfish:" => sue.0[GOLDFISH] = Some(value),
"trees:" => sue.0[TREES] = Some(value),
"cars:" => sue.0[CARS] = Some(value),
"perfumes:" => sue.0[PERFUMES] = Some(value),
"Sue" => (),
_ => panic!("Unknown attribute: {}", name),
};
sue
})
}
}
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(s.lines().map(Sue::from).collect())
}
}
}
Star 1
Just find the Sue for which all known information matches.
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
#[derive(Debug, Default)]
pub struct Sue(pub [Option<u32>; 10]);
pub const CHILDREN: usize = 0;
pub const CATS: usize = 1;
pub const SAMOYEDS: usize = 2;
pub const POMERANIANS: usize = 3;
pub const AKITAS: usize = 4;
pub const VIZSLAS: usize = 5;
pub const GOLDFISH: usize = 6;
pub const TREES: usize = 7;
pub const CARS: usize = 8;
pub const PERFUMES: usize = 9;
const GIFT: &str = r#"children: 3
cats: 7
samoyeds: 2
pomeranians: 3
akitas: 0
vizslas: 0
goldfish: 5
trees: 3
cars: 2
perfumes: 1"#;
pub fn star_1(PuzzleData(sues): &PuzzleData) -> SolType {
let gift = Sue::from(GIFT);
sues.iter()
.position(|sue| (0..gift.0.len()).all(|k| sue.0[k].is_none() || sue.0[k] == gift.0[k]))
.unwrap()
+ 1
}
Star 2
The same thing, but with a different meaning of matches.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub const CMP: [Ordering; 10] = [
Ordering::Equal, // children
Ordering::Greater, // cats
Ordering::Equal, // samoyeds
Ordering::Less, // pomeranians
Ordering::Equal, // akitas
Ordering::Equal, // vizslas
Ordering::Less, // goldfish
Ordering::Greater, // trees
Ordering::Equal, // cars
Ordering::Equal, // perfumes
];
pub fn star_2(PuzzleData(sues): &PuzzleData) -> SolType {
let gift = Sue::from(GIFT);
sues.iter()
.position(|sue| {
(0..gift.0.len()).all(|k| sue.0[k].is_none() || sue.0[k].cmp(&gift.0[k]) == CMP[k])
})
.unwrap()
+ 1
}
Day 17: No Such Thing as Too Much
Rust solution to AoC|2015|17.
Fill eggnog in all different ways in containers…
Input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub mod input {
#[derive(Debug)]
pub struct PuzzleData(pub Vec<usize>);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(
s.split_ascii_whitespace()
.map(str::parse)
.collect::<Result<_, _>>()
.unwrap(),
)
}
}
}
Star 1
Recursively find combinations
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const VOLUME: usize = 150;
pub fn count_options(containers: &[usize], volume: usize, n_max: usize) -> SolType {
match (volume, n_max) {
(0, _) => 1,
(_, 0) => 0,
_ => (0..containers.len())
.filter(|&k| containers[k] <= volume)
.map(|k| count_options(&containers[k + 1..], volume - containers[k], n_max - 1))
.sum(),
}
}
pub fn star_1(PuzzleData(containers): &PuzzleData) -> SolType {
count_options(containers, VOLUME, usize::MAX)
}
Star 2
Recursively find minimum number of containers. Then use the recursion from part 1 with a limit on the number of containers.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pub fn min_required(containers: &[usize], volume: usize) -> Option<usize> {
match volume {
0 => Some(0),
_ => (0..containers.len())
.filter(|&k| containers[k] <= volume)
.filter_map(|k| {
min_required(&containers[k + 1..], volume - containers[k]).map(|mn| mn + 1)
})
.min(),
}
}
pub fn star_2(PuzzleData(containers): &PuzzleData) -> SolType {
count_options(
containers,
VOLUME,
min_required(containers, VOLUME).unwrap(),
)
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"20 15 10 5 5"#;
#[test]
pub fn test_count_options() {
let PuzzleData(containers) = CONTENT.into();
assert_eq!(4, count_options(&containers, 25, usize::MAX));
assert_eq!(3, count_options(&containers, 25, 2));
}
#[test]
pub fn test_min_required() {
let PuzzleData(containers) = CONTENT.into();
assert_eq!(Some(2), min_required(&containers, 25));
}
}
Day 18: Like a GIF For Your Yard
Rust solution to AoC|2015|18.
Animate grid of lights.
Input
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub mod input {
#[derive(Debug)]
pub struct PuzzleData(pub Vec<bool>);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(
s.bytes()
.filter(|&b| !(b as char).is_ascii_whitespace())
.map(|b| b == b'#')
.collect(),
)
}
}
}
Star 1
Simulate rounds. Re-use memory to avoid frequent re-allocations.
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
pub const WIDTH: usize = 100;
pub const ROUNDS: usize = 100;
pub const D: [(isize, isize); 8] = [
(1, 0),
(1, -1),
(0, -1),
(-1, -1),
(-1, 0),
(-1, 1),
(0, 1),
(1, 1),
];
pub fn solve<F: FnMut(&mut [bool])>(
grid: &[bool],
width: usize,
rounds: usize,
mut f: F,
) -> SolType {
let mut grid = grid.to_owned();
let mut upd = vec![false; grid.len()];
f(&mut grid);
for _ in 0..rounds {
(0..width * width)
.map(|p| (p % width, p / width))
.map(|(x, y)| {
(
x + width * y,
D.into_iter()
.map(|(dx, dy)| (x.wrapping_add_signed(dx), y.wrapping_add_signed(dy)))
.filter(|&(x, y)| x < width && y < width && grid[x + width * y])
.count(),
)
})
.map(|(p, n)| (p, grid[p] && n == 2 || n == 3))
.for_each(|(p, on)| upd[p] = on);
(grid, upd) = (upd, grid);
f(&mut grid);
}
grid.into_iter().filter(|&on| on).count()
}
pub fn star_1(PuzzleData(grid): &PuzzleData) -> SolType {
solve(grid, WIDTH, ROUNDS, |_| ())
}
Star 2
Add step that turns lights on the corners on initially and after each update step.
1
2
3
4
5
6
7
8
9
10
const NW: usize = 0;
const NE: usize = WIDTH - 1;
const SW: usize = WIDTH * (WIDTH - 1);
const SE: usize = WIDTH * WIDTH - 1;
pub fn star_2(PuzzleData(grid): &PuzzleData) -> SolType {
solve(grid, WIDTH, ROUNDS, |grid| {
(grid[NW], grid[NE], grid[SW], grid[SE]) = (true, true, true, true)
})
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#".#.#.#
...##.
#....#
..#...
#.#..#
####..
"#;
#[test]
pub fn test_solve_1() {
let PuzzleData(grid) = CONTENT.into();
assert_eq!(4, solve(&grid, 6, 4, |_| ()));
}
#[test]
pub fn test_solve_2() {
let PuzzleData(grid) = CONTENT.into();
assert_eq!(
17,
solve(&grid, 6, 5, |grid| (grid[0], grid[5], grid[30], grid[35]) =
(true, true, true, true))
);
}
}
Day 19: Medicine for Rudolph
Rust solution to AoC|2015|19.
Make molecules to create medicine for poor Rudolph.
Input
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
pub mod input {
use std::iter::successors;
pub type Id = u8;
#[derive(Debug, Default)]
pub struct PuzzleData<'a> {
pub ids: Vec<&'a str>,
pub rules: Vec<(Vec<Id>, Id)>,
pub molecule: Vec<Id>,
}
impl<'a> PuzzleData<'a> {
pub const ROOT: &'static str = "e";
fn get_id_and_suffix(&mut self, names: &'a str) -> (Id, &'a str) {
let l = (1..names.len())
.find(|&k| (names.as_bytes()[k] as char).is_ascii_uppercase())
.unwrap_or(names.len());
let id = match self.ids.iter().position(|&name| name == &names[0..l]) {
Some(id) => id as _,
None => {
self.ids.push(&names[0..l]);
(self.ids.len() - 1) as _
}
};
(id, &names[l..])
}
fn get_molecule(&mut self, names: &'a str) -> Vec<Id> {
successors(
Some(self.get_id_and_suffix(names)),
|(_, names)| match names {
&"" => None,
names => Some(self.get_id_and_suffix(names)),
},
)
.map(|(id, _)| id)
.collect()
}
}
impl<'a> From<&'a str> for PuzzleData<'a> {
fn from(s: &'a str) -> Self {
let mut data = PuzzleData::default();
data.ids.push(Self::ROOT);
let (rules, molecule) = s.trim().split_once("\n\n").unwrap();
for (src, tar) in rules.lines().filter_map(|l| l.split_once(" => ")) {
let (src, _) = data.get_id_and_suffix(src);
let tar = data.get_molecule(tar);
data.rules.push((tar, src));
}
data.molecule = data.get_molecule(molecule);
data
}
}
pub fn get_open_comma_close(rules: &[(Vec<Id>, Id)]) -> (Id, Id, Id) {
// rule with six elements is of form "<a>(<b>,<c>)"
rules
.iter()
.find(|(m, _)| m.len() == 6)
.map(|(m, _)| (m[1], m[3], m[5]))
.unwrap()
}
}
Star 1
For part 1, we simple creates all possible molecules.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn star_1(
PuzzleData {
rules, molecule, ..
}: &PuzzleData,
) -> SolType {
rules
.iter()
.flat_map(|(tar, src)| {
(0..molecule.len())
.filter(move |&k| molecule[k] == *src)
.map(move |k| [&molecule[0..k], tar as &[_], &molecule[k + 1..]].concat())
})
.collect::<HashSet<_>>()
.len()
}
Star 2
For part 2, we use the structure of the rules. It can be observed, that there are two types of rules:
-
<s> ⇒ <t1><t2>
-
<s> ⇒ <t1>Rn…Ar
For the 2nd type, there are three sub-types:
-
<s> ⇒ <t1>Rn<t2>Ar
-
<s> ⇒ <t1>Rn<t2>Y<t3>Ar
-
<s> ⇒ <t1>Rn<t2>Y<t3>Y<t4>Ar
The elements Rn
, Y
, and Ar
do never appear on the left side of a rule or as one the <ti>
. We can interpret Rn
as (
, Y
as ,
and Ar
as )
.
With these observations, we do not need to actually construct the molecule but just count the number of Ar
and Y
in the final molecule to determine the number of required steps.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn star_2(
PuzzleData {
rules, molecule, ..
}: &PuzzleData,
) -> SolType {
let (_, comma, close) = get_open_comma_close(rules);
molecule.iter().fold(molecule.len() - 1, |steps, &id| {
if id == comma || id == close {
steps - 2
} else {
steps
}
})
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"H => HO
H => OH
O => HH
HOHOHO
"#;
#[test]
pub fn test_from() {
let PuzzleData {
rules,
molecule,
ids,
} = CONTENT.into();
let h = ids.iter().position(|&e| e == "H").unwrap() as Id;
let o = ids.iter().position(|&e| e == "O").unwrap() as Id;
assert_eq!(
vec![(vec![h, o], h), (vec![o, h], h), (vec![h, h], o)],
rules
);
assert_eq!(vec![h, o, h, o, h, o], molecule);
}
#[test]
pub fn test_star_1() {
assert_eq!(7, star_1(&CONTENT.into()));
}
}
Day 20: Infinite Elves and Infinite Houses
Rust solution to AoC|2015|20.
Lots of presents.
Input
1
2
3
4
5
6
7
8
9
10
11
12
pub mod input {
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData(pub SolType);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(s.trim().parse().unwrap())
}
}
}
Star 1
Simple linear search… Takes a while but works.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub fn star_1(&PuzzleData(presents): &PuzzleData) -> SolType {
let lim = (presents + 9) / 10;
(1..)
.find(|&house| {
(1..)
.step_by((house & 1) + 1)
.take_while(|f| f * f <= house)
.filter(|&f| house % f == 0)
.map(|f| (f, house / f))
.map(|(f1, f2)| if f1 == f2 { f1 } else { f1 + f2 })
.sum::<SolType>()
>= lim
})
.unwrap()
}
Star 2
Another simple linear search… Takes another while but works.
1
2
3
4
5
6
7
8
9
10
11
12
13
pub fn star_2(&PuzzleData(presents): &PuzzleData) -> SolType {
let lim = (presents + 10) / 11;
(1..)
.find(|&house| {
(1..)
.take_while(|&f| f <= 50 && f <= house)
.filter(|&f| house % f == 0)
.map(|f| house / f)
.sum::<SolType>()
>= lim
})
.unwrap()
}
Day 21: RPG Simulator 20XX
Rust solution to AoC|2015|21.
Role playing game…
Input
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
pub mod input {
use crate::HPType;
#[derive(Debug, Default, Clone)]
pub struct Player {
pub hp: HPType,
pub damage: HPType,
pub armor: HPType,
}
impl From<&str> for Player {
fn from(s: &str) -> Self {
s.lines().fold(Self::default(), |mut data, l| {
match l
.split_once(": ")
.map(|(key, value)| (key, value.parse().unwrap()))
{
Some(("Hit Points", hp)) => data.hp = hp,
Some(("Damage", damage)) => data.damage = damage,
Some(("Armor", armor)) => data.armor = armor,
_ => panic!("Unexpected line: {}", l),
}
data
})
}
}
}
Star 1
We actually never need to play. It is enough to figure out for which choice of items we kill the boss in no more rounds as the boss would take to kill us.
I create some helpers to express the options nicely in an iterator. Adding one zero cost / zero effect armor and two zero cost / zero effect rings to the possible items allows to implicitly choose no armor, no ring, or one ring.
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
pub const WEAPONS: [(SolType, (HPType, HPType)); 5] = [
(8, (4, 0)),
(10, (5, 0)),
(25, (6, 0)),
(40, (7, 0)),
(74, (8, 0)),
];
pub const ARMORS: [(SolType, (HPType, HPType)); 6] = [
(0, (0, 0)),
(13, (0, 1)),
(31, (0, 2)),
(53, (0, 3)),
(75, (0, 4)),
(102, (0, 5)),
];
pub const RINGS: [(SolType, (HPType, HPType)); 8] = [
(0, (0, 0)),
(0, (0, 0)),
(20, (0, 1)),
(25, (1, 0)),
(40, (0, 2)),
(50, (2, 0)),
(80, (0, 3)),
(100, (3, 0)),
];
impl Player {
pub fn with_hp(hp: HPType) -> Self {
Self {
hp,
..Default::default()
}
}
pub fn rounds_to_kill(&self, defense: &Player) -> HPType {
let dec = self.damage.max(defense.armor + 1) - defense.armor;
defense.hp.div_ceil(dec)
}
pub fn add_item(mut self, (damage, armor): (HPType, HPType)) -> Self {
self.damage += damage;
self.armor += armor;
self
}
}
pub fn options() -> impl Iterator<Item = (SolType, Player)> {
(0..WEAPONS.len())
.flat_map(|weapon| (0..ARMORS.len()).map(move |armor| (weapon, armor)))
.flat_map(|(weapon, armor)| (0..RINGS.len() - 1).map(move |ring_1| (weapon, armor, ring_1)))
.flat_map(|(weapon, armor, ring_1)| {
(ring_1 + 1..RINGS.len()).map(move |ring_2| (weapon, armor, ring_1, ring_2))
})
.map(|(weapon, armor, ring_1, ring_2)| {
[WEAPONS[weapon], ARMORS[armor], RINGS[ring_1], RINGS[ring_2]]
.iter()
.fold((0, Player::with_hp(100)), |(c, player), &(itm_c, itm)| {
(c + itm_c, player.add_item(itm))
})
})
}
pub fn star_1(boss: &Player) -> SolType {
options()
.filter(|(_, player)| player.rounds_to_kill(boss) <= boss.rounds_to_kill(player))
.map(|(c, _)| c)
.min()
.unwrap()
}
Star 2
Just invert the filter and replace min by max compared to part 1.
1
2
3
4
5
6
7
pub fn star_2(boss: &Player) -> SolType {
options()
.filter(|(_, player)| player.rounds_to_kill(boss) > boss.rounds_to_kill(player))
.map(|(c, _)| c)
.max()
.unwrap()
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_rounds_to_kill() {
let a = Player {
hp: 8,
damage: 5,
armor: 5,
};
let b = Player {
hp: 12,
damage: 7,
armor: 2,
};
assert_eq!(4, a.rounds_to_kill(&b));
assert_eq!(4, b.rounds_to_kill(&a));
}
}
Day 22: Wizard Simulator 20XX
Rust solution to AoC|2015|22.
Role playing game with wizards…
Input
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
pub mod input {
use crate::HPType;
#[derive(Debug, Default, Clone)]
pub struct Boss {
pub hp: HPType,
pub damage: HPType,
}
impl From<&str> for Boss {
fn from(s: &str) -> Self {
s.lines().fold(Self::default(), |mut data, l| {
match l
.split_once(": ")
.map(|(key, value)| (key, value.parse().unwrap()))
{
Some(("Hit Points", hp)) => data.hp = hp,
Some(("Damage", damage)) => data.damage = damage,
_ => panic!("Unexpected line: {}", l),
}
data
})
}
}
}
Star 1
The solution uses backtracking implemented using depth first search with recursive function play_rec
.
To quickly exclude branches, the max. mana is constraint by the best solution found so far.
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
/// (cost, (damage, hit-points, rounds))
pub const CASTS: [(SolType, (HPType, HPType, HPType)); 5] = [
(53, (4, 0, 0)),
(73, (2, 2, 0)),
(113, (0, 0, 6)),
(173, (0, 0, 6)),
(229, (0, 0, 5)),
];
/// (damage, armor, mana)
pub const EFFECTS: [(HPType, HPType, HPType); 5] =
[(0, 0, 0), (0, 0, 0), (0, 7, 0), (3, 0, 0), (0, 0, 101)];
const ATTACK: fn(&mut HPType, HPType) -> bool = |hp: &mut HPType, dec: HPType| {
*hp -= dec.min(*hp);
*hp == 0
};
#[derive(Debug, Clone)]
pub struct Player {
hp: HPType,
mana: HPType,
armor: HPType,
timers: [HPType; CASTS.len()],
}
impl Player {
pub fn new(hp: HPType, mana: HPType) -> Self {
Self {
hp,
mana,
armor: 0,
timers: Default::default(),
}
}
pub fn apply_effects(&mut self, boss: &mut Boss) -> HPType {
self.armor = 0;
for (k, &(damage, armor, mana)) in EFFECTS.iter().enumerate() {
if self.timers[k] > 0 {
self.timers[k] -= 1;
boss.hp -= boss.hp.min(damage);
self.armor += armor;
self.mana += mana;
}
}
boss.hp
}
}
/// backtracking using recursive depth first search
pub fn play_rec(
mut player: Player,
mut boss: Boss,
damage_0: HPType,
mana_max: HPType,
) -> Option<SolType> {
// players turn
if ATTACK(&mut player.hp, damage_0) {
// player loses
return None;
}
// apply effects and decrement timers
if player.apply_effects(&mut boss) == 0 {
// boss loses
return Some(0);
}
// iterate over candidate spells
let mut mana_opt = mana_max;
for (k, &(mana, (damage, hp, timer))) in
CASTS
.iter()
.enumerate()
.filter(|&(k, &(mana, (_, _, timer)))| {
mana <= player.mana && (timer == 0 || player.timers[k] == 0)
})
{
if mana >= mana_opt {
// no improvement
continue;
}
let mut player = player.clone();
let mut boss = boss.clone();
// finish player's round
player.mana -= mana;
player.hp += hp;
player.timers[k] = timer;
// apply damage to boss
if ATTACK(&mut boss.hp, damage) {
// boss loses
mana_opt = mana;
continue;
}
// bosses turn
// apply effects and decrement timers
if player.apply_effects(&mut boss) == 0 {
// boss loses
mana_opt = mana;
continue;
}
// apply damage to player
if ATTACK(
&mut player.hp,
boss.damage.max(player.armor + 1) - player.armor,
) {
// player loses
continue;
}
// recurse to find next spells
if let Some(mana) = play_rec(player, boss, damage_0, mana_opt - mana)
.map(|m| m + mana)
.filter(|&mana| mana < mana_opt)
{
mana_opt = mana;
}
}
if mana_opt < mana_max {
// return lowest mana that allows to win
Some(mana_opt)
} else {
// no winning options found in this branch
None
}
}
pub fn play(player: Player, boss: Boss, turn_damage: HPType) -> SolType {
play_rec(player, boss, turn_damage, HPType::MAX).unwrap()
}
pub fn star_1(boss: &Boss) -> SolType {
play(Player::new(50, 500), boss.clone(), 0)
}
Star 2
The solution for part 2 is a simple extension to part 1. The hit points decrement at the beginning of the player’s turn is passed as an argument.
1
2
3
pub fn star_2(boss: &Boss) -> SolType {
play(Player::new(50, 500), boss.clone(), 1)
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn test_play() {
let player = Player::new(10, 250);
let boss = Boss { hp: 13, damage: 8 };
// poison: 173, magic missile: 53
assert_eq!(173 + 53, play(player, boss, 0));
let player = Player::new(10, 250);
let boss = Boss { hp: 14, damage: 8 };
// recharge: 229, shield: 113, drain: 73, poison: 173, magic missile: 53
assert_eq!(229 + 113 + 73 + 173 + 53, play(player, boss, 0));
}
}
Day 23: Opening the Turing Lock
Rust solution to AoC|2015|23.
Run instructions on registers
Input
Parse the instructions into a vec of enums Instr
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
pub mod input {
use crate::Instr;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<Instr>);
impl From<&str> for Instr {
fn from(l: &str) -> Self {
let mut words = l.split_ascii_whitespace();
let name = words.next().unwrap();
let a = words.next().unwrap();
let b = words.next().map(|b| b.parse().unwrap());
match name {
"hlf" => Self::Hlf((a.as_bytes()[0] - b'a') as _),
"tpl" => Self::Tpl((a.as_bytes()[0] - b'a') as _),
"inc" => Self::Inc((a.as_bytes()[0] - b'a') as _),
"jmp" => Self::Jmp(a.parse().unwrap()),
"jie" => Self::Jie((a.as_bytes()[0] - b'a') as _, b.unwrap()),
"jio" => Self::Jio((a.as_bytes()[0] - b'a') as _, b.unwrap()),
_ => panic!("Unexpected instruction: {}", l),
}
}
}
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
Self(s.lines().map(Instr::from).collect())
}
}
}
Star 1
Well … run the instructions!
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
#[derive(Debug, PartialEq, Eq)]
pub enum Instr {
Hlf(usize),
Tpl(usize),
Inc(usize),
Jmp(isize),
Jie(usize, isize),
Jio(usize, isize),
}
pub fn run(instructions: &[Instr], mut reg: [SolType; 2]) -> [SolType; 2] {
let mut p = 0;
while p < instructions.len() {
p = match instructions[p] {
Instr::Hlf(idx) => {
reg[idx] >>= 1;
p + 1
}
Instr::Tpl(idx) => {
reg[idx] *= 3;
p + 1
}
Instr::Inc(idx) => {
reg[idx] += 1;
p + 1
}
Instr::Jmp(off) => p.wrapping_add_signed(off),
Instr::Jie(idx, off) if reg[idx] & 1 == 0 => p.wrapping_add_signed(off),
Instr::Jio(idx, off) if reg[idx] == 1 => p.wrapping_add_signed(off),
_ => p + 1,
}
}
reg
}
pub fn star_1(PuzzleData(instructions): &PuzzleData) -> SolType {
run(instructions, [0, 0])[1]
}
Star 2
Well … run the instructions - with different initial conditions!
1
2
3
pub fn star_2(PuzzleData(instructions): &PuzzleData) -> SolType {
run(instructions, [1, 0])[1]
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#"inc a
jio a, +2
tpl a
inc a
"#;
#[test]
pub fn test_from() {
let PuzzleData(instructions) = PuzzleData::from(CONTENT);
assert_eq!(
vec![
Instr::Inc(0),
Instr::Jio(0, 2),
Instr::Tpl(0),
Instr::Inc(0)
],
instructions
);
}
#[test]
pub fn test_run() {
assert_eq!([2, 0], run(&PuzzleData::from(CONTENT).0, [0, 0]));
}
}
Day 24: It Hangs in the Balance
Rust solution to AoC|2015|24.
Help Santa balance his sleigh.
Input
Parse weights and sort them in descending order.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub mod input {
use crate::SolType;
#[derive(Debug)]
pub struct PuzzleData(pub Vec<SolType>);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
let mut v = Self(s.lines().map(str::parse).collect::<Result<_, _>>().unwrap());
v.0.sort_unstable_by_key(|&weight| !weight);
v
}
}
}
Star 1
The solution uses backtracking implemented as depth first traversal via recursive function find_best
. Once set of packages is found that matches the target weight, the number of packages serves as upper bound which is used to eliminate branches. The maximum number of packages is initialized to one third of the total number of packages.
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
pub fn find_best(weights: &[SolType], tar_weight: SolType, max_n: u32) -> Option<(SolType, u32)> {
debug_assert!(max_n > 0);
let k0 = weights.iter().position(|&weight| weight <= tar_weight)?;
if weights[k0] == tar_weight {
// can't do any better
Some((tar_weight, 1))
} else if (max_n as SolType) * weights[k0] < tar_weight {
// no solution possible
None
} else {
// find potential solutions and return best
let (mut opt_qe, mut opt_n, mut found) = (SolType::MAX, max_n, false);
for k in k0..weights.len() {
let Some((qe, n)) =
find_best(&weights[k + 1..], tar_weight - weights[k], opt_n - 1)
.map(|(qe, n)| (qe * weights[k], n + 1))
.filter(|&(qe, n)| (n, qe) < (opt_n, opt_qe)) else { continue };
(opt_n, opt_qe, found) = (n, qe, true);
}
if found {
Some((opt_qe, opt_n))
} else {
None
}
}
}
pub fn star_1(PuzzleData(weights): &PuzzleData) -> SolType {
find_best(
weights,
weights.iter().sum::<SolType>() / 3,
(weights.len() / 3) as _,
)
.map(|(qe, _)| qe)
.unwrap()
}
Star 2
Just change the target weight to 1/4 of the total weight and change the maximum number of packages accordingly.
1
2
3
4
5
6
7
8
9
pub fn star_2(PuzzleData(weights): &PuzzleData) -> SolType {
find_best(
weights,
weights.iter().sum::<SolType>() / 4,
(weights.len() / 4) as _,
)
.map(|(qe, _)| qe)
.unwrap()
}
Tests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#[cfg(test)]
mod tests {
use super::*;
const DATA: &[SolType] = &[11, 10, 9, 8, 7, 5, 4, 3, 2, 1];
#[test]
pub fn test_star_1() {
assert_eq!(99, star_1(&PuzzleData(DATA.into())));
}
#[test]
pub fn test_star_2() {
assert_eq!(44, star_2(&PuzzleData(DATA.into())));
}
}
Day 25: Let It Snow
Rust solution to AoC|2015|25.
Find a code for the weather machine.
Input
Parse row and column from the console message. Convert to zero-based.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pub mod input {
#[derive(Debug)]
pub struct PuzzleData(pub usize, pub usize);
impl From<&str> for PuzzleData {
fn from(s: &str) -> Self {
let mut it = s
.trim()
.split_ascii_whitespace()
.filter(|w| (w.as_bytes()[0] as char).is_ascii_digit())
.map(|w| w.trim_end_matches(['.', ',']).parse::<usize>().unwrap() - 1);
Self(it.next().unwrap(), it.next().unwrap())
}
}
}
Star 1
The solution consists of figuring out the element’s number based on the given row and column and then perform exponentiation by squaring with modulo using a recursive function pow_mod
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pub fn pow_mod(base: SolType, exp: usize, m: SolType) -> SolType {
match exp {
0 => 1,
1 => base,
_ => match (exp & 1, pow_mod(base, exp >> 1, m)) {
(1, v) => (((v * v) % m) * base) % m,
(_, v) => (v * v) % m,
},
}
}
pub const INIT: SolType = 20_151_125;
pub const M: SolType = 33_554_393;
pub const BASE: SolType = 252_533;
pub fn star_1(PuzzleData(row, col): &PuzzleData) -> SolType {
// row + col is number of diagonal (0 based)
// col is number of element within diagonal
(INIT * pow_mod(BASE, (row + col + 1) * (row + col) / 2 + col, M)) % M
}
Tests
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
#[cfg(test)]
mod tests {
use super::*;
const CONTENT: &str = r#" | 1 2 3 4 5 6
---+---------+---------+---------+---------+---------+---------+
1 | 20151125 18749137 17289845 30943339 10071777 33511524
2 | 31916031 21629792 16929656 7726640 15514188 4041754
3 | 16080970 8057251 1601130 7981243 11661866 16474243
4 | 24592653 32451966 21345942 9380097 10600672 31527494
5 | 77061 17552253 28094349 6899651 9250759 31663883
6 | 33071741 6796745 25397450 24659492 1534922 27995004
"#;
#[test]
pub fn test_star_1() {
for (row, col, exp) in CONTENT.lines().skip(2).enumerate().flat_map(|(row, l)| {
l.split_ascii_whitespace()
.skip(2)
.enumerate()
.map(move |(col, w)| (row, col, w.parse::<SolType>().unwrap()))
}) {
assert_eq!(exp, star_1(&PuzzleData(row, col)))
}
}
}