Initial commit

This commit is contained in:
2024-12-01 17:41:25 +01:00
commit dc284b7f47
9 changed files with 1419 additions and 0 deletions

60
.gitignore vendored Normal file
View File

@ -0,0 +1,60 @@
# Created by https://www.toptal.com/developers/gitignore/api/go,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=go,macos
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
# End of https://www.toptal.com/developers/gitignore/api/go,macos

2
README.org Normal file
View File

@ -0,0 +1,2 @@
#+title: Readme

106
days/day1/README.org Normal file
View File

@ -0,0 +1,106 @@
#+title: December First challenge - Historian Hysteria
* Blurb
The *Chief Historian* is always present for the big Christmas sleigh launch, but
nobody has seen him in months! Last anyone heard, he was visiting locations that
are historically significant to the North Pole; a group of Senior Historians has
asked you to accompany them as they check the places they think he was most
likely to visit.
As each location is checked, they will mark it on their list with a *star*. They
figure the Chief Historian must be in one of the first fifty places they'll
look, so in order to save Christmas, you need to help them get fifty stars on
their list before Santa takes off on December 25th.
Collect stars by solving puzzles. Two puzzles will be made available on each day
in the Advent calendar; the second puzzle is unlocked when you complete the
first. Each puzzle grants one star. Good luck!
You haven't even left yet and the group of Elvish Senior Historians has already
hit a problem: their list of locations to check is currently empty. Eventually,
someone decides that the best place to check first would be the Chief
Historian's office.
Upon pouring into the office, everyone confirms that the Chief Historian is
indeed nowhere to be found. Instead, the Elves discover an assortment of notes
and lists of historically significant locations! This seems to be the planning
the Chief Historian was doing before he left. Perhaps these notes can be used to
determine which locations to search?
Throughout the Chief's office, the historically significant locations are listed
not by name but by a unique number called the location ID. To make sure they
don't miss anything, The Historians split into two groups, each searching the
office and trying to create their own complete list of location IDs.
There's just one problem: by holding the two lists up side by side (your puzzle
input), it quickly becomes clear that the lists aren't very similar. Maybe you
can help The Historians reconcile their lists?
* Example
#+begin_example
3 4
4 3
2 5
1 3
3 9
3 3
#+end_example
Maybe the lists are only off by a small amount! To find out, pair up the numbers
and measure how far apart they are. Pair up the *smallest number in the left list*
with *the smallest number in the right list*, then the *second-smallest left number*
with the *second-smallest right number*, and so on.
Within each pair, figure out *how far apart* the two numbers are; you'll need to
*add up all of those distances*. For example, if you pair up a =3= from the left
list with a =7= from the right list, the distance apart is =4=; if you pair up a =9=
with a =3=, the distance apart is =6=.
In the example list above, the pairs and distances would be as follows:
+ The smallest number in the left list is 1, and the smallest number in the right list is 3. The distance between them is 2.
+ The second-smallest number in the left list is 2, and the second-smallest number in the right list is another 3. The distance between them is 1.
+ The third-smallest number in both lists is 3, so the distance between them is 0.
+ The next numbers to pair up are 3 and 4, a distance of 1.
+ The fifth-smallest numbers in each list are 3 and 5, a distance of 2.
+ Finally, the largest number in the left list is 4, while the largest number in the right list is 9; these are a distance 5 apart.
+ To find the total distance between the left list and the right list, add up
the distances between all of the pairs you found. In the example above, this
is 2 + 1 + 0 + 1 + 2 + 5, a total distance of 11!
Your actual left and right lists contain many location IDs. What is the total distance between your lists?
* Part 2
Your analysis only confirmed what everyone feared: the two lists of location IDs are indeed very different.
Or are they?
The Historians can't agree on which group made the mistakes or how to read most of the Chief's handwriting, but in the commotion you notice an interesting detail: a lot of location IDs appear in both lists! Maybe the other numbers aren't location IDs at all but rather misinterpreted handwriting.
This time, you'll need to figure out exactly how often each number from the left list appears in the right list. Calculate a total similarity score by adding up each number in the left list after multiplying it by the number of times that number appears in the right list.
Here are the same example lists again:
#+begin_example
3 4
4 3
2 5
1 3
3 9
3 3
#+end_example
For these example lists, here is the process of finding the similarity score:
+ The first number in the left list is 3. It appears in the right list three times, so the similarity score increases by 3 * 3 = 9.
+ The second number in the left list is 4. It appears in the right list once, so the similarity score increases by 4 * 1 = 4.
+ The third number in the left list is 2. It does not appear in the right list, so the similarity score does not increase (2 * 0 = 0).
+ The fourth number, 1, also does not appear in the right list.
+ The fifth number, 3, appears in the right list three times; the similarity score increases by 9.
+ The last number, 3, appears in the right list three times; the similarity score again increases by 9.
So, for these example lists, the similarity score at the end of this process is 31 (9 + 4 + 0 + 0 + 9 + 9).

142
days/day1/day1.go Normal file
View File

@ -0,0 +1,142 @@
package day1
import (
"bufio"
"fmt"
"os"
"slices"
"strconv"
"strings"
)
func getDistance(x, y int) int {
// Take the distance between two numbers.
// The distance is the difference between the two numbers.
if x > y {
return (x - y)
} else {
return (y - x)
}
}
func processLine(line string) (int, int, error) {
// Split the line into fields.
fields := strings.Fields(line)
if len(fields) != 2 {
return 0, 0, fmt.Errorf("Expected 2 fields, got %d", len(fields))
}
num1, err := strconv.Atoi(fields[0])
if err != nil {
return 0, 0, fmt.Errorf("Failed to parse first number: %w", err)
}
num2, err := strconv.Atoi(fields[1])
if err != nil {
return 0, 0, fmt.Errorf("Failed to parse the second number: %w", err)
}
return num1, num2, nil
}
func readDataSet(filename string) ([]int, []int, error) {
file, err := os.Open(filename)
if err != nil {
return nil, nil, err
}
defer file.Close()
var firstSet, secondSet []int
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
num1, num2, err := processLine(line)
if err != nil {
return nil, nil, err
}
firstSet = append(firstSet, num1)
secondSet = append(secondSet, num2)
}
return firstSet, secondSet, nil
}
func numFreq(dataset []int) map[int]int {
// Take a slice of numbers and return an array of each number and the amount of times it occurs.
distribution := make(map[int]int)
for _, num := range dataset {
elem, ok := distribution[num]
if ok {
distribution[num] = elem + 1
} else {
distribution[num] = 1
}
}
return distribution
}
func partTwo(filename string) (int, error) {
//Solve part two.
var similarities int
firstSet, secondSet, err := readDataSet(filename)
if err != nil {
return 0, err
}
secondDistribution := numFreq(secondSet)
for _, num := range firstSet {
freq, ok := secondDistribution[num]
if ok {
similarity := num * freq
similarities = similarities + similarity
}
}
return similarities, nil
}
func partOne(filename string) (int, error) {
// Solve part one.
firstSet, secondSet, err := readDataSet(filename)
if err != nil {
return 0, err
}
slices.Sort(firstSet)
slices.Sort(secondSet)
if len(firstSet) != len(secondSet) {
return 0, fmt.Errorf("Error: Datasets are not the same size. %v != %v", len(firstSet), len(secondSet))
}
var distances int
for i := 0; i < len(firstSet); i++ {
distance := getDistance(firstSet[i], secondSet[i])
distances = distances + distance
}
return distances, nil
}
func Day1(filename string) {
distances, err := partOne(filename)
if err != nil {
fmt.Printf("Error: %v", err)
os.Exit(1)
}
fmt.Println("-------- PART 1 -------")
fmt.Printf("The total distance is %v\n", distances)
similarities, err := partTwo(filename)
if err != nil {
fmt.Printf("Error: %v", err)
os.Exit(1)
}
fmt.Println("-------- PART 2 -------")
fmt.Printf("The similarities between the two sets: %v\n", similarities)
}

65
days/day1/day1_test.go Normal file
View File

@ -0,0 +1,65 @@
package day1
import "testing"
func Test_partTwo(t *testing.T) {
type args struct {
filename string
}
tests := []struct {
name string
args args
want int
wantErr bool
}{
{
name: "Example Part 2",
args: args{filename: "example.txt"},
want: 31,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := partTwo(tt.args.filename)
if (err != nil) != tt.wantErr {
t.Errorf("partTwo() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("partTwo() = %v, want %v", got, tt.want)
}
})
}
}
func Test_partOne(t *testing.T) {
type args struct {
filename string
}
tests := []struct {
name string
args args
want int
wantErr bool
}{
{
name: "Example Part 1",
args: args{filename: "example.txt"},
want: 11,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := partOne(tt.args.filename)
if (err != nil) != tt.wantErr {
t.Errorf("partOne() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("partOne() = %v, want %v", got, tt.want)
}
})
}
}

6
days/day1/example.txt Normal file
View File

@ -0,0 +1,6 @@
3 4
4 3
2 5
1 3
3 9
3 3

1000
days/day1/test_input.txt Normal file

File diff suppressed because it is too large Load Diff

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.eising.cloud/Eising/aoc2024
go 1.23.1

35
main.go Normal file
View File

@ -0,0 +1,35 @@
package main
import (
"flag"
"fmt"
"os"
"git.eising.cloud/Eising/aoc2024/days/day1"
)
func main() {
day := flag.Int("day", 1, "Day of the challenge to run (1-24)")
useExample := flag.Bool("exampledata", false, "Load example data instead of actual challenge data.")
flag.Parse()
filename := fmt.Sprintf("days/day%d/test_input.txt", *day)
if *useExample {
filename = fmt.Sprintf("days/day%d/example.txt", *day)
}
if *day < 1 || *day > 24 {
fmt.Println("That's not very Christmas.")
os.Exit(1)
}
switch *day {
case 1:
day1.Day1(filename)
default:
fmt.Printf("Solution for day %d is not ready yet.\n", *day)
}
}