summaryrefslogtreecommitdiff
path: root/2020/rust/day05/src
diff options
context:
space:
mode:
Diffstat (limited to '2020/rust/day05/src')
-rw-r--r--2020/rust/day05/src/main.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/2020/rust/day05/src/main.rs b/2020/rust/day05/src/main.rs
new file mode 100644
index 0000000..4d6072a
--- /dev/null
+++ b/2020/rust/day05/src/main.rs
@@ -0,0 +1,135 @@
+use anyhow::{anyhow, Result};
+use std::env::args;
+use std::fs::File;
+use std::io::{BufRead, BufReader};
+use std::ops::RangeInclusive;
+use std::path::Path;
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+enum BinarySpaceError {
+ #[error("Space is not fully reduced")]
+ NotReduced,
+}
+
+struct BinarySpace {
+ start: usize,
+ end: usize,
+}
+
+impl BinarySpace {
+ fn new(range: RangeInclusive<usize>) -> BinarySpace
+ // FIXME It would be nice with a cleaner interface. The intention was to use something like the
+ // RangeBounds trait, but it seems to require support of Unbounded ranges, which this
+ // BinarySpace will not be able to work with.
+ {
+ BinarySpace {
+ start: *range.start(),
+ end: *range.end(),
+ }
+ }
+
+ fn size(&self) -> usize {
+ self.end - self.start
+ }
+
+ fn split(&mut self, op: char) {
+ let size = self.size() + 1;
+ let half = size / 2;
+ match op {
+ 'F' | 'L' => self.end -= half,
+ 'B' | 'R' => self.start += half,
+ c => panic!("Invalid op to BinarySpace.split(): {}", c),
+ }
+ }
+
+ fn value(&self) -> std::result::Result<usize, BinarySpaceError> {
+ if self.start == self.end {
+ Ok(self.start)
+ } else {
+ Err(BinarySpaceError::NotReduced)
+ }
+ }
+}
+
+fn read_input<T: AsRef<Path>>(filename: T) -> std::io::Result<Vec<String>> {
+ let f = File::open(filename)?;
+ let reader = BufReader::new(f);
+
+ reader.lines().collect()
+}
+
+fn parse_boarding_passes(input: &[String]) -> Result<Vec<usize>> {
+ input.iter().map(|ops| {
+ let mut row = BinarySpace::new(0..=127);
+ let mut col = BinarySpace::new(0..=7);
+ for op in ops.clone().drain(..) {
+ match op {
+ 'F' | 'B' => row.split(op),
+ 'L' | 'R' => col.split(op),
+ _ => return Err(anyhow!("Invalid op encountered")),
+ }
+ }
+
+ Ok(row.value()? * 8 + col.value()?)
+ }).collect()
+}
+
+fn part1(input: &[usize]) -> Option<usize> {
+ input.iter().max().copied()
+}
+
+fn part2(input: &[usize]) -> Option<usize> {
+ let mut ret = None;
+
+ for seat in 1..(128*8) {
+ if (input.contains(&seat),
+ input.contains(&(seat-1)),
+ input.contains(&(seat+1))
+ ) == (false, true, true) {
+ match ret {
+ None => ret = Some(seat),
+ Some(_) => return None, /* No more than one solution allowed */
+ }
+ }
+ }
+
+ ret
+}
+
+fn main() {
+ let (do_part_1, do_part_2) = aoc::do_parts();
+
+ let filename = match args().nth(1) {
+ Some(f) => f,
+ None => {
+ eprintln!("Missing input filename");
+ std::process::exit(1);
+ },
+ };
+ match read_input(filename) {
+ Ok(input) => {
+ match parse_boarding_passes(&input) {
+ Ok(seat_ids) => {
+ if do_part_1 {
+ match part1(&seat_ids) {
+ Some(solution) =>
+ println!("Part1, highest seat ID found to be: {}", solution),
+ None => eprintln!("Part1, no soluton found"),
+ }
+ }
+ if do_part_2 {
+ match part2(&seat_ids) {
+ Some(solution) =>
+ println!("Part2, seat {} is lacking a boarding pass", solution),
+ None => eprintln!("Part2, no soluton found"),
+ }
+ }
+ },
+ Err(err) => eprintln!("Could not parse input: {}", err),
+ }
+
+ },
+ Err(err) => eprintln!("Could not read input: {}", err),
+ }
+}