1

Introduction

2

This year, several of us at Mojang decided to participate in the Advent of Code. It was the first time for me, and I

3

chose Go as my programming language. Inevitably, choosing a static language meant spending a bit more time each day

4

on input parsing. In this posts I will share some insights from day 13.

5

6

Problem description

7

First, lets have a look at the problem description:

8

9
Your handheld device must still not be working properly; the packets from the distress signal got decoded out of order.
10
You'll need to re-order the list of received packets (your puzzle input) to decode the message. Packet data consists of
11
lists and integers. Each list starts with [, ends with ], and contains zero or more comma-separated values (either
12
integers or other lists). Each packet is always a list and appears on its own line.
13
 
14
When comparing two values, the first value is called left and the second value is called right. Then:
15
 
16
- If both values are integers, the lower integer should come first. If the left integer is lower than the right integer,
17
the inputs are in the right order. If the left integer is higher than the right integer, the inputs are not in the right
18
order. Otherwise, the inputs are the same integer; continue checking the next part of the input.
19
 
20
- If both values are lists, compare the first value of each list, then the second value, and so on. If the left list
21
runs out of items first, the inputs are in the right order. If the right list runs out of items first, the inputs are
22
not in the right order. If the lists are the same length and no comparison makes a decision about the order, continue
23
checking the next part of the
24
input.
25
 
26
- If exactly one value is an integer, convert the integer to a list which contains that integer as its only value, then
27
retry the comparison. For example, if comparing [0,0,0] and 2, convert the right value to [2] (a list containing 2); the
28
result is then found by instead comparing [0,0,0] and [2].
10

11

and here is what the example input looked like:

12

13
[1,1,3,1,1]
14
[1,1,5,1,1]
15
 
16
[[1],[2,3,4]]
17
[[1],4]
18
 
19
[9]
20
[[8,7,6]]
21
 
22
[[4,4],4,4]
23
[[4,4],4,4,4]
24
 
25
[7,7,7,7]
26
[7,7,7]
27
 
28
[]
29
[3]
30
 
31
[[[]]]
32
[[]]
33
 
34
[1,[2,[3,[4,[5,6,7]]]],8,9]
35
[1,[2,[3,[4,[5,6,0]]]],8,9]
14

15

Solution

16

I think the logic for the comparisons is pretty straightforward. The tricky part is how to represent a dynamic

17

data structure like this in a statically typed language.

18

19

For the previous questions I had divided the process into two steps. Step one, I parsed the input. Step two, I tried

20

to solve the puzzle.

21

22

However, this approach wouldn't work very well here. Instead, I decided to merge the input parsing with the

23

comparisons. By using recursion, I was able to delay the type evaluation of the entire input, choosing to examine

24

the data type at each index individually. If the elements at each index were numbers, I could then perform the

25

necessary comparisons.

26

27

Here is the code for my solution:

28

29
func cmp(a, b any) int {
30
	as, aok := a.([]any)
31
	bs, bok := b.([]any)
32
 
33
	switch {
34
	case !aok && !bok:
35
		return int(a.(float64) - b.(float64))
36
	case !aok:
37
		as = []any{a}
38
	case !bok:
39
		bs = []any{b}
40
	}
41
 
42
	for i := 0; i < len(as) && i < len(bs); i++ {
43
		if c := cmp(as[i], bs[i]); c != 0 {
44
			return c
45
		}
46
	}
47
	return len(as) - len(bs)
48
}
49
 
50
func parseInput(input io.Reader) ([]any, int) {
51
	rawInput, _ := ioutil.ReadAll(input)
52
	packages, total := []any{}, 0
53
	for i, s := range strings.Split(strings.TrimSpace(string(rawInput)), "\n\n") {
54
		s := strings.Split(s, "\n")
55
		var a, b any
56
		json.Unmarshal([]byte(s[0]), &a)
57
		json.Unmarshal([]byte(s[1]), &b)
58
		packages = append(packages, a, b)
59
		if cmp(a, b) <= 0 {
60
			total += i + 1
61
		}
62
 
63
	}
64
	return packages, total
65
}
66
 
67
func PartOne(input io.Reader) int {
68
	_, total := parseInput(input)
69
	return total
70
}
71
 
72
func PartTwo(input io.Reader) int {
73
	packages, _ := parseInput(input)
74
	packages = append(packages, []any{[]any{2.}}, []any{[]any{6.}})
75
	sort.Slice(packages, func(i, j int) bool { return cmp(packages[i], packages[j]) < 0 })
76
	total := 1
77
	for i, p := range packages {
78
		if fmt.Sprint(p) == "[[2]]" || fmt.Sprint(p) == "[[6]]" {
79
			total *= i + 1
80
		}
81
	}
82
	return total
83
}
29

30

The end

31

I usually tweet something when I've finished writing a new post. You can find me on Twitter

32

by clicking 

normalintroduction.md
||129:3

Recently Edited

Recently Edited

File name

Tags

Time to read

Created at

context

  • go
  • context
8 minutes2024-02-28

circular-buffers

  • go
  • concurrency
  • data processing
5 minutes2024-02-04

go-directives

  • go
  • compiler
  • performance
4 minutes2023-10-21

async-tree-traversals

  • node
  • trees
  • graphs
  • typescript
19 minutes2023-09-10

All Files

All Files

  • go

    5 files

  • node

    2 files

  • typescript

    1 file

  • frontend

    1 file

  • workflow

    7 files