Skip to content

Commit

Permalink
feat: Day 19
Browse files Browse the repository at this point in the history
  • Loading branch information
rgoulais committed Dec 19, 2023
1 parent d740d57 commit e78cd94
Show file tree
Hide file tree
Showing 3 changed files with 360 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www.
| [Day 16](./src/bin/16.rs) | `73.4µs` | `8.7ms` |
| [Day 17](./src/bin/17.rs) | `24.6ms` | `89.6ms` |
| [Day 18](./src/bin/18.rs) | `43.9µs` | `58.5µs` |
| [Day 19](./src/bin/19.rs) | `1.2ms` | `964.4µs` |

**Total: 153.49ms**
**Total: 155.66ms**
<!--- benchmarking table --->

---
Expand Down
17 changes: 17 additions & 0 deletions data/examples/19.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
px{a<2006:qkq,m>2090:A,rfg}
pv{a>1716:R,A}
lnx{m>1548:A,A}
rfg{s<537:gd,x>2440:R,A}
qs{s>3448:A,lnx}
qkq{x<1416:A,crn}
crn{x>2662:A,R}
in{s<1351:px,qqz}
qqz{s>2770:qs,m<1801:hdj,R}
gd{a>3333:R,R}
hdj{m>838:A,pv}

{x=787,m=2655,a=1222,s=2876}
{x=1679,m=44,a=2067,s=496}
{x=2036,m=264,a=79,s=2244}
{x=2461,m=1339,a=466,s=291}
{x=2127,m=1623,a=2188,s=1013}
341 changes: 341 additions & 0 deletions src/bin/19.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
advent_of_code::solution!(19);

const X: u8 = 'x' as u8;
const M: u8 = 'm' as u8;
const A: u8 = 'a' as u8;
const S: u8 = 's' as u8;

const GREATER: u8 = '>' as u8;
const LESSER: u8 = '<' as u8;

const START_VALUE: (u32,u32) = (1, 4000);

struct Instruction {
attribut: u8,
condition: u8,
value: u32,
action: String,
}

impl Instruction {
pub fn new(input: &str) -> Self {
let parts: Vec<&str> = input.split(':').collect();
let action = parts.last().unwrap().to_string();
if parts[0].contains('>') || parts[0].contains('<') {
let bytes = parts[0].as_bytes();
let attribut = match bytes[0] {
X => X,
M => M,
A => A,
S => S,
_ => panic!("Unknown attribut"),
};
let condition = match bytes[1] {
GREATER => GREATER,
LESSER => LESSER,
_ => panic!("Unknown condition"),
};
let value = parts[0][2..].parse().unwrap();
Self { attribut, condition, value, action }
} else {
Self { attribut: 0, condition: 0, value: 0, action }
}
}

pub fn correspond(&self, part: &mut Part) -> bool {
if self.attribut == 0 {
part.label = self.action.clone();
return true;
}
let part_value = match self.attribut {
X => part.x,
M => part.m,
A => part.a,
S => part.s,
_ => panic!("Unknown field: {}", self.attribut),
};
let condition_satisfied = match self.condition {
GREATER => part_value > self.value,
LESSER => part_value < self.value,
_ => panic!("Unknown operator: {}", self.condition),
};
if condition_satisfied {
part.label = self.action.clone();
}
return condition_satisfied;
}

fn check_ranges(&self, range: &Ranges) -> Option<(Ranges, Ranges)> {
let mut match_range = range.clone();
match_range.label = self.action.clone();
if self.attribut == 0 {
return Some((match_range, Ranges::get_invalid()));
}
let mut match_range = range.clone();
match_range.label = self.action.clone();
let mut left_range = range.clone();
let mut found = false;
match self.attribut {
X => {
if self.condition == GREATER {
if self.value > range.x.0 {
match_range.x.0 = self.value + 1;
left_range.x.1 = self.value;
found = true;
}
} else {
if self.value < range.x.1 {
match_range.x.1 = self.value - 1;
left_range.x.0 = self.value;
found = true;
}
}
}
M => {
if self.condition == GREATER {
if self.value > range.m.0 {
match_range.m.0 = self.value + 1;
left_range.m.1 = self.value;
found = true;
}
} else {
if self.value < range.m.1 {
match_range.m.1 = self.value - 1;
left_range.m.0 = self.value;
found = true;
}
}
}
A => {
if self.condition == GREATER {
if self.value > range.a.0 {
match_range.a.0 = self.value + 1;
left_range.a.1 = self.value;
found = true;
}
} else {
if self.value < range.a.1 {
match_range.a.1 = self.value - 1;
left_range.a.0 = self.value;
found = true;
}
}
}
S => {
if self.condition == GREATER {
if self.value > range.s.0 {
match_range.s.0 = self.value + 1;
left_range.s.1 = self.value;
found = true;
}
} else {
if self.value < range.s.1 {
match_range.s.1 = self.value - 1;
left_range.s.0 = self.value;
found = true;
}
}
}
_ => panic!("Unknown field: {}", self.attribut),
}
if found {
return Some((match_range, left_range));
} else {
return None;
}
}
}


struct Workflow {
label: String,
actions: Vec<Instruction>,
}

struct Part {
label: String,
x: u32,
m: u32,
a: u32,
s: u32,
}

#[derive(Clone)]
struct Ranges {
label: String,
x: (u32, u32),
m: (u32, u32),
a: (u32, u32),
s: (u32, u32),
}

impl Ranges {
fn is_valid(&self) -> bool {
self.x.0 <= self.x.1 && self.m.0 <= self.m.1 && self.a.0 <= self.a.1 && self.s.0 <= self.s.1 && self.label != "R"
}
fn get_invalid() -> Self {
Self { label: "R".to_string(), x: (1, 0), m: (0, 0), a: (0, 0), s: (0, 0) }
}

fn combinaisons(&self) -> u64 {
if !self.is_valid() {
return 0;
}
let mut combinaisons = 1;
combinaisons *= self.x.1 as u64 - self.x.0 as u64 + 1;
combinaisons *= self.m.1 as u64 - self.m.0 as u64 + 1;
combinaisons *= self.a.1 as u64 - self.a.0 as u64 + 1;
combinaisons *= self.s.1 as u64 - self.s.0 as u64 + 1;
combinaisons
}

}

impl Part {
fn sum(&self) -> u32 {
self.x + self.m + self.a + self.s
}
}

struct Solver {
instructions: Vec<Workflow>,
parts: Vec<Part>,
}


impl Solver {
pub fn new() -> Self {
Self {
instructions: Vec::new(),
parts: Vec::new(),
}
}

pub fn solve_part1(&mut self, input: &str) -> Option<u32> {
self.parse_input(input);
Some(self.process_parts())
}

pub fn solve_part2(&mut self, input: &str) -> Option<u64> {
self.parse_input(input);
Some(self.find_ranges())
}

fn parse_input(&mut self, input: &str) {
let mut is_instruction = true;

for line in input.lines() {
if line.trim().is_empty() {
is_instruction = false;
continue;
}
if is_instruction {
let mut split = line.splitn(2, '{');
let label = split.next().unwrap().to_string();
let actions = split.next().unwrap().trim_end_matches('}').split(',').map(|s| Instruction::new(s)).collect();
self.instructions.push(Workflow { label, actions });
} else {
let values: Vec<u32> = line.trim_start_matches('{').trim_end_matches('}').split(',').map(|s| s[2..].trim().parse().unwrap()).collect();
let part = Part { x: values[0], m: values[1], a: values[2], s: values[3], label: "in".to_string() };
self.parts.push(part);
}
}
}

fn process_parts(&mut self) -> u32 {
let mut somme = 0;
while let Some(mut part) = self.parts.pop() {
let instruction = self.instructions.iter().find(|i| i.label == part.label).unwrap();
for action in &instruction.actions {
if action.correspond(&mut part) {
break;
}
}
if part.label == "A" {
somme += part.sum();
} else if part.label != "R" {
self.parts.push(part);
}
}
somme
}

fn find_ranges(&self) -> u64 {
let range = Ranges { label: "in".to_string(), x: START_VALUE, m: START_VALUE, a: START_VALUE, s: START_VALUE };
let mut ranges = vec![range];
let mut somme = 0;
while let Some(range) = ranges.pop() {
let instruction = self.instructions.iter().find(|i| i.label == range.label).unwrap();
let mut current_range = range.clone();
for action in &instruction.actions {
if let Some((match_range, left_range)) = action.check_ranges(&current_range) {
if match_range.is_valid() {
if match_range.label == "A" {
somme += match_range.combinaisons();
} else {
ranges.push(match_range);
}
}
if left_range.is_valid() {
current_range = left_range;
} else {
break;
}
}
}

}
somme
}
}

pub fn part_one(input: &str) -> Option<u32> {
let mut solver = Solver::new();
solver.solve_part1(input)
}

pub fn part_two(input: &str) -> Option<u64> {
let mut solver = Solver::new();
solver.solve_part2(input)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_part_one() {
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(19114));
}

#[test]
fn test_part_two() {
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
assert_eq!(result, Some(167409079868000));
}

#[test]
fn combinaisons_calculates_correctly_for_valid_ranges() {
let ranges = Ranges { label: "in".to_string(), x: (1, 3), m: (1, 2), a: (1, 2), s: (1, 2) };
assert_eq!(ranges.combinaisons(), 24);
}

#[test]
fn combinaisons_calculates_correctly_for_zero_ranges() {
let ranges = Ranges { label: "in".to_string(), x: (0, 0), m: (0, 0), a: (0, 0), s: (0, 0) };
assert_eq!(ranges.combinaisons(), 1);
}

#[test]
fn combinaisons_calculates_correctly_for_single_value_ranges() {
let ranges = Ranges { label: "in".to_string(), x: (1, 1), m: (1, 1), a: (1, 1), s: (1, 1) };
assert_eq!(ranges.combinaisons(), 1);
}

#[test]
fn combinaisons_calculates_correctly_for_invalid_ranges() {
let ranges = Ranges { label: "in".to_string(), x: (2, 1), m: (2, 1), a: (2, 1), s: (2, 1) };
assert_eq!(ranges.combinaisons(), 0);
}
}

0 comments on commit e78cd94

Please sign in to comment.