From b5299b174b71f856157cb0b014e94ec6db25c050 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Thu, 16 Dec 2021 17:00:15 -0600 Subject: [PATCH] Holy crap rust --- 2021/readme.md | 52 +++++---- 2021/rust/.gitignore | 1 + 2021/rust/Cargo.lock | 7 ++ 2021/rust/Cargo.toml | 11 ++ 2021/rust/common.rs | 25 +++++ 2021/rust/day16.rs | 251 +++++++++++++++++++++++++++++++++++++++++++ 2021/rust/main.rs | 1 + 7 files changed, 329 insertions(+), 19 deletions(-) create mode 100644 2021/rust/.gitignore create mode 100644 2021/rust/Cargo.lock create mode 100644 2021/rust/Cargo.toml create mode 100644 2021/rust/common.rs create mode 100644 2021/rust/day16.rs create mode 100644 2021/rust/main.rs diff --git a/2021/readme.md b/2021/readme.md index 4f8d6bd..eed0cf3 100644 --- a/2021/readme.md +++ b/2021/readme.md @@ -11,9 +11,7 @@ Specifically, here's my `deno --version` output: v8 9.7.106.5 typescript 4.4.2 -Enjoy! - -**EDIT**: Since performance is not what I would like, it looks like I'm also doing some of these in Nim. +It looks like I'm also doing some of these in Nim. $ nim --version Nim Compiler Version 1.6.0 [Linux: amd64] @@ -23,9 +21,16 @@ Enjoy! git hash: 727c6378d2464090564dbcd9bc8b9ac648467e38 active boot switches: -d:release +And I decided part way through to do some Rust. + + $ rustc --version + rustc 1.58.0-beta.2 (0e07bcb68 2021-12-04) + +Enjoy! + ## Usage -Run these solutions like so: +Run these solutions like so from their respective directories: deno run --unstable --allow-all $DAY.ts @@ -33,6 +38,10 @@ And the Nim ones like so: nim c -d:release -d:ssl --run day$DAY.nim +And Rust: + + rustc -O --out-dir build day$DAY.rs && ./build/day$DAY + And if you want to measure memory usage with Deno programs: mkdir -p build @@ -45,23 +54,28 @@ Or for Nim programs: nim c -d:release -d:ssl --outdir:build day$DAY.nim /usr/bin/time -v ./day$DAY +And similarly for Rust: + + rustc -O --out-dir build day$DAY.rs + /usr/bin/time -v ./build/day$DAY + # Days -- [x] Day 1: [Deno](./1.ts), [Nim](./one.nim) -- [x] Day 2: [Deno](./2.ts), [Nim](./two.nim) -- [x] Day 3: [Deno](./3.ts), [Nim](./three.nim) -- [x] Day 4: [Nim](./four.nim) -- [x] Day 5: [Nim](./five.nim) -- [x] Day 6: [Nim](./six.nim) -- [x] Day 7: [Nim](./seven.nim) -- [x] Day 8: [Nim](./eight.nim) -- [ ] Day 9 -- [ ] Day 10 -- [ ] Day 11 -- [ ] Day 12 -- [ ] Day 13 -- [ ] Day 14 -- [ ] Day 15 +- [x] Day 1: [Deno](./deno/1.ts), [Nim](./nim/day1.nim) +- [x] Day 2: [Deno](./deno/2.ts), [Nim](./nim/day2.nim) +- [x] Day 3: [Deno](./deno/3.ts), [Nim](./nim/day3.nim) +- [x] Day 4: [Nim](./nim/day4.nim) +- [x] Day 5: [Nim](./nim/day5.nim) +- [x] Day 6: [Nim](./nim/day6.nim) +- [x] Day 7: [Nim](./nim/day7.nim) +- [x] Day 8: [Nim](./nim/day8.nim) +- [x] Day 9: [Nim](./nim/day9.nim) +- [x] Day 10: [Nim](./nim/day10.nim) +- [x] Day 11: [Nim](./nim/day11.nim) +- [x] Day 12: [Nim](./nim/day12.nim) +- [x] Day 13: [Nim](./nim/day13.nim) +- [x] Day 14: [Nim](./nim/day14.nim) +- [x] Day 15: [Nim](./nim/day15.nim) - [ ] Day 16 - [ ] Day 17 - [ ] Day 18 diff --git a/2021/rust/.gitignore b/2021/rust/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/2021/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/2021/rust/Cargo.lock b/2021/rust/Cargo.lock new file mode 100644 index 0000000..7baa389 --- /dev/null +++ b/2021/rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aoc2021" +version = "0.1.0" diff --git a/2021/rust/Cargo.toml b/2021/rust/Cargo.toml new file mode 100644 index 0000000..4698de2 --- /dev/null +++ b/2021/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "aoc2021" +version = "0.1.0" +edition = "2021" +include = ["common.rs"] + +[[bin]] +name = "day16" +path = "day16.rs" + +[dependencies] diff --git a/2021/rust/common.rs b/2021/rust/common.rs new file mode 100644 index 0000000..ba9bd00 --- /dev/null +++ b/2021/rust/common.rs @@ -0,0 +1,25 @@ +use std::env::var as envvar; +use std::path::Path; +use std::fs; + +const ASCII_UPPER_A: u8 = 65; +const ASCII_LOWER_A: u8 = 97; +const ASCII_0: u8 = 48; + +pub fn day_input(day: u8) -> String { + let home = &envvar("HOME").unwrap(); + let home_path = Path::new(home); + let path_buf = home_path.join(format!("./.cache/aoc2021/{0}.input", day)); + let file_path = path_buf.to_str().unwrap(); + fs::read_to_string(file_path).unwrap() +} + +pub const fn hex_val(c: char) -> Result { + match c { + 'A'..='F' => Ok(c as u8 - ASCII_UPPER_A + 10), + 'a'..='f' => Ok(c as u8 - ASCII_LOWER_A + 10), + '0'..='9' => Ok(c as u8 - ASCII_0 as u8), + _ => Err("invalid hex char"), + } +} + diff --git a/2021/rust/day16.rs b/2021/rust/day16.rs new file mode 100644 index 0000000..3d3c4cb --- /dev/null +++ b/2021/rust/day16.rs @@ -0,0 +1,251 @@ +#![warn(clippy::all, clippy::pedantic)] + +mod common; +use common::{day_input, hex_val}; + +/// # Panics +/// we may panic +pub fn hex_string_to_bytes(s: &String) -> Vec { + let bytes = s + .trim() + .chars() + .collect::>() + .chunks(2) + .enumerate() + .map(|(_, ct)| Ok(hex_val(ct[0])? << 4 | hex_val(ct[1])?)) + .collect::, &'static str>>() + .unwrap(); + + println!("Hex String: {}", s); + bytes.iter().for_each(|b| println!("{:#010b}", b)); + bytes +} + +/// # Errors +/// lots of them +/// # Panics +/// whenever I want +pub fn parse_bytes_from_bits(bytes: &[u8], bit_offset: usize, length: u8) -> Result { + if length > 64 { + return Err("cannot parse bytes with length > 64"); + } + + // first chunk + let mut result = 0u64; + let start = bit_offset; + let end = bit_offset + usize::from(length); + let first_offset = start % 8; + // println!("Start: {}, End: {}", start, end); + let first_chunk_size = if first_offset + usize::from(length) >= 8 { 8 - (start % 8) } else { end - start }; + let byte_offset = bit_offset / 8; + /* println!("{:?}", vec![ + first_offset, + first_chunk_size, + (8 - first_chunk_size - first_offset), + usize::from(bytes[byte_offset] << first_offset >> first_offset >> (8 - first_chunk_size - first_offset)) + ]); */ + result += u64::from(bytes[byte_offset] << first_offset >> first_offset >> (8 - first_chunk_size - first_offset)); + + // middle bytes + let num_bytes = (first_offset + usize::from(length) + 7) / 8; + // println!("Num Bytes: {}, Bit Offset: {}, Length: {}", num_bytes, bit_offset, length); + for n in 1..(num_bytes - 1) { + result <<= 8; + result += u64::from(bytes[byte_offset + n]); + // println!("After Mid Byte: {:#066b}", result); + } + + // last chunk + if num_bytes > 1 { + let bits_so_far = first_chunk_size + ((num_bytes - 2) * 8); + let bits_left = usize::from(length) - bits_so_far; + // println!("left: {}", bits_left); + let cutoff = 8 - bits_left; + let last_byte_index = byte_offset + num_bytes - 1; + let byte = bytes[last_byte_index]; + let addend = byte >> cutoff; + result <<= bits_left; + result += u64::from(addend); + // println!("After Last Byte: {:#066b} ({}: {}) {} {:#010b}", result, last_byte_index, byte, cutoff, addend); + } + println!(" -> BFROMB Result: {:#066b} ({}->{} [{}] across {} bytes)", result, start, end, length, num_bytes); + Ok(result) +} + +#[derive(Debug)] +struct BITSLiteralValue { + value: u64 +} + +#[derive(Debug)] +enum BITSLengthType { + SubpacketBits, + SubpacketCount, +} + +#[derive(Debug)] +struct BITSOperator { + length_type: BITSLengthType, + length: u64, + subpackets: Vec, +} + +#[derive(Debug)] +enum BITSValue { + LiteralValue(BITSLiteralValue), + Operator(BITSOperator), +} + +#[derive(Debug)] +pub struct BITSPacket { + version: u8, + packet_type: u8, + inner: BITSValue, +} + +#[derive(Debug)] +struct ParsedPacket { + bits_parsed: usize, + packet: BITSPacket, +} + +fn parse_packet(bytes: &[u8], packet_start_bit: usize) -> Result { + println!("PARSE PACKET at {}", packet_start_bit); + let version = parse_bytes_from_bits(bytes, packet_start_bit, 3)?; + let packet_type = parse_bytes_from_bits(bytes, packet_start_bit + 3, 3)?; + println!("Processing packet with version {0} of type {2} at bit_index {1}", version, packet_start_bit, packet_type); + if packet_type == 4 { + let mut bit_index = packet_start_bit + 6; + let mut value = 0u64; + loop { + let value_segment = parse_bytes_from_bits(bytes, bit_index, 5)?; + value = (value << 4) & (value_segment & 0b1111); + println!("Literal Value Segment at {}...", bit_index); + bit_index += 5; + if value_segment < 0b10000u64 { break; } + } + println!("PARSE PACKET DONE (at {})", packet_start_bit); + Ok(ParsedPacket { + bits_parsed: bit_index - packet_start_bit, + packet: BITSPacket { + version: version.to_le_bytes()[0], + packet_type: packet_type.to_le_bytes()[0], + inner: BITSValue::LiteralValue(BITSLiteralValue { value }), + } + }) + } else if parse_bytes_from_bits(bytes, packet_start_bit + 6, 1)? == 0u64 { + println!("Operator..."); + let length = parse_bytes_from_bits(bytes, packet_start_bit + 7, 15)?; + println!("Length in bits: {}", length); + // ignore subpackets parsing in this case + // 7 bits for the version, type, and length_type header, 15 bits of length, and + // 8 bits to ensure we're on the next packet boundary + let mut subpacket_bits = 0usize; + let mut subpackets = vec![]; + while u64::try_from(subpacket_bits).unwrap() < length { + let pp = parse_packet(bytes, packet_start_bit + 7 + 15 + subpacket_bits)?; + subpacket_bits += pp.bits_parsed; + subpackets.push(pp.packet); + } + println!("PARSE PACKET DONE (at {})", packet_start_bit); + Ok(ParsedPacket { + bits_parsed: 7 + 15 + subpacket_bits, + packet: BITSPacket { + version: version.to_le_bytes()[0], + packet_type: packet_type.to_le_bytes()[0], + inner: BITSValue::Operator(BITSOperator { + length_type: BITSLengthType::SubpacketCount, + length, + subpackets, + }), + } + }) + } else { + println!("Operator..."); + // 11 bits of length + let length = parse_bytes_from_bits(bytes, packet_start_bit + 7, 11)?; + println!("Length in subpackets: {}", length); + let mut bit_index = packet_start_bit + 7 + 11; + let mut subpackets = vec![]; + for _ in 0..length { + let pp = parse_packet(bytes, bit_index)?; + bit_index += pp.bits_parsed; + subpackets.push(pp.packet); + } + println!("PARSE PACKET DONE (at {})", packet_start_bit); + // 7 bits for the version, type, and length_type header, 11 bits of length, and + // 8 bits to ensure we're on the next packet boundary + Ok(ParsedPacket { + bits_parsed: bit_index - packet_start_bit, + packet: BITSPacket { + version: version.to_le_bytes()[0], + packet_type: packet_type.to_le_bytes()[0], + inner: BITSValue::Operator(BITSOperator { + length_type: BITSLengthType::SubpacketBits, + length, + subpackets, + }), + } + }) + } +} + +/// # Panics +/// anytime I want +pub fn packet_version_sum(packet: BITSPacket) -> u64 { + // bytes.iter().for_each(|b| println!("{:#010b}", b)); + if let BITSValue::Operator(p) = packet.inner { + let mut result = 0u64; + for p in p.subpackets { + result += packet_version_sum(p); + } + result + u64::from(packet.version) + } else { + u64::from(packet.version) + } +} + +fn main() { + let bytes = hex_string_to_bytes(&day_input(16)); + bytes.iter().for_each(|b| println!("{:#010b}", b)); + let packet = parse_packet(&bytes, 0).unwrap().packet; + // println!("{:#?}", packet); + let version_sum = packet_version_sum(packet); + println!("Sum of packet versions: {:?}", version_sum); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn packet_version_sum_examples() { + let tester = |s: &str| packet_version_sum(parse_packet(&hex_string_to_bytes(&s.to_string()), 0).unwrap().packet); + assert_eq!(tester("8A004A801A8002F478"), 16); + assert_eq!(tester("620080001611562C8802118E34"), 12); + assert_eq!(tester("C0015000016115A2E0802F182340"), 23); + assert_eq!(tester("A0016C880162017C3686B18A3D4780"), 31); + } + + #[test] + fn parse_bytes_from_bits_examples() { + let v: Vec = vec![0b0011_0011, 0b0100_1111, 0b1101_1011, 0b1001_1010]; + // assert_eq!(0b1100_1011u8 >> 9u8, 0b0); + // assert_eq!(0b1100_1011u8 >> 8u8, 0b0); + assert_eq!(0b1100_1011u8 >> 7u8, 0b1); + assert_eq!(parse_bytes_from_bits(&v, 2, 1), Ok(0b1)); + assert_eq!(parse_bytes_from_bits(&v, 2, 2), Ok(0b11)); + assert_eq!(parse_bytes_from_bits(&v, 1, 2), Ok(0b01)); + assert_eq!(parse_bytes_from_bits(&v, 0, 3), Ok(0b001)); + assert_eq!(parse_bytes_from_bits(&v, 0, 8), Ok(0b0011_0011u64)); + assert_eq!(parse_bytes_from_bits(&v, 2, 8), Ok(0b1100_1101u64)); + assert_eq!(parse_bytes_from_bits(&v, 6, 8), Ok(0b1101_0011u64)); + assert_eq!(parse_bytes_from_bits(&v, 7, 10), Ok(0b10_1001_1111u64)); + assert_eq!(parse_bytes_from_bits(&v, 7, 3), Ok(0b101u64)); + assert_eq!(parse_bytes_from_bits(&v, 6, 3), Ok(0b110u64)); + assert_eq!(parse_bytes_from_bits(&v, 22, 3), Ok(0b111u64)); + assert_eq!(parse_bytes_from_bits(&v, 7, 20), Ok(0b1010_0111_1110_1101_1100u64)); + assert_eq!(parse_bytes_from_bits(&v, 15, 12), Ok(0b1110_1101_1100u64)); + assert_eq!(parse_bytes_from_bits(&v, 18, 3), Ok(0b011u64)); + } +} diff --git a/2021/rust/main.rs b/2021/rust/main.rs new file mode 100644 index 0000000..8be26d2 --- /dev/null +++ b/2021/rust/main.rs @@ -0,0 +1 @@ +// empty since all the source actually lives in each day