# Advent of Code 2021 - Day 8, in Kotlin - Seven Segment Search

Kotlin solutions to parts 1 and 2 of Advent of Code 2021, Day 8: 'Seven Segment Search'

I think this is my favorite puzzle of 2021 so far, I enjoyed solving part two especially. To me, it was a good lesson in reading the requirements as *requirements* and not as *instructions*.

If you’d rather just view code, the GitHub Repository is here .

**Problem Input**

We need to look at our input row-by-row, effectively solving the same puzzle a few times. The rows have nothing to do with each other at all. So let’s pull our input into a class called `InputRow`

, which will hold a single row of input and later, allow us to perform some functions on a row.

We’ll start by importing our raw input as a `List<String>`

and parsing them into a `List<InputRow>`

as `inputRows`

.

```
class Day08(input: List<String>) {
private val inputRows = input.map { InputRow.of(it) }
```

As for `InputRow`

, we’ll define that within `Day08`

so we can keep it private and not interfere with other daily solution classes. As is my usual preference, we’ll define a companion object with an `of`

function to handle the parsing:

```
// In Day08
private class InputRow(val digitSegments: List<Set<Char>>) {
companion object {
fun of(input: String): InputRow =
InputRow(
input.split(" ").filterNot { it == "|" }.map { it.toSet() }
)
}
}
```

You might notice that we don’t store the digit segments as a `String`

, but instead as a `Set<Char>`

. This helps us because we don’t really care about the *order* of the lit segments of a digit, just the presence. A `String`

or a `List<Char>`

would impose an order where we don’t actually want one.

#### ⭐ Day 8, Part 1

The puzzle text can be found here.

Because we did a lot of working in the parsing section, solving part one is fairly straight forward:

```
// In Day08
fun solvePart1(): Int =
inputRows.sumOf { row ->
row.digitSegments.takeLast(4).count { it.size in setOf(2, 3, 4, 7) }
}
```

We look at all the `inputRows`

and get the `sumOf`

the `count`

of the number of segments that have unique lengths (2, 3, 4, or 7). We constrain this to the last 4 elements in the list.

Star earned! Onward!

#### ⭐ Day 8, Part 2

The puzzle text can be found here.

This is one of the very rare cases where I have been able to guess what part two is before seeing it. If we read the instructions we might get the impression that we need to get down to the segment level (a, b, c, etc) and map them to locations within the seven segment display. However, if we look at the displays as they are rendered, we can see that some numbers overlap others.

For example, here are 3 and 5:

```
1: 3:
.... aaaa
. c . c
. c . c
.... dddd
. f . f
. f . f
.... gggg
```

Sure, they share segments `c`

and `f`

, but more interestingly is that 3 covers 1. Meaning, the segments that make up 3 are a superset of the segments that make up 1. Also notice that 3 has 5 lit segments. Of all the digits with 5 lit segments (which are 2, 3, and 5), 3 is the *only* one that is a superset of 1. Therefore, if we know 1, we can find 3. And we know 1.

Let’s do this for the rest of the numbers and figure out their unique properties:

Digit | Segments Lit | Properties |
---|---|---|

0 | 7 | Overlaps 1 and 7 |

1 | 2 | Unique size |

2 | 5 | Not 3 or 5 |

3 | 5 | Overlaps 1 |

4 | 4 | Unique size |

5 | 6 | Overlapped by 6 |

6 | 6 | Overlaps 6 |

7 | 3 | Unique size |

8 | 7 | Unique size |

9 | 6 | Overlaps 1, 3, 4, 5, and 7 |

Let’s use this chart to implement our digit discovery logic, which we will write as a function in `InputRow`

:

```
// In InputRow
private val digitValues = discoverMappings()
private fun discoverMappings(): Map<Set<Char>, Int> {
val digitToString = Array<Set<Char>>(10) { emptySet() }
// Unique based on size
digitToString[1] = digitSegments.first { it.size == 2 }
digitToString[4] = digitSegments.first { it.size == 4 }
digitToString[7] = digitSegments.first { it.size == 3 }
digitToString[8] = digitSegments.first { it.size == 7 }
// 3 is length 5 and overlaps 1
digitToString[3] = digitSegments
.filter { it.size == 5 }
.first { it overlaps digitToString[1] }
// 9 is length 6 and overlaps 3
digitToString[9] = digitSegments
.filter { it.size == 6 }
.first { it overlaps digitToString[3] }
// 0 is length 6, overlaps 1 and 7, and is not 9
digitToString[0] = digitSegments
.filter { it.size == 6 }
.filter { it overlaps digitToString[1] && it overlaps digitToString[7] }
.first { it != digitToString[9] }
// 6 is length 6 and is not 0 or 9
digitToString[6] = digitSegments
.filter { it.size == 6 }
.first { it != digitToString[0] && it != digitToString[9] }
// 5 is length 5 and is overlapped by 6
digitToString[5] = digitSegments
.filter { it.size == 5 }
.first { digitToString[6] overlaps it }
// 2 is length 5 and is not 3 or 5
digitToString[2] = digitSegments
.filter { it.size == 5 }
.first { it != digitToString[3] && it != digitToString[5] }
return digitToString.mapIndexed { index, chars -> chars to index }.toMap()
}
```

We’ll declare a `digitToString`

array in order to hold our working data. As we discover digits we’ll replace the default `emptySet`

with the `Set<Char>`

for the digit we found. We could have gone with nullable values in the array here but I don’t really like nulls so we’ll populate the array with empty sets instead.

First, we’ll get digits we can identify based on their size alone. Then we’ll go through the digits one by one and find them according to the criteria in the table above. Note that order is somewhat important - we can’t check for overlaps unless we know one of the digits. Similarly, in the case with 2, we can’t identify that until we’ve identified all of the other 5-segment digits.

At the end of this function we’ll turn our array into a `Map<Set<Char>, Int>`

. This will let us look up digits based on sets of characters in that digits representation, which we will store in `digitValues`

in `InputRow`

.

As for `overlap`

, we could have written this inline but I thought an infix extension function looked nicer:

```
// In InputRow
private infix fun Set<Char>.overlaps(that: Set<Char>): Boolean =
this.containsAll(that)
```

We also need a way to actually calculate the answer:

```
// In InputRow
fun calculateValue(): Int =
(digitValues.getValue(digitSegments[10]) * 1000) +
(digitValues.getValue(digitSegments[11]) * 100) +
(digitValues.getValue(digitSegments[12]) * 10) +
digitValues.getValue(digitSegments[13])
```

Note that this could have been a property too, since our `InputRow`

is immutable and we always want to know this answer (in part two anyway).

Summing the values of each of our `InputRow`

s gives us the answer to part two:

```
// In Day08
fun solvePart2(): Int = inputRows.sumOf { row ->
row.calculateValue()
}
```

Star earned!

#### Further Reading

- Index of All Solutions - All posts and solutions for 2021, in Kotlin.
- My Github repo - Solutions and tests for each day.
- Solution - Full code for day 8
- Advent of Code - Come join in and do these challenges yourself!