advent-of-code/aoc2018/src/day03.rs

160 lines
3.7 KiB
Rust
Raw Normal View History

2019-11-15 16:11:58 +01:00
use std::collections::HashMap;
2019-11-15 17:25:40 +01:00
use std::collections::HashSet;
use std::fmt::Write;
use std::str::FromStr;
2019-11-15 16:11:58 +01:00
2020-12-14 20:24:09 +01:00
use anyhow::{Context, Result};
2019-11-15 16:11:58 +01:00
const INPUT: &str = include_str!("../input/day03.txt");
pub fn run() -> Result<String> {
let mut res = String::with_capacity(128);
2019-11-15 16:11:58 +01:00
writeln!(res, "part 1: {}", part1(INPUT)?)?;
writeln!(res, "part 2: {}", part2(INPUT)?)?;
Ok(res)
2019-11-15 16:11:58 +01:00
}
struct Claim {
x: usize,
y: usize,
width: usize,
height: usize,
id: usize,
}
impl FromStr for Claim {
2020-12-14 20:24:09 +01:00
type Err = anyhow::Error;
/// Parses a claim from a line
///
/// Fails if the line is badly formatted. The expected format is:
///
/// ```text
/// #ID @ X,Y: WxH
/// ```
fn from_str(s: &str) -> Result<Self> {
// skip '#' in line
let s = &s[1..];
// find ' @ ' separator
2020-12-14 20:24:09 +01:00
let at = s.find(" @ ").context("` @ ` delimiter not found")?;
let id = s[..at].parse()?;
let s = &s[(at + 3)..];
// parse 'X,Y: WxH
2020-12-14 20:24:09 +01:00
let comma = s.find(',').context("`,` delimiter not found")?;
let colon = s.find(':').context("`:` delimiter not found")?;
let x = s[..comma].parse()?;
let y = s[(comma + 1)..colon].parse()?;
// reduce line to 'WxH'
let s = &s[(colon + 2)..];
2020-12-14 20:24:09 +01:00
let sep = s.find('x').context("`x` delimiter not found")?;
let width = s[..sep].parse()?;
let height = s[(sep + 1)..].parse()?;
Ok(Claim {
x,
y,
width,
height,
id,
})
}
2019-11-15 16:11:58 +01:00
}
fn part1(input: &str) -> Result<u64> {
let mut res = 0;
2019-11-18 13:56:50 +01:00
let mut map: HashMap<(usize, usize), u64> = HashMap::new();
2019-11-15 16:11:58 +01:00
for line in input.lines() {
let claim: Claim = line
.parse()
2020-12-14 20:24:09 +01:00
.with_context(|| format!("couldn't parse line: `{}`", line))?;
2019-11-15 16:11:58 +01:00
for i in 0..claim.width {
for j in 0..claim.height {
let x = claim.x + i;
let y = claim.y + j;
2019-11-15 16:11:58 +01:00
// add tissue patch at coordinates (x, y)
let entry = map.entry((x, y)).or_default();
*entry += 1;
// count overlap the first time only
if *entry == 2 {
res += 1;
}
}
}
}
Ok(res)
}
2019-11-15 17:25:40 +01:00
fn part2(input: &str) -> Result<usize> {
2019-11-18 13:56:50 +01:00
let mut map: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
2019-11-15 17:25:40 +01:00
let mut set = HashSet::new();
for line in input.lines() {
let claim: Claim = line
.parse()
2020-12-14 20:24:09 +01:00
.with_context(|| format!("couldn't parse line: `{}`", line))?;
2019-11-15 17:25:40 +01:00
set.insert(claim.id);
for i in 0..claim.width {
for j in 0..claim.height {
let x = claim.x + i;
let y = claim.y + j;
// add tissue patch at coordinates (x, y)
let entry = map.entry((x, y)).or_default();
entry.push(claim.id);
// if overlap, remove claims from possible solutions
if entry.len() > 1 {
for id in entry {
set.remove(id);
}
}
}
}
}
assert!(!set.is_empty());
Ok(set.into_iter().next().unwrap())
}
2019-11-15 16:11:58 +01:00
#[cfg(test)]
mod tests {
use super::*;
2019-11-15 17:25:40 +01:00
const PROVIDED: &str = "#1 @ 1,3: 4x4
2019-11-15 16:11:58 +01:00
#2 @ 3,1: 4x4
#3 @ 5,5: 2x2
";
2019-11-15 17:25:40 +01:00
#[test]
fn part1_provided() {
assert_eq!(part1(PROVIDED).unwrap(), 4);
2019-11-15 16:11:58 +01:00
}
#[test]
fn part1_real() {
assert_eq!(part1(INPUT).unwrap(), 114946);
}
2019-11-15 17:25:40 +01:00
#[test]
fn part2_provided() {
assert_eq!(part2(PROVIDED).unwrap(), 3);
}
#[test]
fn part2_real() {
assert_eq!(part2(INPUT).unwrap(), 877);
}
2019-11-15 16:11:58 +01:00
}