Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions _2021/AoC2021.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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' }
}
}
138 changes: 124 additions & 14 deletions _2021/Day 16 Packet Decoder.ps1
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
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"