Initial commit
This commit is contained in:
60
.gitignore
vendored
Normal file
60
.gitignore
vendored
Normal 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
2
README.org
Normal file
@ -0,0 +1,2 @@
|
||||
#+title: Readme
|
||||
|
||||
106
days/day1/README.org
Normal file
106
days/day1/README.org
Normal 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
142
days/day1/day1.go
Normal 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
65
days/day1/day1_test.go
Normal 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
6
days/day1/example.txt
Normal 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
1000
days/day1/test_input.txt
Normal file
File diff suppressed because it is too large
Load Diff
35
main.go
Normal file
35
main.go
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user