diff --git a/2021/day16/day16_rs/Cargo.toml b/2021/day16/day16_rs/Cargo.toml new file mode 100644 index 0000000..6d6d7d7 --- /dev/null +++ b/2021/day16/day16_rs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "day16_rs" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom = "7.1.0" diff --git a/2021/day16/day16_rs/src/main.rs b/2021/day16/day16_rs/src/main.rs new file mode 100644 index 0000000..de91976 --- /dev/null +++ b/2021/day16/day16_rs/src/main.rs @@ -0,0 +1,199 @@ +#![warn(clippy::pedantic)] + +use std::{ + io::{stdin, Read}, + ops::ControlFlow, +}; + +use nom::{ + bits::complete as bits, + combinator::flat_map, + multi::{many0, many_m_n}, +}; +use nom::{combinator::map, sequence::pair}; +use parsers::fold_till; + +mod parsers; + +type Input<'a> = (&'a [u8], usize); +type IResult<'a, T> = nom::IResult, T>; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct Packet { + version: u8, + typ: PacketType, +} + +impl Packet { + pub fn parse(i: Input) -> IResult { + map( + pair(bits::take(3_usize), PacketType::parse), + |(version, typ)| Packet { version, typ }, + )(i) + } + + pub fn version_sum(&self) -> usize { + usize::from(self.version) + + match self.typ { + PacketType::Literal(_) => 0, + PacketType::Operation { + ref sub_packets, .. + } => sub_packets.iter().map(Packet::version_sum).sum(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +enum PacketType { + Literal(usize), + Operation { + operator: Operator, + sub_packets: Vec, + }, +} + +impl PacketType { + pub fn parse(input: Input) -> IResult { + flat_map(bits::take(3_usize), |type_id: u8| { + move |i| match Operator::try_from(type_id) { + Ok(operator) => map(Self::operation_inner, |sub_packets| PacketType::Operation { + operator, + sub_packets, + })(i), + Err(_) => map(Self::literal_inner, PacketType::Literal)(i), + } + })(input) + } + + fn operation_inner(i: Input) -> IResult> { + enum LengthType { + Bits(u16), + Packets(u16), + } + + flat_map( + flat_map(bits::take(1_usize), |length_type_id: u8| { + move |i| match length_type_id { + 0 => map(bits::take(15_usize), LengthType::Bits)(i), + 1 => map(bits::take(11_usize), LengthType::Packets)(i), + _ => unreachable!(), + } + }), + |length_type| { + move |i| match length_type { + LengthType::Packets(n) => { + many_m_n(usize::from(n), usize::from(n), Packet::parse)(i) + } + LengthType::Bits(n) => { + // map_parser(recognize(bits::take(n)), many1(Packet::parse))(i) + let extra_bytes_required = (usize::from(n) + i.1) / 8; + let extra_bits_required = (usize::from(n) + i.1) % 8; + + let subpackets_slice = (&i.0[..=extra_bytes_required], i.1); + let (subpackets_end, subpackets) = many0(Packet::parse)(subpackets_slice)?; + if subpackets_end.0.len() > 1 { + todo!() + } + Ok(( + (&i.0[extra_bytes_required..], extra_bits_required), + subpackets, + )) + } + } + }, + )(i) + } + + fn literal_inner(i: Input) -> IResult { + fold_till( + pair(bits::take(1_usize), bits::take(4_usize)), + || 0, + |acc, (marker, bits): (u8, usize)| { + (if marker == 1 { + ControlFlow::Continue + } else { + ControlFlow::Break + })((acc << 4) | bits) + }, + )(i) + } + + pub fn evaluate(&self) -> usize { + match self { + Self::Literal(n) => *n, + Self::Operation { + ref operator, + ref sub_packets, + } => operator.evaluate(sub_packets.iter().map(|p| p.typ.evaluate())), + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Operator { + Sum, + Product, + Minimum, + Maximum, + GreaterThan, + LessThan, + EqualTo, +} + +impl Operator { + pub fn evaluate(self, mut operands: impl Iterator) -> usize { + match self { + Self::Sum => operands.sum(), + Self::Product => operands.product(), + Self::Minimum => operands.min().unwrap(), + Self::Maximum => operands.max().unwrap(), + Self::GreaterThan => usize::from(operands.next().unwrap() > operands.next().unwrap()), + Self::LessThan => usize::from(operands.next().unwrap() < operands.next().unwrap()), + Self::EqualTo => usize::from(operands.next().unwrap() == operands.next().unwrap()), + } + } +} + +impl TryFrom for Operator { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Sum), + 1 => Ok(Self::Product), + 2 => Ok(Self::Minimum), + 3 => Ok(Self::Maximum), + 5 => Ok(Self::GreaterThan), + 6 => Ok(Self::LessThan), + 7 => Ok(Self::EqualTo), + _ => Err(()), + } + } +} + +fn main() { + let bytes: Vec = stdin() + .lock() + .bytes() + .filter_map(|c| char::from(c.unwrap()).to_digit(16)) + .map(|c| { + #[allow(clippy::cast_possible_truncation)] // a hex digit always fits in a u8 + let c = c as u8; + c + }) + .scan(None, |prev, n| { + Some(match *prev { + Some(i) => { + *prev = None; + Some(i | n) + } + None => prev.replace(n << 4), + }) + }) + .flatten() + .collect(); + + let packet = Packet::parse((&bytes, 0)).unwrap().1; + println!("{}", packet.version_sum()); + println!("{}", packet.typ.evaluate()); +} diff --git a/2021/day16/day16_rs/src/parsers.rs b/2021/day16/day16_rs/src/parsers.rs new file mode 100644 index 0000000..20034af --- /dev/null +++ b/2021/day16/day16_rs/src/parsers.rs @@ -0,0 +1,41 @@ +use std::ops::ControlFlow; + +use nom::{ + error::{ErrorKind, ParseError}, + Err, InputLength, Parser, +}; + +pub fn fold_till( + mut p: P, + mut init: F, + mut acc: G, +) -> impl FnMut(I) -> nom::IResult +where + I: InputLength, + E: ParseError, + P: Parser, + F: FnMut() -> St, + G: FnMut(St, O) -> ControlFlow, +{ + move |i| { + let mut res = init(); + let mut input = i; + + loop { + let len = input.input_len(); + let (i, o) = p.parse(input)?; + + if i.input_len() == len { + return Err(Err::Error(E::from_error_kind(i, ErrorKind::Many0))); + } + + match acc(res, o) { + ControlFlow::Continue(next) => { + res = next; + input = i; + } + ControlFlow::Break(res) => return Ok((i, res)), + } + } + } +}