Iteration and strings.Map
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!