diff --git a/2021/src/day16.rs b/2021/src/day16.rs new file mode 100644 index 0000000..4690b2b --- /dev/null +++ b/2021/src/day16.rs @@ -0,0 +1,127 @@ +use color_eyre::eyre; + +use crate::lib::{BitVec, BitSlice}; + + +#[derive(Debug)] +struct Packet { + version: u8, + type_id: u8, + content: PacketContent, +} + +#[derive(Debug)] +enum PacketContent { + Literal(u64), + Operator(Vec), +} + +struct Parser { + data: BitVec, + idx: usize, +} + +impl Parser { + fn from_hex(hex: &str) -> eyre::Result { + let mut data = BitVec::with_capacity(hex.len() * 4); + for i in 0 .. (hex.len() / 2) { // we are assuming valid hex + let chunk = &hex[i * 2 .. (i + 1) * 2]; + let byte = u8::from_str_radix(chunk, 16)?; + data.push_byte(byte); + } + + Ok(Parser {data, idx: 0}) + } + + fn next(&mut self, n: usize) -> BitSlice { + let range = self.idx .. self.idx + n; + self.idx += n; + self.data.slice(range) + } + + fn parse_value(&mut self) -> u64 { + let mut value = 0; + loop { + let group = self.next(5); + value = (value << 4) | group.slice(1..5).as_u64(); + if !group[0] {break;} + } + value + } + + fn parse_children(&mut self) -> Vec { + let mut children = Vec::new(); + if self.next(1).as_bool() { + // count refers to number of sub-packets + let count = self.next(11).as_u16(); + children = Vec::with_capacity(count as usize); + for _i in 0..count { + children.push(self.parse()); + } + } + else { + // len refers to content length in bits + let len = self.next(15).as_u16(); + let end = self.idx + (len as usize); + while self.idx < end { + children.push(self.parse()); + } + } + children + } + + fn parse(&mut self) -> Packet { + let version = self.next(3).as_u8(); + let type_id = self.next(3).as_u8(); + let content = match type_id { + 4 => PacketContent::Literal(self.parse_value()), + _ => PacketContent::Operator(self.parse_children()), + }; + + Packet {version, type_id, content} + } +} + + +fn sum_versions(root: &Packet) -> usize { + let mut total = root.version as usize; + if let PacketContent::Operator(children) = &root.content { + for child in children { + total += sum_versions(child); + } + } + total +} + + +fn evaluate(packet: &Packet) -> usize { + let children = match &packet.content { + PacketContent::Literal(value) => {return *value as usize;}, // early exit here + PacketContent::Operator(children) => children, + }; + + let mut values = children.iter().map(|p| evaluate(p)); + match packet.type_id { + 0 => values.sum(), + 1 => values.product(), + 2 => values.min().unwrap(), + 3 => values.max().unwrap(), + // it's guaranteed that comparisons will have exactly two children + 5 => if values.next().unwrap() > values.next().unwrap() {1} else {0}, + 6 => if values.next().unwrap() < values.next().unwrap() {1} else {0}, + 7 => if values.next().unwrap() == values.next().unwrap() {1} else {0}, + // the version number was a 3-bit value, so: + _ => unreachable!(), + } +} + + +pub fn run(data: &str) -> eyre::Result<(usize, usize)> { + let mut parser = Parser::from_hex(data)?; + let root = parser.parse(); + + let one = sum_versions(&root); + let two = evaluate(&root); + + Ok((one, two)) +} diff --git a/2021/src/lib.rs b/2021/src/lib.rs index 77c970d..6940ade 100644 --- a/2021/src/lib.rs +++ b/2021/src/lib.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use std::fmt::{Display, Formatter}; use std::fs::File; use std::hash::Hash; -use std::ops::{Index, IndexMut}; +use std::ops::{Index, IndexMut, Range}; use std::str::FromStr; use color_eyre::{eyre}; @@ -341,3 +341,128 @@ impl Counter self.data.iter().map(|(k, v)| (k, *v)) } } + + +pub struct BitVec { + data: Vec, + len: usize, +} + +#[allow(dead_code)] +impl BitVec { + fn new() -> Self { + BitVec {data: vec![], len: 0} + } + + pub fn with_capacity(cap: usize) -> Self { + let true_cap = (cap + 8 - 1) / 8; // ceiling division + BitVec { + data: Vec::with_capacity(true_cap), + len: 0 + } + } + + pub fn len(&self) -> usize { + self.len + } + + pub fn push(&mut self, b: bool) { + let offset = self.len % 8; + let v = (b as u8) << (7 - offset); + match offset { + 0 => self.data.push(v), + _ => *self.data.last_mut().unwrap() |= v, + } + self.len += 1; + } + + pub fn push_byte(&mut self, byte: u8) { + if self.len() % 8 != 0 { + panic!("Attempted to push full byte across byte boundary"); + } + self.data.push(byte); + self.len += 8; + } + + pub fn slice(&self, range: Range) -> BitSlice { + if range.end > self.len { + panic!("Slice out of bounds: {}..{}", range.start, range.end); + } + BitSlice { + src: self, + start: range.start, + end: range.end, + } + } +} + +impl Index for BitVec { + type Output = bool; + fn index(&self, index: usize) -> &bool { + let (idx, offset) = (index / 8, index % 8); + let bit = self.data[idx] & (128 >> offset); + if bit == 0 {&false} else {&true} + } +} + + +#[derive(Copy, Clone)] +pub struct BitSlice<'a> { + src: &'a BitVec, + start: usize, + end: usize, +} + +#[allow(dead_code)] +impl BitSlice<'_> { + pub fn len(&self) -> usize { + self.end - self.start + } + + pub fn as_u64(&self) -> u64 { + if self.len() > 64 {panic!("Slice too large for u64");} + + let mut value = 0; + for i in self.start..self.end { + let offset = self.end - 1 - i; + value |= (self.src[i] as u64) << offset; + } + value + } + + pub fn as_u16(&self) -> u16 { + if self.len() > 16 {panic!("Slice too large for u16");} + self.as_u64() as u16 + } + + pub fn as_u8(&self) -> u8 { + if self.len() > 8 {panic!("Slice too large for u8");} + self.as_u64() as u8 + } + + pub fn as_bool(&self) -> bool { + if self.len() != 1 { + panic!("Only slices of length 1 can be converted to bool"); + } + self.as_u64() == 1 + } + + pub fn slice(&self, range: Range) -> BitSlice { + if range.end > self.len() { + panic!("Slice out of bounds: {}..{}", range.start, range.end); + } + + BitSlice { + src: &self.src, + start: self.start + range.start, + end: self.start + range.end, + } + } +} + +impl Index for BitSlice<'_> { + type Output = bool; + fn index(&self, index: usize) -> &bool { + self.src.index(self.start + index) + } +} diff --git a/2021/src/main.rs b/2021/src/main.rs index 1c18e6b..424d2c9 100644 --- a/2021/src/main.rs +++ b/2021/src/main.rs @@ -4,14 +4,14 @@ use color_eyre::eyre; mod lib; use lib::load; -mod day15; +mod day16; fn main() -> eyre::Result<()> { - let data = load("data/15.txt")?; + let data = load("data/16.txt")?; let start = Instant::now(); - let (one, two) = day15::run(&data)?; + let (one, two) = day16::run(&data)?; let (dur, unit) = format_ns(start.elapsed().as_nanos()); let precision = 2.0 - dur.log10().floor();