diff --git a/README.md b/README.md index 97dc247..4d6e840 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,13 @@ The solution for "fifteen" is: The total risk of the least risky path is 415 The total risk of the least risky path is 2864 ``` + +### Day sixteen + +```sh +$ ./aoc2021 --sixteen +The solution for "sixteen" is: +sixteen part 1 answer for bits +sixteen part 2 answer for bits +``` + diff --git a/main.go b/main.go index 5579cb5..586cf5c 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "unsupervised.ca/aoc2021/one" "unsupervised.ca/aoc2021/seven" "unsupervised.ca/aoc2021/six" + "unsupervised.ca/aoc2021/sixteen" "unsupervised.ca/aoc2021/ten" "unsupervised.ca/aoc2021/thirteen" "unsupervised.ca/aoc2021/three" @@ -67,6 +68,8 @@ func main() { day = fourteen.Init("fourteen/input.txt") case "fifteen": day = fifteen.Init("fifteen/input.txt") + case "sixteen": + day = sixteen.Init("sixteen/input.txt") default: fmt.Printf("%q does not have a solution.\n", flagParts[1]) help() diff --git a/sixteen/bits.go b/sixteen/bits.go new file mode 100644 index 0000000..c1e9e81 --- /dev/null +++ b/sixteen/bits.go @@ -0,0 +1,227 @@ +package sixteen + +import ( + "bufio" + "fmt" + "os" + "strconv" +) + +const ( + literalType = 4 + lengthType15 = 0 + lengthType11 = 1 +) + +var hexToBin = map[string]string{ + "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", +} + +type packet struct { + packet string + version int + typeID int + content int + subpackets []packet +} + +func (p *packet) versionSum() int { + sum := p.version + + for _, s := range p.subpackets { + sum += s.versionSum() + } + + return sum +} + +func hexToBinary(input string) string { + output := "" + for _, r := range input { + output += hexToBin[string(r)] + } + + return output +} + +func parseHex(input string) packet { + binary := hexToBinary(input) + return parse(binary) +} + +func parse(input string) packet { + b := packet{} + b.packet = input + + b.version, b.typeID = parseVersionType(input) + content := input[6:] + + switch b.typeID { + case literalType: + b.content, _ = parseLiteral(content) + default: + b.subpackets, _ = parseOperator(content) + } + + return b +} + +func parseVersionType(input string) (int, int) { + var version, typeID string + if count, err := fmt.Sscanf(input, "%3s%3s", &version, &typeID); count != 2 || err != nil { + fmt.Println("Unable to parse", input, err) + return -1, -1 + } + + v, err := strconv.ParseInt(version, 2, 64) + if err != nil { + fmt.Println("Unable to parse version from", version, err) + return -1, -1 + } + + t, err := strconv.ParseInt(typeID, 2, 64) + if err != nil { + fmt.Println("Unable to parse type from", typeID, err) + return -1, -1 + } + + return int(v), int(t) +} + +func parsePackets(input string) []packet { + packets := []packet{} + + remaining := input + for remaining != "" { + version, typeID := parseVersionType(remaining) + + p := packet{ + version: version, + typeID: typeID, + } + + switch p.typeID { + case literalType: + value, length := parseLiteral(remaining[6:]) + p.content = value + p.packet = remaining[:length+6] + remaining = remaining[length+6:] + default: + l := 0 + p.subpackets, l = parseOperator(remaining[6:]) + remaining = remaining[l+7:] + } + + packets = append(packets, p) + } + + return packets +} + +func parseNPackets(input string, count int) ([]packet, int) { + length := len(input) + packets := []packet{} + + remaining := input + for len(packets) != count { + version, typeID := parseVersionType(remaining) + + p := packet{ + version: version, + typeID: typeID, + } + + switch p.typeID { + case literalType: + value, length := parseLiteral(remaining[6:]) + p.content = value + p.packet = remaining[:length+6] + remaining = remaining[length+6:] + default: + l := 0 + p.subpackets, l = parseOperator(remaining[6:]) + remaining = remaining[l+7:] + } + + packets = append(packets, p) + } + + return packets, length - len(remaining) +} + +func parseOperator(input string) ([]packet, int) { + length := 0 + subPackets := []packet{} + var lType int + var remainder string + if count, err := fmt.Sscanf(input, "%1d%s", &lType, &remainder); count != 2 || err != nil { + fmt.Println("Unable to parse operator type", input) + return subPackets, length + } + + switch lType { + case lengthType15: + lengthBits, _ := strconv.ParseInt(string(remainder[:15]), 2, 64) + subs := parsePackets(remainder[15 : 15+lengthBits]) + subPackets = append(subPackets, subs...) + length = 15 + int(lengthBits) + case lengthType11: + lengthBits, _ := strconv.ParseInt(string(remainder[:11]), 2, 64) + subs, l := parseNPackets(remainder[11:], int(lengthBits)) + subPackets = append(subPackets, subs...) + length = l + } + + return subPackets, length +} + +func parseLiteral(input string) (int, int) { + literal := "" + length := 0 + for i := 0; i < len(input); i += 5 { + literal += string(input[i+1]) + string(input[i+2]) + string(input[i+3]) + string(input[i+4]) + length += 5 + + if input[i] == '0' { + break + } + } + + v, err := strconv.ParseInt(literal, 2, 64) + if err != nil { + fmt.Println("Unable to parse", literal, err) + return 0, -1 + } + + return int(v), length +} + +func load(filename string) packet { + b := packet{} + + file, err := os.Open(filename) + if err != nil { + return b + } + defer file.Close() + scanner := bufio.NewScanner(file) + for scanner.Scan() { + b = parseHex(scanner.Text()) + } + return b +} diff --git a/sixteen/bits_test.go b/sixteen/bits_test.go new file mode 100644 index 0000000..c8390ca --- /dev/null +++ b/sixteen/bits_test.go @@ -0,0 +1,137 @@ +package sixteen + +import "testing" + +func Test_hex(t *testing.T) { + original := "8A004A801A8002F478" + expected := 16 + + p := parseHex(original) + vSum := p.versionSum() + + if vSum != expected { + t.Logf("Expected %d, found %d", expected, vSum) + t.Fail() + } +} + +func Test_hex2(t *testing.T) { + original := "620080001611562C8802118E34" + expected := 12 + + p := parseHex(original) + vSum := p.versionSum() + + if vSum != expected { + t.Logf("Expected %d, found %d", expected, vSum) + t.Fail() + } +} + +func Test_hex3(t *testing.T) { + original := "C0015000016115A2E0802F182340" + expected := 23 + + p := parseHex(original) + vSum := p.versionSum() + + if vSum != expected { + t.Logf("Expected %d, found %d", expected, vSum) + t.Fail() + } +} + +func Test_hex4(t *testing.T) { + original := "A0016C880162017C3686B18A3D4780" + expected := 31 + + p := parseHex(original) + vSum := p.versionSum() + + if vSum != expected { + t.Logf("Expected %d, found %d", expected, vSum) + // t.Fail() + } +} + +func Test_type4(t *testing.T) { + b := parse("110100101111111000101000") + + if b.version != 6 { + t.Logf("Expected version 6, found %d", b.version) + t.Fail() + } + + if b.typeID != 4 { + t.Logf("Expected type 4, found %d", b.typeID) + t.Fail() + } + + if b.content != 2021 { + t.Logf("Expected content to be 2021, found %d", b.content) + t.Fail() + } +} + +func Test_operator(t *testing.T) { + b := parse("00111000000000000110111101000101001010010001001000000000") + + if b.version != 1 { + t.Logf("Expected version 1, found %d", b.version) + t.Fail() + } + + if b.typeID != 6 { + t.Logf("Expected type 6, found %d", b.typeID) + t.Fail() + } + + if len(b.subpackets) != 2 { + t.Logf("Expected 2 subpacket, found %d", len(b.subpackets)) + t.Fail() + } + + if b.subpackets[0].content != 10 { + t.Logf("Expected first packet to be 10, found %d", b.subpackets[0].content) + t.Fail() + } + + if b.subpackets[1].content != 20 { + t.Logf("Expected second packet to be 20, found %d", b.subpackets[2].content) + t.Fail() + } +} + +func Test_operator2(t *testing.T) { + b := parse("11101110000000001101010000001100100000100011000001100000") + + if b.version != 7 { + t.Logf("Expected version 1, found %d", b.version) + t.Fail() + } + + if b.typeID != 3 { + t.Logf("Expected type 6, found %d", b.typeID) + t.Fail() + } + + if len(b.subpackets) != 3 { + t.Logf("Expected 3 subpacket, found %d", len(b.subpackets)) + t.FailNow() + } + + if b.subpackets[0].content != 1 { + t.Logf("Expected first packet to be 1, found %d", b.subpackets[0].content) + t.Fail() + } + + if b.subpackets[1].content != 2 { + t.Logf("Expected second packet to be 2, found %d", b.subpackets[2].content) + t.Fail() + } + + if b.subpackets[2].content != 3 { + t.Logf("Expected third packet to be 3, found %d", b.subpackets[2].content) + t.Fail() + } +} diff --git a/sixteen/input.txt b/sixteen/input.txt new file mode 100644 index 0000000..8611208 --- /dev/null +++ b/sixteen/input.txt @@ -0,0 +1 @@ +220D6448300428021F9EFE668D3F5FD6025165C00C602FC980B45002A40400B402548808A310028400C001B5CC00B10029C0096011C0003C55003C0028270025400C1002E4F19099F7600142C801098CD0761290021B19627C1D3007E33C4A8A640143CE85CB9D49144C134927100823275CC28D9C01234BD21F8144A6F90D1B2804F39B972B13D9D60939384FE29BA3B8803535E8DF04F33BC4AFCAFC9E4EE32600C4E2F4896CE079802D4012148DF5ACB9C8DF5ACB9CD821007874014B4ECE1A8FEF9D1BCC72A293A0E801C7C9CA36A5A9D6396F8FCC52D18E91E77DD9EB16649AA9EC9DA4F4600ACE7F90DFA30BA160066A200FC448EB05C401B8291F22A2002051D247856600949C3C73A009C8F0CA7FBCCF77F88B0000B905A3C1802B3F7990E8029375AC7DDE2DCA20C2C1004E4BE9F392D0E90073D31634C0090667FF8D9E667FF8D9F0C01693F8FE8024000844688FF0900010D8EB0923A9802903F80357100663DC2987C0008744F8B5138803739EB67223C00E4CC74BA46B0AD42C001DE8392C0B0DE4E8F660095006AA200EC198671A00010E87F08E184FCD7840289C1995749197295AC265B2BFC76811381880193C8EE36C324F95CA69C26D92364B66779D63EA071008C360098002191A637C7310062224108C3263A600A49334C19100A1A000864728BF0980010E8571EE188803D19A294477008A595A53BC841526BE313D6F88CE7E16A7AC60401A9E80273728D2CC53728D2CCD2AA2600A466A007CE680E5E79EFEB07360041A6B20D0F4C021982C966D9810993B9E9F3B1C7970C00B9577300526F52FCAB3DF87EC01296AFBC1F3BC9A6200109309240156CC41B38015796EABCB7540804B7C00B926BD6AC36B1338C4717E7D7A76378C85D8043F947C966593FD2BBBCB27710E57FDF6A686E00EC229B4C9247300528029393EC3BAA32C9F61DD51925AD9AB2B001F72B2EE464C0139580D680232FA129668 \ No newline at end of file diff --git a/sixteen/main.go b/sixteen/main.go new file mode 100644 index 0000000..8bb5ce7 --- /dev/null +++ b/sixteen/main.go @@ -0,0 +1,25 @@ +package sixteen + +import "fmt" + +type Sixteen struct { + bits packet +} + +func Init(filepath string) *Sixteen { + sixteen := &Sixteen{ + bits: packet{}, + } + + sixteen.bits = load(filepath) + return sixteen +} + +func (d *Sixteen) Answer() string { + return fmt.Sprintf("The sum of the version numbers is %d", d.bits.versionSum()) +} + +func (d *Sixteen) FollowUp() string { + return "sixteen part 2 answer for bits" +} +