Iteration and strings.Map

2020-12-08

We've been tackling and enjoying the daily Advent of Code puzzles. Our main goal is to find a solution, not a pretty one 😅. If there's some time available afterwards, we like to revisit some decisions and explore other approaches. I especially enjoy watching Jonathan's approach afterwards. He first tackles the problem for speed and then takes extra time to explain his approach in more detail. For us, there's always been at least a small lesson that emerged when we reflect on our solutions afterwards.

Iteration

One reminder that I got on the first day was about iteration: Iteration using indices is often frowned upon as brittle or error-prone but it really shines when you're trying to limit iterations. Based on the first puzzle, compare:

count := 0
for _, a := range as {
    for _, b := range as {
        if a + b == 2020 {
            count++
        }
    }
}

with the use of indices for the inner loop:

count := 0
for i, a  := range as {
    for j := i + 1; j < len(as); j++ {
        if a + as[j] == 2020 {
            count++
        }
    }
}

For me, the first version is what I default to and it's exactly what I'm looking for in most cases. It feels concise and I don't check any boundaries, so it's unlikely that I run into off-by-one issues. However, for the puzzle that meant lots of redundant work, as you don't have to check if b + a == 2020 if you already checked if a + b == 2020. More specifically, the inner loop only needs to inspect the remainder of as rather than starting at the beginning every time. I enjoyed that it was straight-forward to change the inner loop without changing my approach entirely.

strings.Map

After tackling day 6 I skimmed godoc.org/strings for a different approach as I also had a feeling that we would do more string manipulation in the future 😉. I found strings.Map which I hadn't used or known about before. In Go you need to implement specialized maps as generics are not supported yet. In the case of strings.Map this specialization results in an implementation that I did not expect: It allows for modifying the string that is being mapped by cutting out runes:

Map returns a copy of the string s with all its characters modified
according to the mapping function. If mapping returns a negative value,
the character is dropped from the string with no replacement.

So you can use it to find common parts like this:

common := as[0]
for _, a := range as {
    common = strings.Map(
        func(r rune) rune {
            if strings.Contains(a, string(r)) {
                return r
            }
            return -1
        },
        common,
    )
}

This is how I implemented it before using strings.ReplaceAll:

common := as[0]
for _, a := range as {
    next := common
    for _, r := range common {
        if !strings.Contains(a, string(r)) {
            next = strings.ReplaceAll(next, string(r), "")
        }
    }
    common = next
}

If you're interested in my hacky solutions you can browse this repository at your own risk 😉. Happy hacking!