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:

  1. <s> ⇒ <t1><t2>

  2. <s> ⇒ <t1>Rn…​Ar

For the 2nd type, there are three sub-types:

  1. <s> ⇒ <t1>Rn<t2>Ar

  2. <s> ⇒ <t1>Rn<t2>Y<t3>Ar

  3. <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)))
        }
    }
}