diff --git a/_2021/AoC2021.Tests.ps1 b/_2021/AoC2021.Tests.ps1 index e06527f..55721de 100644 --- a/_2021/AoC2021.Tests.ps1 +++ b/_2021/AoC2021.Tests.ps1 @@ -119,4 +119,41 @@ Describe 'Day 5 Hydro Venture' { Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq $expected } } } +} + +Describe 'Day 16 Packet Decoder' { + BeforeEach { Remove-Item "$PSScriptRoot/input16" -ErrorAction 'SilentlyContinue' } + + It 'Given example input, it solves Part 1 correctly for literal value' { + Out-File -InputObject 'D2FE28' "$PSScriptRoot/input16" + Mock Write-Warning { Write-Output $message } + $result = & "$PSScriptRoot/Day 16 Packet Decoder.ps1" + $result.Count | Should -Be 2 + Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq 'Part 1 - Version sum: 6' } + Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq 'Part 2 - Evaluated value: 2021' } + } + + It 'Given example input, it solves Part 1 correctly for operator packet' { + Out-File -InputObject '8A004A801A8002F478' "$PSScriptRoot/input16" + Mock Write-Warning { Write-Output $message } + $result = & "$PSScriptRoot/Day 16 Packet Decoder.ps1" + $result.Count | Should -Be 2 + Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq 'Part 1 - Version sum: 16' } + } + + It 'Given example input, it solves Part 2 correctly for sum operation' { + Out-File -InputObject 'C200B40A82' "$PSScriptRoot/input16" + Mock Write-Warning { Write-Output $message } + $result = & "$PSScriptRoot/Day 16 Packet Decoder.ps1" + $result.Count | Should -Be 2 + Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq 'Part 2 - Evaluated value: 3' } + } + + It 'Given example input, it solves Part 2 correctly for product operation' { + Out-File -InputObject '04005AC33890' "$PSScriptRoot/input16" + Mock Write-Warning { Write-Output $message } + $result = & "$PSScriptRoot/Day 16 Packet Decoder.ps1" + $result.Count | Should -Be 2 + Should -Invoke Write-Warning -Exactly 1 -Scope It -ParameterFilter { $message -eq 'Part 2 - Evaluated value: 54' } + } } \ No newline at end of file diff --git a/_2021/Day 16 Packet Decoder.ps1 b/_2021/Day 16 Packet Decoder.ps1 index 8febc8d..e8e06eb 100644 --- a/_2021/Day 16 Packet Decoder.ps1 +++ b/_2021/Day 16 Packet Decoder.ps1 @@ -1,4 +1,14 @@ -<# --- Day 16: Packet Decoder ---#> +<# --- 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. + +Part 1: Decode the structure of your hexadecimal-encoded BITS transmission; +what do you get if you add up the version numbers in all packets? + +Part 2: What do you get if you evaluate the expression represented by your hexadecimal-encoded BITS transmission? +#> $year, $day = 2021, 16 @@ -18,38 +28,138 @@ $binary = $string.ToCharArray().foreach({ [Convert]::ToString([Convert]::ToByte( # 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. -function decode_package ($package) +function decode_package ($package, $position = 0) { - Write-Debug "Decoding ${package}" - $version = [convert]::ToInt16($package.Substring(0, 3), 2) - Write-Debug $version - $type_ID = [convert]::ToInt16($package.Substring(3, 3), 2) - Write-Debug $type_ID + Write-Debug "Decoding ${package} at position ${position}" + $version = [convert]::ToInt16($package.Substring($position, 3), 2) + $version_sum = $version + Write-Debug "Version: $version" + $type_ID = [convert]::ToInt16($package.Substring($position + 3, 3), 2) + Write-Debug "Type ID: $type_ID" + $current_pos = $position + 6 + if ($type_ID -eq 4) { # literal value - $pointer = 1 $value = '' do { - $pointer += 5 - $value += $package.Substring($pointer + 1, 4) - } until ($package.Substring($pointer, 1) -eq '0') - return [convert]::ToInt16($value, 2) + $group = $package.Substring($current_pos, 5) + $continue_bit = $group.Substring(0, 1) + $value += $group.Substring(1, 4) + $current_pos += 5 + } while ($continue_bit -eq '1') + + $literal_value = [convert]::ToInt64($value, 2) + Write-Debug "Literal value: $literal_value" + return @{ + Version = $version + TypeID = $type_ID + Value = $literal_value + VersionSum = $version_sum + BitsConsumed = $current_pos - $position + } } else { # operator - if ($package.Substring($pointer, 1) -eq '0') + $length_type_ID = $package.Substring($current_pos, 1) + $current_pos += 1 + $sub_packets = @() + + if ($length_type_ID -eq '0') { # the next 15 bits are a number that represents the total length in bits of the sub-packets contained by this packet. + $sub_packets_length = [convert]::ToInt16($package.Substring($current_pos, 15), 2) + $current_pos += 15 + $sub_packets_end = $current_pos + $sub_packets_length + + Write-Debug "Operator with length type 0, sub-packets length: $sub_packets_length" + + while ($current_pos -lt $sub_packets_end) + { + $sub_packet = decode_package $package $current_pos + $sub_packets += $sub_packet + $version_sum += $sub_packet.VersionSum + $current_pos += $sub_packet.BitsConsumed + } } else { # the next 11 bits are a number that represents the number of sub-packets immediately contained by this packet. + $num_sub_packets = [convert]::ToInt16($package.Substring($current_pos, 11), 2) + $current_pos += 11 + + Write-Debug "Operator with length type 1, number of sub-packets: $num_sub_packets" + + for ($i = 0; $i -lt $num_sub_packets; $i++) + { + $sub_packet = decode_package $package $current_pos + $sub_packets += $sub_packet + $version_sum += $sub_packet.VersionSum + $current_pos += $sub_packet.BitsConsumed + } } + return @{ + Version = $version + TypeID = $type_ID + LengthTypeID = $length_type_ID + SubPackets = $sub_packets + VersionSum = $version_sum + BitsConsumed = $current_pos - $position + } } } -decode_package $binary \ No newline at end of file +function evaluate_packet ($packet) +{ + if ($packet.TypeID -eq 4) + { + # Literal value + return $packet.Value + } + else + { + # Operator packet - evaluate sub-packets first + $sub_values = $packet.SubPackets | ForEach-Object { evaluate_packet $_ } + + switch ($packet.TypeID) + { + 0 { # sum + return ($sub_values | Measure-Object -Sum).Sum + } + 1 { # product + $product = 1 + $sub_values | ForEach-Object { $product *= $_ } + return $product + } + 2 { # minimum + return ($sub_values | Measure-Object -Minimum).Minimum + } + 3 { # maximum + return ($sub_values | Measure-Object -Maximum).Maximum + } + 5 { # greater than + return [int]($sub_values[0] -gt $sub_values[1]) + } + 6 { # less than + return [int]($sub_values[0] -lt $sub_values[1]) + } + 7 { # equal to + return [int]($sub_values[0] -eq $sub_values[1]) + } + default { + throw "Unknown type ID: $($packet.TypeID)" + } + } + } +} + +# Parse the packet and calculate version sum for Part 1 +$packet = decode_package $binary +Write-Warning "Part 1 - Version sum: $($packet.VersionSum)" + +# Evaluate the packet for Part 2 +$result = evaluate_packet $packet +Write-Warning "Part 2 - Evaluated value: $result" \ No newline at end of file