mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-31 01:17:11 +02:00
230 lines
5.9 KiB
Go
230 lines
5.9 KiB
Go
package main
|
|
|
|
import (
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/alexchao26/advent-of-code-go/util"
|
|
)
|
|
|
|
//go:embed input.txt
|
|
var input string
|
|
|
|
func init() {
|
|
// do this in init (not main) so test file has same input
|
|
input = strings.TrimRight(input, "\n")
|
|
if len(input) == 0 {
|
|
panic("empty input.txt file")
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var part int
|
|
flag.IntVar(&part, "part", 1, "part 1 or 2")
|
|
flag.Parse()
|
|
fmt.Println("Running part", part)
|
|
|
|
ans := jumbledSevenSegment(input, part)
|
|
util.CopyToClipboard(fmt.Sprintf("%v", ans))
|
|
fmt.Println("Output:", ans)
|
|
}
|
|
|
|
func jumbledSevenSegment(input string, part int) int {
|
|
// four digit seven segment displays
|
|
// seven segments are letters
|
|
// need to unjumble the ten mappings (pre-bar) to outputs (last 4 post-bar)
|
|
var parsedInput [][]string
|
|
for i, line := range strings.Split(input, "\n") {
|
|
// regexp capture group to just get characters
|
|
parts := regexp.MustCompile(`([a-g]+)`).FindAllString(line, -1)
|
|
|
|
if len(parts) != 14 {
|
|
log.Fatalf("should be 14 parts in each input line, got %d for line %d", len(parts), i)
|
|
}
|
|
|
|
// return them as just all 14 mappings together, alphabetize them for easier comparison later
|
|
var fourteen []string
|
|
for _, v := range parts {
|
|
fourteen = append(fourteen, alphabetizeString(v))
|
|
}
|
|
parsedInput = append(parsedInput, fourteen)
|
|
}
|
|
|
|
// PART 1 just count the outputs that are "easy" to determine
|
|
// aka only one displayed number uses that number of segments
|
|
if part == 1 {
|
|
var ans int
|
|
for _, set := range parsedInput {
|
|
// check the back 4: output
|
|
for _, o := range set[10:] {
|
|
switch len(o) {
|
|
// 1, 4, 7, 8 have a unique number of segments lit
|
|
// 2, 4, 4, 7 segments respectively
|
|
case 2, 4, 3, 7:
|
|
ans++
|
|
}
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
// PART 2
|
|
var ans int
|
|
indexToCharacters := make([]string, 10)
|
|
for _, set := range parsedInput {
|
|
|
|
workingSet := set[:10]
|
|
var killIndices []int // store the indices that will need to be removed in batches
|
|
|
|
// 1, 4, 7 and 8 are all easy to find because they have a unique number of segments
|
|
for i, mapping := range workingSet {
|
|
switch len(mapping) {
|
|
case 2:
|
|
// these two make up the 1
|
|
indexToCharacters[1] = mapping
|
|
killIndices = append(killIndices, i)
|
|
case 4: // the 4
|
|
indexToCharacters[4] = mapping
|
|
killIndices = append(killIndices, i)
|
|
case 3: // the 7
|
|
indexToCharacters[7] = mapping
|
|
killIndices = append(killIndices, i)
|
|
case 7: // the 8
|
|
indexToCharacters[8] = mapping
|
|
killIndices = append(killIndices, i)
|
|
}
|
|
}
|
|
|
|
// remove them from the workingSet
|
|
workingSet = removeSliceIndices(workingSet, killIndices...)
|
|
|
|
// only 0 3 and 9 will fully overlap the 1 characters
|
|
var zeroThreeOrNine []string
|
|
killIndices = []int{} // reset these...
|
|
for i, mapping := range workingSet {
|
|
if checkStringOverlap(mapping, indexToCharacters[1]) {
|
|
zeroThreeOrNine = append(zeroThreeOrNine, mapping)
|
|
killIndices = append(killIndices, i)
|
|
}
|
|
}
|
|
if len(zeroThreeOrNine) != 3 {
|
|
log.Fatalf("one three or nine does not have three matches: got %d", len(zeroThreeOrNine))
|
|
}
|
|
|
|
// find the 3 based on segment length
|
|
for i, maybe039 := range zeroThreeOrNine {
|
|
if len(maybe039) == 5 {
|
|
// must be the 3
|
|
indexToCharacters[3] = maybe039
|
|
zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i)
|
|
break
|
|
}
|
|
}
|
|
|
|
// the 9 will have a full overlap with 4
|
|
for i, maybe09 := range zeroThreeOrNine {
|
|
if checkStringOverlap(maybe09, indexToCharacters[4]) {
|
|
indexToCharacters[9] = maybe09
|
|
zeroThreeOrNine = removeSliceIndices(zeroThreeOrNine, i)
|
|
}
|
|
}
|
|
|
|
// 0 is only one left of the triplet
|
|
indexToCharacters[0] = zeroThreeOrNine[0]
|
|
|
|
// trim down working set again
|
|
workingSet = removeSliceIndices(workingSet, killIndices...)
|
|
if len(workingSet) != 3 {
|
|
log.Fatalf("expected length of 3 at this stage, got %d", len(workingSet))
|
|
}
|
|
|
|
// 6 is the last one with a length of 6
|
|
for i, mapping := range workingSet {
|
|
if len(mapping) == 6 {
|
|
indexToCharacters[6] = mapping
|
|
workingSet = removeSliceIndices(workingSet, i)
|
|
}
|
|
}
|
|
|
|
// 5 will be fully contained within the 9
|
|
for i, mapping := range workingSet {
|
|
if checkStringOverlap(indexToCharacters[9], mapping) {
|
|
indexToCharacters[5] = mapping
|
|
workingSet = removeSliceIndices(workingSet, i)
|
|
}
|
|
}
|
|
|
|
if len(workingSet) != 1 {
|
|
log.Fatalf("expected length of 1 at this stage, got %d", len(workingSet))
|
|
}
|
|
|
|
// 2 is the last remaining mapping
|
|
indexToCharacters[2] = workingSet[0]
|
|
|
|
// finally, collect the four digits in the output & add it to the answer
|
|
var num int
|
|
for _, out := range set[10:] {
|
|
for i, mapping := range indexToCharacters {
|
|
// because they were all alphabetized, we can just do a direct comparison
|
|
if out == mapping {
|
|
// shift all digits to the left and add the new digit to the end
|
|
num *= 10
|
|
num += i
|
|
}
|
|
}
|
|
}
|
|
ans += num
|
|
}
|
|
|
|
return ans
|
|
}
|
|
|
|
func removeSliceIndices(sli []string, indices ...int) []string {
|
|
m := map[int]bool{}
|
|
for _, v := range indices {
|
|
m[v] = true
|
|
}
|
|
|
|
var ans []string
|
|
for i, v := range sli {
|
|
if !m[i] {
|
|
ans = append(ans, v)
|
|
}
|
|
}
|
|
return ans
|
|
}
|
|
|
|
func checkStringOverlap(larger, smaller string) bool {
|
|
// safeguard, a smaller string can never contain a larger string
|
|
// i think every use of this function already passes args in the correct order, but this makes
|
|
// the checkStringOverlap algo more robust in general
|
|
if len(larger) < len(smaller) {
|
|
larger, smaller = smaller, larger
|
|
}
|
|
|
|
largeMap := map[rune]bool{}
|
|
for _, r := range larger {
|
|
largeMap[r] = true
|
|
}
|
|
|
|
// check all runes in the smaller string, return false if not in largeMap
|
|
for _, r := range smaller {
|
|
if !largeMap[r] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// alphabetize the strings to make them easier to compare later
|
|
func alphabetizeString(input string) string {
|
|
chars := strings.Split(input, "")
|
|
sort.Strings(chars)
|
|
return strings.Join(chars, "")
|
|
}
|