summaryrefslogtreecommitdiffstats
path: root/day16/__init__.py
blob: 66c0d363da7462610bc71e3d5c0c07019a61ae6a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import math
from abc import ABC
from dataclasses import dataclass
from typing import List, Tuple, Iterator, TypedDict, Dict, Union

from aoc import BaseAssignment

@dataclass
class Packet:
    raw: str
    version: int = None
    type: int = None
    length_type = str
    padding: int = 0
    payload_length = None
    payload_start = None
    payload: Union[int, List['Packet']] = None

    LITERAL = 4

    FIFTEEN = '0'
    ELEVEN = '1'

    PAYLOAD_LENGTH_TYPE = {
        FIFTEEN: 15,
        ELEVEN: 11
    }

    @classmethod
    def from_hex(cls, value: str) -> 'Packet':
        print()
        print(value)
        for byte in value:
            print(byte, bin(int(byte, 16))[2:].rjust(4, '0'))

        bits = ''.join([
            bin(int(byte, 16))[2:]
                .rjust(4, '0')
            for byte
            in value
        ])

        return cls(
            raw=bits
        )


    def __post_init__(self):
        self.version = int(self.raw[:3], 2)
        self.type = int(self.raw[3:6], 2)

        match self.type:
            case self.LITERAL:
                self.payload = self.parse_literal()
            case _:
                self.length_type = self.raw[6]
                self.payload_start = 8 + self.PAYLOAD_LENGTH_TYPE[self.length_type]
                self.payload_length = int(self.raw[8:self.payload_start], 2)

                print(self.raw[:3], self.raw[3:6], self.raw[7], self.raw[7:self.payload_start], self.raw[self.payload_start:])

                self.payload = self.parse_operator()


    def __len__(self):
        return len(self.raw) - self.padding

    def pad(self):
        added_zeroes = 4 - len(self.raw) % 4
        self.raw += added_zeroes * '0'
        self.padding = added_zeroes

    def parse_literal(self) -> int:
        start_index = 6

        end = False
        bits = []

        while not end:
            bits.append(self.raw[start_index + 1:start_index + 5])
            if self.raw[start_index] == '0':
                end = True

            start_index += 5

        self.raw = self.raw[:start_index]
        self.pad()

        return int(''.join(bits), 2)

    def parse_operator(self) -> List['Packet']:
        packets = []
        payload_end = self.payload_start + self.payload_length
        payload = self.raw[self.payload_start:payload_end]

        while len(payload) > 0:
            packets.append(Packet(raw=payload))
            payload = payload[len(packets[-1]):]

        self.raw = self.raw[:payload_end]
        self.pad()

        return packets


class Assignment(BaseAssignment, ABC):
    def parse_item(self, item: str) -> Packet:
        return Packet.from_hex(item)


    def read_input(self, example=False) -> Packet:
        return next(super().read_input(example))

class AssignmentOne(Assignment):
    example_result = 31

    def version_sum(self, packet: Packet):
        if packet.type == Packet.LITERAL:
            return packet.version

        return packet.version + sum(
            self.version_sum(p)
            for p in packet.payload
        )

    def run(self, input: Packet):
        return self.version_sum(input)