Question

Phew… This took a lot of googling to get right. Working with unstructured data in Go wasn’t as simple as I’m used to from JS. Having said that, I think the outcome is quite alright.

The communication device is still acting up… We receive a distress signal but it seems like the packets are arriving out of order (our puzzle input).

Packet pairs are separated by an empty line and the packet data is represented as integers and a list of integers.

Example

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 `````` ``````[1,1,3,1,1] [1,1,5,1,1] [[1],[2,3,4]] [[1],4] [9] [[8,7,6]] [[4,4],4,4] [[4,4],4,4,4] [7,7,7,7] [7,7,7] [] [3] [[[]]] [[]] [1,[2,[3,[4,[5,6,7]]]],8,9] [1,[2,[3,[4,[5,6,0]]]],8,9] ``````

## Parsing#

Splitting parsing into two parts:

1. Parse the raw input into pairs of packets
2. Deserialize each packet

### Parse the raw data into packets pairs#

From the question description, we know that packets are separated into pairs that and each pair is separated by an empty line Within a pair, each packet is on its on line.

 ``````1 2 3 4 5 6 `````` `````` var packets []string packetPairs := strings.Split(string(raw), "\n\n") for _, l := range packetPairs { parts := strings.Split(l, "\n") packets = append(packets, parts...) } ``````

At this stage, our data looks something like this

 ``````1 2 3 4 5 `````` ``````// example packet pair [1,1,3,1,1] [1,1,5,1,1] [["[1,1,3,1,1]", "[1,1,5,1,1]"], ...//other pairs] ``````

### Deserialize each packet#

Let’s take that string and construct something meaningful from that, in our case, it’s going to be an array of integers and arrays.
Luckily for us, the packets are a valid JSON string, we can use Go built-in json.Unmarshal function.

The `json.Unmarshal` accepts our JSON string in byte formats and some variables to assign the results to. Ideally, we would like the variable we assign the results to have a type that reflects that recursive nature but as far as I can tell it’s not possible in go.

 ``````1 2 3 `````` ``````// Ideal type type intOrArrayOfInt = [](int | intOrArrayOfInt) type Deserialized = intOrArrayOfInt ``````

Instead, we will assign the results to variables of type `any` and handle the different types in our logic using type assertion. Our `parse` function now looks like this

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 `````` ``````func parse(raw string) []any { var packets []any packetPairs := strings.Split(string(raw), "\n\n") for _, l := range packetPairs { var left, right any parts := strings.Split(l, "\n") json.Unmarshal([]byte(parts[0]), &left) json.Unmarshal([]byte(parts[1]), &right) packets = append(packets, left, right) } return packets } ``````

## Part 1#

We are asked to find valid pairs of packets and sum up their indexes.
But what exactly is an invalid pair of packets you ask?!

For each index `i` in our pair of packets, we need to make sure that the following holds There is some index `i` such that `left[i] < right[i]`, if at any index `j` where `j < i` we find that `left[j] > right[j]` or we run out of items in our `right` packet before our `left` we say that that packet pair as invalid!

Example

 `````` 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 `````` ``````// The "nothing special" pair == Pair 1 == - Compare [1,1,3,1,1] vs [1,1,5,1,1] - Compare 1 vs 1 - Compare 1 vs 1 - Compare 3 vs 5 - Left side is smaller, so inputs are in the right order ... ... // The "my right side numbers are smaller" pair == Pair 3 == - Compare [9] vs [[8,7,6]] - Compare 9 vs [8,7,6] - Mixed types; convert left to [9] and retry comparison - Compare [9] vs [8,7,6] - Compare 9 vs 8 - Right side is smaller, so inputs are not in the right order // The "my left side is the same as your right side and we are all human beings" pair == Pair 4 == - Compare [[4,4],4,4] vs [[4,4],4,4,4] - Compare [4,4] vs [4,4] - Compare 4 vs 4 - Compare 4 vs 4 - Compare 4 vs 4 - Compare 4 vs 4 - Left side ran out of items, so inputs are in the right order // The "my right side is shorter, fuck!" pair == Pair 5 == - Compare [7,7,7,7] vs [7,7,7] - Compare 7 vs 7 - Compare 7 vs 7 - Compare 7 vs 7 - Right side ran out of items, so inputs are not in the right order ``````

Now we need to write the actual logic that enforces those rules. The first thing we notice is that on equal numbers we keep going until we find a violation or we succeed, this means that a simple `true`/`false` return value is not sufficient. We need a way to say `lower`, `equal`, and `greater`, we can do just that using simple subtraction and setting `0` as our `equal` point, and `greater` marks an invalid packet pair

We will start by writing a `compare` function that takes `left` and `right` as arguments and using type assertion validate that they are of type `array`.

 ``````1 2 3 4 5 `````` ``````func compare(left, right any) int { leftArr, isLeftArray := left.([]any) rightArr, isRightArray := right.([]any) ... } ``````

We will build our logic based on `isLeftArray` and `isRightArray`, they represent the success or failure of the assertion, basically answering the question “Is it an array of any type?”

We now have 5 cases to deal with

1. Both are not arrays -> return `left` - `right`
2. Only `left` is not an array -> wrap `left` in an array and call `compare`
3. Only `right` is not an array -> wrap `right` in an array and call `compare`
4. Both are not arrays -> call `compare` for each element in range
5. We run out of items -> subtract the `len` of each side
 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 `````` ``````func compare(left, right any) int { leftArr, isLeftArray := left.([]any) rightArr, isRightArray := right.([]any) if !isLeftArray && !isRightArray { return int(left.(float64) - right.(float64)) } else if !isLeftArray { return compare([]any{left}, right) } else if !isRightArray { return compare(left, []any{right}) } for i := 0; i < util.Min(len(leftArr), len(rightArr)); i++ { res := compare(leftArr[i], rightArr[i]) // at some point we either found an invalid pair of numbers or a valid one, either way, we will stop and return the result. if res != 0 { return res } } return len(leftArr) - len(rightArr) } ``````

When both `left` and `right` are not arrays we cast them to `float64` since that’s the actual type the deserialization gave us back and not an `int`

We have a working `compare` function, let’s utilize it in our solution

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 `````` `````` func Part1(raw string) int { packets := parse(raw) sum := 0 for i := 0; i < len(packets)-1; i += 2 { rs := compare(packets[i], packets[i+1]) if rs <= 0 { sum += int((i + } } return sum } ``````

Adding 2 to each index to compensate for the fact that we start from zero and then divide by 2 to account for `i` jumping 2 steps on each iteration

## Part 2#

Now, you just need to put all of the packets in the right order. Disregard the blank lines in your list of received packets.

We are asked to add two divider packets `[[2]]` and `[[6]]` to our received packets, then sort our packets in the right order according to the rules described in part 1. After we sort our packets we need to find the divider packets indexes and multiply them together to get our answer for part 2. easy peasy!

Luckily for us, we created our compare function in a way we can easily reuse to sort our packets.

 `````` 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 `````` ``````func stringifyPacket(p any) string { packet, _ := json.Marshal(p) return string(packet) } func Part2(raw string) int { packets := parse(raw) div1, div2 := []any{[]any{float64(2)}}, []any{[]any{float64(6)}} div1Str, div2Str := stringifyPacket(div1), stringifyPacket(div2) packets = append(packets, div1, div2) sort.Slice(packets, func(i, j int) bool { return compare(packets[i], packets[j]) < 0 }) res := 1 for i, p := range packets { if len(p.([]any)) == 1 { packet := stringifyPacket(p) if packet == div1Str || packet == div2Str { res *= i + 1 } } } return res } ``````

The only other thing to note here is how we find the divider packets, what I did is pretty shitty but all of those types gave me a major headache and I ended up stratifying the packets and comparing the actual strings.
A better solution will be to maybe pack them in a struct and assign IDs, pass pointers instead of values so we can compare the memory addresses or assert the length is one and the values are either 2 or 6.

That’s it for today, see you tomorrow ⭐️

You can find the complete code here Thanks for reading!

This post is number 13 of a 13 posts series > Learning Go.