advent_of_code/year2018/
day03.rs1use crate::input::Input;
2
3struct Fabric {
4 num_claims: Vec<u32>,
5}
6
7struct Claim {
8 id: u32,
9 x: u32,
10 y: u32,
11 width: u32,
12 height: u32,
13}
14
15const SQUARE_WIDTH: u32 = 1000;
16
17impl Fabric {
18 fn from_claims(claims: &[Claim]) -> Self {
19 let mut result = Self {
20 num_claims: vec![0; SQUARE_WIDTH as usize * SQUARE_WIDTH as usize],
21 };
22 claims.iter().for_each(|claim| result.add_claim(claim));
23 result
24 }
25
26 fn add_claim(&mut self, claim: &Claim) {
27 for y in claim.y..claim.y + claim.height {
28 let row_offset = y * 1000;
29 for x in claim.x..claim.x + claim.width {
30 self.num_claims[(row_offset + x) as usize] += 1_u32;
31 }
32 }
33 }
34
35 fn inches_claimed_multiple(&self) -> u32 {
36 self.num_claims.iter().filter(|&&c| c > 1).count() as u32
37 }
38
39 fn is_claimed_once(&self, claim: &Claim) -> bool {
40 let mut result = true;
41 for y in claim.y..claim.y + claim.height {
42 let row_offset = y * 1000;
43 for x in claim.x..claim.x + claim.width {
44 if self.num_claims[(row_offset + x) as usize] > 1 {
45 result = false;
46 }
47 }
48 }
49 result
50 }
51}
52
53fn parse_input(input_string: &str) -> Result<Vec<Claim>, String> {
54 input_string
55 .lines()
56 .enumerate()
57 .map(|(line_index, line)| {
58 let error_message = || format!("Invalid input on line {}", line_index + 1);
59 let parts: Vec<u32> = line
60 .replace(['#', '@', ':'], "")
61 .replace([',', 'x'], " ")
62 .split_whitespace()
63 .map(|s| s.parse::<u32>().map_err(|_| error_message()))
64 .collect::<Result<_, _>>()?;
65 if parts.len() != 5 {
66 return Err(error_message());
67 }
68 let claim = Claim {
69 id: parts[0],
70 x: parts[1],
71 y: parts[2],
72 width: parts[3],
73 height: parts[4],
74 };
75 if claim.x + claim.width > SQUARE_WIDTH || claim.y + claim.height > SQUARE_WIDTH {
76 return Err(format!(
77 "Claim outside {SQUARE_WIDTH} by {SQUARE_WIDTH} square"
78 ));
79 }
80 Ok(claim)
81 })
82 .collect()
83}
84
85pub fn solve(input: &Input) -> Result<u32, String> {
86 let claims = parse_input(input.text)?;
87 let fabric = Fabric::from_claims(&claims);
88
89 if input.is_part_one() {
90 Ok(fabric.inches_claimed_multiple())
91 } else {
92 let claim_without_overlap = claims
93 .iter()
94 .find(|claim| fabric.is_claimed_once(claim))
95 .ok_or("No claim without overlap found")?;
96 Ok(claim_without_overlap.id)
97 }
98}
99
100#[test]
101fn tests() {
102 use crate::input::{test_part_one, test_part_two};
103 test_part_one!(
104 "#1 @ 1,3: 4x4
105#2 @ 3,1: 4x4
106#3 @ 5,5: 2x2" => 4
107 );
108
109 test_part_two!(
110 "#1 @ 1,3: 4x4
111#2 @ 3,1: 4x4
112#3 @ 5,5: 2x2"
113 => 3
114 );
115
116 let input = include_str!("day03_input.txt");
117 test_part_one!(input => 104_126);
118 test_part_two!(input => 695);
119}