aoc-2021-rust

git clone https://git.sinitax.com/sinitax/aoc-2021-rust
Log | Files | Refs | README | sfeed.txt

commit c8bc42ff2d9e9aa8eaa1c067d1a9fbf85af1254b
parent cf1948d3dd6efeea04d89a81adb9c71cf3f76132
Author: Louis Burda <quent.burda@gmail.com>
Date:   Thu, 13 Apr 2023 23:38:39 -0400

Add day 16 solution

Diffstat:
Asrc/16/Cargo.toml | 10++++++++++
Asrc/16/input | 2++
Asrc/16/part1 | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/16/part2 | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/16/src/main.rs | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/Makefile | 2+-
6 files changed, 477 insertions(+), 1 deletion(-)

diff --git a/src/16/Cargo.toml b/src/16/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day16" +version = "0.1.0" +edition = "2021" + +[dependencies] +aoc = { path = "../common/aoc" } +num = "0.4.0" +num-derive = "0.3.3" +num-traits = "0.2.15" diff --git a/src/16/input b/src/16/input @@ -0,0 +1,2 @@ +60556F980272DCE609BC01300042622C428BC200DC128C50FCC0159E9DB9AEA86003430BE5EFA8DB0AC401A4CA4E8A3400E6CFF7518F51A554100180956198529B6A700965634F96C0B99DCF4A13DF6D200DCE801A497FF5BE5FFD6B99DE2B11250034C00F5003900B1270024009D610031400E70020C0093002980652700298051310030C00F50028802B2200809C00F999EF39C79C8800849D398CE4027CCECBDA25A00D4040198D31920C8002170DA37C660009B26EFCA204FDF10E7A85E402304E0E60066A200F4638311C440198A11B635180233023A0094C6186630C44017E500345310FF0A65B0273982C929EEC0000264180390661FC403006E2EC1D86A600F43285504CC02A9D64931293779335983D300568035200042A29C55886200FC6A8B31CE647880323E0068E6E175E9B85D72525B743005646DA57C007CE6634C354CC698689BDBF1005F7231A0FE002F91067EF2E40167B17B503E666693FD9848803106252DFAD40E63D42020041648F24460400D8ECE007CBF26F92B0949B275C9402794338B329F88DC97D608028D9982BF802327D4A9FC10B803F33BD804E7B5DDAA4356014A646D1079E8467EF702A573FAF335EB74906CF5F2ACA00B43E8A460086002A3277BA74911C9531F613009A5CCE7D8248065000402B92D47F14B97C723B953C7B22392788A7CD62C1EC00D14CC23F1D94A3D100A1C200F42A8C51A00010A847176380002110EA31C713004A366006A0200C47483109C0010F8C10AE13C9CA9BDE59080325A0068A6B4CF333949EE635B495003273F76E000BCA47E2331A9DE5D698272F722200DDE801F098EDAC7131DB58E24F5C5D300627122456E58D4C01091C7A283E00ACD34CB20426500BA7F1EBDBBD209FAC75F579ACEB3E5D8FD2DD4E300565EBEDD32AD6008CCE3A492F98E15CC013C0086A5A12E7C46761DBB8CDDBD8BE656780 + diff --git a/src/16/part1 b/src/16/part1 @@ -0,0 +1,165 @@ +--- Day 16: Packet Decoder --- + +As you leave the cave and reach open waters, you receive a transmission from the Elves back on the +ship. + +The transmission was sent using the Buoyancy Interchange Transmission System (BITS), a method of +packing numeric expressions into a binary sequence. Your submarine's computer has saved the +transmission in hexadecimal (your puzzle input). + +The first step of decoding the message is to convert the hexadecimal representation into binary. +Each character of hexadecimal corresponds to four bits of binary data: + +0 = 0000 +1 = 0001 +2 = 0010 +3 = 0011 +4 = 0100 +5 = 0101 +6 = 0110 +7 = 0111 +8 = 1000 +9 = 1001 +A = 1010 +B = 1011 +C = 1100 +D = 1101 +E = 1110 +F = 1111 + +The BITS transmission contains a single packet at its outermost layer which itself contains many +other packets. The hexadecimal representation of this packet might encode a few extra 0 bits at the +end; these are not part of the transmission and should be ignored. + +Every packet begins with a standard header: the first three bits encode the packet +version, and the next three bits encode the packet type ID. These two values are numbers; all +numbers encoded in any packet are represented as binary with the most significant bit first. For +example, a version encoded as the binary sequence 100 represents the number 4. + +Packets with type ID 4 represent a literal value. Literal value packets encode a single binary +number. To do this, the binary number is padded with leading zeroes until its length is a multiple +of four bits, and then it is broken into groups of four bits. Each group is prefixed by a 1 bit +except the last group, which is prefixed by a 0 bit. These groups of five bits immediately follow +the packet header. For example, the hexadecimal string D2FE28 becomes: + +110100101111111000101000 +VVVTTTAAAAABBBBBCCCCC + +Below each bit is a label indicating its purpose: + + + - The three bits labeled V (110) are the packet version, 6. + + - The three bits labeled T (100) are the packet type ID, 4, which means the packet is a literal +value. + + - The five bits labeled A (10111) start with a 1 (not the last group, keep reading) and contain the +first four bits of the number, 0111. + + - The five bits labeled B (11110) start with a 1 (not the last group, keep reading) and contain +four more bits of the number, 1110. + + - The five bits labeled C (00101) start with a 0 (last group, end of packet) and contain the last +four bits of the number, 0101. + + - The three unlabeled 0 bits at the end are extra due to the hexadecimal representation and should +be ignored. + + +So, this packet represents a literal value with binary representation 011111100101, which is 2021 in +decimal. + +Every other type of packet (any packet with a type ID other than 4) represent an +operator that performs some calculation on one or more sub-packets contained within. Right now, the +specific operations aren't important; focus on parsing the hierarchy of sub-packets. + +An operator packet contains one or more packets. To indicate which subsequent binary data represents +its sub-packets, an operator packet can use one of two modes indicated by the bit immediately after +the packet header; this is called the length type ID: + + + - If the length type ID is 0, then the next 15 bits are a number that represents the total length +in bits of the sub-packets contained by this packet. + + - If the length type ID is 1, then the next 11 bits are a number that represents the +number of sub-packets immediately contained by this packet. + + +Finally, after the length type ID bit and the 15-bit or 11-bit field, the sub-packets appear. + +For example, here is an operator packet (hexadecimal string 38006F45291200) with length type ID 0 +that contains two sub-packets: + +00111000000000000110111101000101001010010001001000000000 +VVVTTTILLLLLLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBBBBBB + + + - The three bits labeled V (001) are the packet version, 1. + + - The three bits labeled T (110) are the packet type ID, 6, which means the packet is an operator. + + - The bit labeled I (0) is the length type ID, which indicates that the length is a 15-bit number +representing the number of bits in the sub-packets. + + - The 15 bits labeled L (000000000011011) contain the length of the sub-packets in bits, 27. + + - The 11 bits labeled A contain the first sub-packet, a literal value representing the number 10. + + - The 16 bits labeled B contain the second sub-packet, a literal value representing the number 20. + + +After reading 11 and 16 bits of sub-packet data, the total length indicated in L (27) is reached, +and so parsing of this packet stops. + +As another example, here is an operator packet (hexadecimal string EE00D40C823060) with length type +ID 1 that contains three sub-packets: + +11101110000000001101010000001100100000100011000001100000 +VVVTTTILLLLLLLLLLLAAAAAAAAAAABBBBBBBBBBBCCCCCCCCCCC + + + - The three bits labeled V (111) are the packet version, 7. + + - The three bits labeled T (011) are the packet type ID, 3, which means the packet is an operator. + + - The bit labeled I (1) is the length type ID, which indicates that the length is a 11-bit number +representing the number of sub-packets. + + - The 11 bits labeled L (00000000011) contain the number of sub-packets, 3. + + - The 11 bits labeled A contain the first sub-packet, a literal value representing the number 1. + + - The 11 bits labeled B contain the second sub-packet, a literal value representing the number 2. + + - The 11 bits labeled C contain the third sub-packet, a literal value representing the number 3. + + +After reading 3 complete sub-packets, the number of sub-packets indicated in L (3) is reached, and +so parsing of this packet stops. + +For now, parse the hierarchy of the packets throughout the transmission and add up all of the +version numbers. + +Here are a few more examples of hexadecimal-encoded transmissions: + + + - 8A004A801A8002F478 represents an operator packet (version 4) which contains an operator packet +(version 1) which contains an operator packet (version 5) which contains a literal value (version +6); this packet has a version sum of 16. + + - 620080001611562C8802118E34 represents an operator packet (version 3) which contains two +sub-packets; each sub-packet is an operator packet that contains two literal values. This packet has +a version sum of 12. + + - C0015000016115A2E0802F182340 has the same structure as the previous example, but the outermost +packet uses a different length type ID. This packet has a version sum of 23. + + - A0016C880162017C3686B18A3D4780 is an operator packet that contains an operator packet that +contains an operator packet that contains five literal values; it has a version sum of +31. + + +Decode the structure of your hexadecimal-encoded BITS transmission; what do you get if you add up +the version numbers in all packets? + + diff --git a/src/16/part2 b/src/16/part2 @@ -0,0 +1,61 @@ +--- Part Two --- + +Now that you have the structure of your transmission decoded, you can calculate the value of the +expression it represents. + +Literal values (type ID 4) represent a single number as described above. The remaining type IDs are +more interesting: + + + - Packets with type ID 0 are sum packets - their value is the sum of the values of their +sub-packets. If they only have a single sub-packet, their value is the value of the sub-packet. + + - Packets with type ID 1 are product packets - their value is the result of multiplying together +the values of their sub-packets. If they only have a single sub-packet, their value is the value of +the sub-packet. + + - Packets with type ID 2 are minimum packets - their value is the minimum of the values of their +sub-packets. + + - Packets with type ID 3 are maximum packets - their value is the maximum of the values of their +sub-packets. + + - Packets with type ID 5 are greater than packets - their value is 1 if the value of the first +sub-packet is greater than the value of the second sub-packet; otherwise, their value is +0. These packets always have exactly two sub-packets. + + - Packets with type ID 6 are less than packets - their value is 1 if the value of the first +sub-packet is less than the value of the second sub-packet; otherwise, their value is +0. These packets always have exactly two sub-packets. + + - Packets with type ID 7 are equal to packets - their value is 1 if the value of the first +sub-packet is equal to the value of the second sub-packet; otherwise, their value is 0. These +packets always have exactly two sub-packets. + + +Using these rules, you can now work out the value of the outermost packet in your BITS transmission. + +For example: + + + - C200B40A82 finds the sum of 1 and 2, resulting in the value 3. + + - 04005AC33890 finds the product of 6 and 9, resulting in the value 54. + + - 880086C3E88112 finds the minimum of 7, 8, and 9, resulting in the value 7. + + - CE00C43D881120 finds the maximum of 7, 8, and 9, resulting in the value 9. + + - D8005AC2A8F0 produces 1, because 5 is less than 15. + + - F600BC2D8F produces 0, because 5 is not greater than 15. + + - 9C005AC2F8F0 produces 0, because 5 is not equal to 15. + + - 9C0141080250320F1802104A08 produces 1, because 1 + 3 = 2 * 2. + + +What do you get if you evaluate the expression represented by your hexadecimal-encoded BITS +transmission? + + diff --git a/src/16/src/main.rs b/src/16/src/main.rs @@ -0,0 +1,238 @@ +use std::mem::ManuallyDrop; + +#[derive(PartialEq,Eq,Copy,Clone,num_derive::FromPrimitive)] +enum PacketType { + Sum = 0, + Product = 1, + Minimum = 2, + Maximum = 3, + Literal = 4, + GreaterThan = 5, + LessThan = 6, + EqualTo = 7 +} + +#[derive(PartialEq,Eq,Clone)] +enum PacketSizeType { + Count, + Len +} + +#[derive(Clone)] +struct LiteralPacket { + val: usize +} + +#[derive(Clone,Copy)] +union PacketSize { + cnt: usize, + len: usize +} + +#[derive(Clone)] +struct OperatorPacket { + sztype: PacketSizeType, + size: PacketSize, + sub: Vec<usize> +} + +union PacketUnion { + lit: ManuallyDrop<LiteralPacket>, + op: ManuallyDrop<OperatorPacket> +} + +struct Packet { + ver: u8, + typ: PacketType, + u: PacketUnion, +} + +impl Clone for Packet { + fn clone(&self) -> Self { + let pkt_u: PacketUnion; + if self.typ == PacketType::Literal { + pkt_u = PacketUnion { lit: unsafe { self.u.lit.clone() } }; + } else { + pkt_u = PacketUnion { op: unsafe { self.u.op.clone() } }; + } + return Packet { ver: self.ver, typ: self.typ, u: pkt_u }; + } +} + +struct ParseState { + pkt: usize, + idx: usize, + start: usize +} + +struct EvalState { + pkt: usize, + ins: Vec<usize> +} + +fn bitstr(hexstr: &str) -> String { + let conv = |i| u8::from_str_radix(&hexstr[i..i+2], 16).unwrap(); + let bytes: Vec<u8> = (0..hexstr.len()).step_by(2).map(conv).collect(); + bytes.iter().map(|b| format!("{b:08b}")).collect::<Vec<String>>().join("") +} + +fn decode_packet(bitstr: &str) -> (Packet, usize) { + aoc::debugln!("{}", bitstr); + let pver = u8::from_str_radix(&bitstr[0..3], 2).unwrap(); + let ptype_val = u8::from_str_radix(&bitstr[3..6], 2).unwrap(); + let pu: PacketUnion; + let mut cnt: usize = 0; + let ptype: PacketType = num::FromPrimitive::from_u8(ptype_val).unwrap(); + if ptype == PacketType::Literal { + let mut imm: usize = 0; + for i in (6..bitstr.len()).step_by(5) { + imm = (imm << 4) | usize::from_str_radix(&bitstr[i+1..i+5], 2).unwrap(); + if bitstr[i..].chars().next().unwrap() == '0' { + cnt = i + 5; + break; + } + } + let lit = ManuallyDrop::new(LiteralPacket { val: imm }); + pu = PacketUnion { lit }; + } else { + let szbit = bitstr[6..].chars().next().unwrap(); + let sztype: PacketSizeType; + let size: PacketSize; + if szbit == '1' { + sztype = PacketSizeType::Count; + let imm = usize::from_str_radix(&bitstr[7..7+11], 2).unwrap(); + size = PacketSize { cnt: imm }; + cnt = 18; + } else { + sztype = PacketSizeType::Len; + let imm = usize::from_str_radix(&bitstr[7..7+15], 2).unwrap(); + size = PacketSize { len: imm }; + cnt = 22; + } + let op = ManuallyDrop::new(OperatorPacket { sztype, size, sub: Vec::new() }); + pu = PacketUnion { op }; + } + return (Packet { ver: pver, typ: ptype, u: pu }, cnt); +} + +fn decode_packets(input: &str) -> Vec<Packet> { + let mut packets: Vec<Packet> = Vec::new(); + let mut stack: Vec<ParseState> = Vec::new(); + + let bits = bitstr(input.lines().next().unwrap()); + let mut pos: usize = 0; + while !stack.is_empty() || pos + 8 <= bits.len() { + if !stack.is_empty() { + let state = stack.last_mut().unwrap(); + let op = unsafe { &*packets.get_mut(state.pkt).unwrap().u.op }; + let done = op.sztype == PacketSizeType::Count + && state.idx >= unsafe { op.size.cnt } + || op.sztype == PacketSizeType::Len + && pos - state.start >= unsafe { op.size.len }; + if done { + assert!(op.sztype == PacketSizeType::Count + && state.idx == unsafe { op.size.cnt } + || op.sztype == PacketSizeType::Len + && pos - state.start == unsafe { op.size.len }); + stack.pop(); + continue; + } + } + + let pkt_idx: usize; + let (m_pkt, skip) = decode_packet(&bits[pos..]); + packets.push(m_pkt); + pkt_idx = packets.len() - 1; + + let ptype = packets.last().unwrap().typ; + let pver = packets.last().unwrap().ver; + aoc::debugln!("PKT {} {}", ptype as u8, pver); + + if !stack.is_empty() { + let mut state = stack.last_mut().unwrap(); + /* we are not necessarily done with the packet at this point + * (for example if this is an OperatorPacket) but will be as + * soon as this state is at the top of the stack again */ + state.idx += 1; + let pkt = packets.get_mut(state.pkt).unwrap(); + assert!(pkt.typ != PacketType::Literal); + let op = unsafe { &mut pkt.u.op }; + op.sub.push(pkt_idx); + } + + if ptype != PacketType::Literal { + stack.push(ParseState { pkt: pkt_idx, idx: 0, start: pos + skip }); + } + + pos += skip; + } + + aoc::debugln!("REST: {}", &bits[pos..]); + + return packets; +} + +fn eval_packet(ptype: PacketType, ins: &Vec<usize>) -> usize { + match ptype { + PacketType::Literal => ins[0], + PacketType::Sum => ins.iter().sum(), + PacketType::Product => ins.iter().fold(1, |a, b| a * b), + PacketType::Minimum => *ins.iter().min().unwrap(), + PacketType::Maximum => *ins.iter().max().unwrap(), + PacketType::LessThan => if ins[0] < ins[1] { 1 } else { 0 }, + PacketType::GreaterThan => if ins[0] > ins[1] { 1 } else { 0 }, + PacketType::EqualTo => if ins[0] == ins[1] { 1 } else { 0 }, + } +} + +fn eval_packets(packets: &Vec<Packet>) -> usize { + let mut stack: Vec<EvalState> = Vec::new(); + stack.push(EvalState { pkt: 0, ins: Vec::new() }); + while !stack.is_empty() { + let top = stack.last().unwrap(); + let pkt = packets.get(top.pkt).unwrap(); + aoc::debugln!("packet {}", pkt.typ as u8); + if pkt.typ == PacketType::Literal { + stack.pop(); + assert!(!stack.is_empty()); + let top = stack.last_mut().unwrap(); + top.ins.push(unsafe { pkt.u.lit.val }); + } else { + let op = unsafe { &pkt.u.op }; + let len = op.sub.len(); + let idx = top.ins.len(); + aoc::debugln!("=> op {} {}", idx, len); + assert!(len != 0); + if idx < len { + let child = *op.sub.get(idx).unwrap(); + stack.push(EvalState { pkt: child, ins: Vec::new() }); + } else { + let res = eval_packet(pkt.typ, &top.ins); + stack.pop(); + if stack.is_empty() { return res; } + let top = stack.last_mut().unwrap(); + top.ins.push(res); + } + } + } + return 0; +} + +fn part1(aoc: &mut aoc::Info) { + let packets = decode_packets(&aoc.input); + let answer = packets.iter().map(|p| p.ver as usize).sum::<usize>(); + aoc.answer = Some(format!("{}", answer)); + aoc.solution = Some("871"); +} + +fn part2(aoc: &mut aoc::Info) { + let packets = decode_packets(&aoc.input); + let answer = eval_packets(&packets); + aoc.answer = Some(format!("{}", answer)); + aoc.solution = Some("68703010504"); +} + +fn main() { + aoc::run(part1, part2); +} + diff --git a/src/Makefile b/src/Makefile @@ -21,7 +21,7 @@ $1/run1: $1/main $1/run2: $1/main @cd $1 && time ./main 2 -$1/run: +$1/run: $1/main @echo "== day $1 ==" @echo -en "\npart 1: " && cd $1 && time ./main 1 @echo -en "\npart 2: " && cd $1 && time ./main 2