1
0
Fork 0

Compare commits

...

5 Commits

30 changed files with 906 additions and 501 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
**/testdata/input

View File

@ -1,77 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day1/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day1/part2"
)
/*
Something is wrong with global snow production, and you've been selected to take
a look. The Elves have even given you a map; on it, they've used stars to mark
the top fifty locations that are likely to be having problems.
You've been doing this long enough to know that to restore snow operations, you
need to check all fifty stars by 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 try to ask why they can't just use a weather machine ("not powerful enough")
and where they're even sending you ("the sky") and why your map looks mostly
blank ("you sure ask a lot of questions") and hang on did you just say the sky
("of course, where do you think snow comes from") when you realize that the
Elves are already loading you into a trebuchet ("please hold still, we need to
strap you in").
As they're making the final adjustments, they discover that their calibration
document (your puzzle input) has been amended by a very young Elf who was
apparently just excited to show off her art skills. Consequently, the Elves are
having trouble reading the values on the document.
Consider your entire calibration document. What is the sum of all of the
calibration values?
*/
func ExampleDay1() {
input, err := os.ReadFile(filepath.Join("internal", "day1", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part1.Calibrate(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot sum all calibration values:", err)
}
fmt.Println(result)
// Output: 54601
}
/*
Your calculation isn't quite right.
Equipped with this new information, you now need to find the real first and last
digit on each line.
What is the sum of all of the calibration values?
*/
func ExampleDay1_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day1", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part2.Calibrate(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot sum all calibration values:", err)
}
fmt.Println(result)
// Output: 54078
}

View File

@ -1,78 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day2/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day2/part2"
)
/*
You're launched high into the atmosphere! The apex of your trajectory just
barely reaches the surface of a large island floating in the sky. You gently
land in a fluffy pile of leaves. It's quite cold, but you don't see much snow.
An Elf runs over to greet you.
The Elf explains that you've arrived at Snow Island and apologizes for the lack
of snow. He'll be happy to explain the situation, but it's a bit of a walk, so
you have some time. They don't get many visitors up here; would you like to play
a game in the meantime?
As you walk, the Elf shows you a small bag and some cubes which are either red,
green, or blue. Each time you play this game, he will hide a secret number of
cubes of each color in the bag, and your goal is to figure out information about
the number of cubes.
Determine which games would have been possible if the bag had been loaded with
only 12 red cubes, 13 green cubes, and 14 blue cubes. What is the sum of the IDs
of those games?
*/
func ExampleDay2() {
input, err := os.ReadFile(filepath.Join("internal", "day2", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part1.GetSecretNumber(bytes.NewReader(input), part1.Bag{
Red: 12,
Green: 13,
Blue: 14,
})
if err != nil {
log.Fatalln("cannot get secret number:", err)
}
fmt.Println(result)
// Output: 3035
}
/*
The Elf says they've stopped producing snow because they aren't getting any
water! He isn't sure why the water stopped; however, he can show you how to get
to the water source to check it out for yourself. It's just up ahead!
As you continue your walk, the Elf poses a second question: in each game you
played, what is the fewest number of cubes of each color that could have been in
the bag to make the game possible?
For each game, find the minimum set of cubes that must have been present. What
is the sum of the power of these sets?
*/
func ExampleDay2_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day2", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part2.GetPowerSum(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get power sum:", err)
}
fmt.Println(result)
// Output: 66027
}

View File

@ -1,41 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day3/part1"
)
/*
You and the Elf eventually reach a gondola lift station; he says the gondola
lift will take you up to the water source, but this is as far as he can bring
you. You go inside.
It doesn't take long to find the gondolas, but there seems to be a problem:
they're not moving.
"Aaah!"
You turn around to see a slightly-greasy Elf with a wrench and a look of
surprise. "Sorry, I wasn't expecting anyone! The gondola lift isn't working
right now; it'll still be a while before I can fix it." You offer to help.
The engineer explains that an engine part seems to be missing from the engine,
but nobody can figure out which one. If you can add up all the part numbers in
the engine schematic, it should be easy to work out which part is missing.
*/
func ExampleDay3() {
input, err := os.ReadFile(filepath.Join("internal", "day3", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result := part1.GetPartNumbersSum(bytes.NewReader(input))
fmt.Println(result)
// Output: 540025
}

View File

@ -1,81 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day4/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day4/part2"
)
/*
The gondola takes you up. Strangely, though, the ground doesn't seem to be
coming with you; you're not climbing a mountain. As the circle of Snow Island
recedes below you, an entire new landmass suddenly appears above you! The
gondola carries you to the surface of the new island and lurches into the
station.
As you exit the gondola, the first thing you notice is that the air here is much
warmer than it was on Snow Island. It's also quite humid. Is this where the
water source is?
The next thing you notice is an Elf sitting on the floor across the station in
what seems to be a pile of colorful square cards.
"Oh! Hello!" The Elf excitedly runs over to you. "How may I be of service?" You
ask about water sources.
"I'm not sure; I just operate the gondola lift. That does sound like something
we'd have, though - this is Island Island, after all! I bet the gardener would
know. He's on a different island, though - er, the small kind surrounded by
water, not the floating kind. We really need to come up with a better naming
scheme. Tell you what: if you can help me with something quick, I'll let you
borrow my boat and you can go visit the gardener. I got all these scratchcards
as a gift, but I can't figure out what I've won."
Take a seat in the large pile of colorful cards. How many points are they worth
in total?
*/
func ExampleDay4() {
input, err := os.ReadFile(filepath.Join("internal", "day4", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part1.GetTotalWorthPoints(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get total worth:", err)
}
fmt.Println(result)
// Output: 32001
}
/*
Just as you're about to report your findings to the Elf, one of you realizes
that the rules have actually been printed on the back of every card this whole
time.
There's no such thing as "points". Instead, scratchcards only cause you to win
more scratchcards equal to the number of winning numbers you have.
Including the original set of scratchcards, how many total scratchcards do you
end up with?
*/
func ExampleDay4_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day4", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
result, err := part2.GetTotalScratchcards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get total count of scratchcards:", err)
}
fmt.Println(result)
// Output: 5037841
}

View File

@ -1,74 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day5/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day5/part2"
)
/*
You take the boat and find the gardener right where you were told he would be:
managing a giant "garden" that looks more to you like a farm.
"A water source? Island Island is the water source!" You point out that Snow
Island isn't receiving any water.
"Oh, we had to stop the water because we ran out of sand to filter it with!
Can't make snow with dirty water. Don't worry, I'm sure we'll get more sand
soon; we only turned off the water a few days... weeks... oh no." His face sinks
into a look of horrified realization.
"I've been so busy making sure everyone here has food that I completely forgot
to check why we stopped getting more sand! There's a ferry leaving soon that is
headed over in that direction - it's much faster than your boat. Could you
please go check it out?"
You barely have time to agree to this request when he brings up another. "While
you wait for the ferry, maybe you can help us with our food production problem.
The latest Island Island Almanac just arrived and we're having trouble making
sense of it."
*/
func ExampleDay5() {
input, err := os.ReadFile(filepath.Join("internal", "day5", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
almanac, err := part1.ParseAlmanac(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse almanac:", err)
}
fmt.Println(almanac.FindLowestLocation())
// Output: 551761867
}
/*
Everyone will starve if you only plant such a small number of seeds. Re-reading
the almanac, it looks like the seeds: line actually describes ranges of seed
numbers.
What is the lowest location number that corresponds to any of the initial seed
numbers?
*/
func ExampleDay5_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day5", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
almanac, err := part2.ParseAlmanac(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse almanac:", err)
}
// WARN(toby3d): Extremely slow! Took ~209 seconds to run, but returned
// the correct result BY ANY PRICE, BUT FREE.
fmt.Println(almanac.FindLowestLocation())
// Output: 57451709
}

View File

@ -1,68 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day6/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day6/part2"
)
/*
The ferry quickly brings you across Island Island. After asking around, you
discover that there is indeed normally a large pile of sand somewhere near here,
but you don't see anything besides lots of water and the small island where the
ferry has docked.
As you try to figure out what to do next, you notice a poster on a wall near the
ferry dock. "Boat races! Open to the public! Grand prize is an all-expenses-paid
trip to Desert Island!" That must be where the sand comes from! Best of all, the
boat races are starting in just a few minutes.
You manage to sign up as a competitor in the boat races just in time. The
organizer explains that it's not really a traditional race - instead, you will
get a fixed amount of time during which your boat has to travel as far as it
can, and you win if your boat goes the farthest.
Determine the number of ways you could beat the record in each race. What do you
get if you multiply these numbers together?
*/
func ExampleDay6() {
input, err := os.ReadFile(filepath.Join("internal", "day6", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
races, err := part1.ParseDocument(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse races document:", err)
}
fmt.Println(races.Power())
// Output: 505494
}
/*
As the race is about to start, you realize the piece of paper with race times
and record distances you got earlier actually just has very bad kerning. There's
really only one race - ignore the spaces between the numbers on each line.
How many ways can you beat the record in this one much longer race?
*/
func ExampleDay6_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day6", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
race, err := part2.ParseDocument(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse race document:", err)
}
fmt.Println(race.CountWinningStrategies())
// Output: 23632299
}

View File

@ -1,82 +0,0 @@
package adventofcode_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"source.toby3d.me/toby3d/adventofcode/internal/day7/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day7/part2"
)
/*
Your all-expenses-paid trip turns out to be a one-way, five-minute ride in an
airship. (At least it's a cool airship!) It drops you off at the edge of a vast
desert and descends back to Island Island.
"Did you bring the parts?"
You turn around to see an Elf completely covered in white clothing, wearing
goggles, and riding a large camel.
"Did you bring the parts?" she asks again, louder this time. You aren't sure
what parts she's looking for; you're here to figure out why the sand stopped.
"The parts! For the sand, yes! Come with me; I will show you." She beckons you
onto the camel.
After riding a bit across the sands of Desert Island, you can see what look like
very large rocks covering half of the horizon. The Elf explains that the rocks
are all along the part of Desert Island that is directly above Island Island,
making it hard to even get there. Normally, they use big machines to move the
rocks and filter the sand, but the machines have broken down because Desert
Island recently stopped receiving the parts they need to fix the machines.
You've already assumed it'll be your job to figure out why the parts stopped
when she asks if you can help. You agree automatically.
Because the journey will take a few days, she offers to teach you the game of
Camel Cards. Camel Cards is sort of similar to poker except it's designed to be
easier to play while riding a camel.
Find the rank of every hand in your set. What are the total winnings?
*/
func ExampleDay7() {
input, err := os.ReadFile(filepath.Join("internal", "day7", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
camelCards, err := part1.ParseCamelCards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse Camel Cards list:", err)
}
fmt.Println(camelCards.CountTotalWinnings())
// Output: 246795406
}
/*
To make things a little more interesting, the Elf introduces one additional
rule. Now, J cards are jokers - wildcards that can act like whatever card would
make the hand the strongest type possible.
Using the new joker rule, find the rank of every hand in your set. What are the
new total winnings?
*/
func ExampleDay7_PartTwo() {
input, err := os.ReadFile(filepath.Join("internal", "day7", "testdata", "input"))
if err != nil {
log.Fatalln("cannot open input file:", err)
}
camelCards, err := part2.ParseCamelCards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse Camel Cards list:", err)
}
fmt.Println(camelCards.CountTotalWinnings())
// Output: 249356515
}

View File

@ -0,0 +1,44 @@
package day1_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day1/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day1/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay1() {
result, err := part1.Calibrate(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot sum all calibration values:", err)
}
fmt.Println(result)
// Output: 54601
}
func ExampleDay1_PartTwo() {
result, err := part2.Calibrate(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot sum all calibration values:", err)
}
fmt.Println(result)
// Output: 54078
}

0
internal/day1/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,48 @@
package day2_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day2/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day2/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay2() {
result, err := part1.GetSecretNumber(bytes.NewReader(input), part1.Bag{
Red: 12,
Green: 13,
Blue: 14,
})
if err != nil {
log.Fatalln("cannot get secret number:", err)
}
fmt.Println(result)
// Output: 3035
}
func ExampleDay2_PartTwo() {
result, err := part2.GetPowerSum(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get power sum:", err)
}
fmt.Println(result)
// Output: 66027
}

0
internal/day2/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,34 @@
package day3_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day3/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day3/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay3() {
fmt.Println(part1.GetPartNumbersSum(bytes.NewReader(input)))
// Output: 540025
}
func ExampleDay3_PartTwo() {
fmt.Println(part2.ParseEngineMap(bytes.NewReader(input)).GetGearRatiosSum())
// Output: 84584891
}

View File

@ -0,0 +1,203 @@
package part2
import (
"bufio"
"io"
"strconv"
)
type (
EngineMap [][]Point
Point byte
Number struct {
value []byte
minX int
maxX int
y int
}
Gear struct {
x, y int
}
)
func NewNumber(y int) *Number {
return &Number{
minX: 0,
maxX: 0,
y: y,
value: make([]byte, 0),
}
}
func (n *Number) Write(x int, p Point) {
n.value = append(n.value, byte(p))
n.maxX = x
}
func (n Number) IsZero() bool {
return len(n.value) == 0
}
func (n *Number) Move(x int) {
n.minX = x
n.maxX = x
}
func (n Number) Uint() uint {
result, _ := strconv.ParseUint(string(n.value), 10, 64)
return uint(result)
}
func (p Point) IsGear() bool {
return p == '*'
}
func (p Point) IsDigit() bool {
switch p {
default:
return false
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return true
}
}
func (p Point) IsSymbol() bool {
switch p {
default:
return true
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
return false
}
}
func (p Point) IsEmpty() bool {
return p == '.'
}
func (em EngineMap) HasGearAround(n Number) *Gear {
minx := n.minX - 1
if minx < 0 {
minx = 0
}
maxx := n.maxX + 1
if maxx >= len(em[0]) {
maxx = len(em[0]) - 1
}
miny := n.y - 1
if miny < 0 {
miny = 0
}
maxy := n.y + 1
if maxy >= len(em) {
maxy = len(em) - 1
}
for y := miny; y <= maxy; y++ {
for x := minx; x <= maxx; x++ {
if !em[y][x].IsGear() {
continue
}
return &Gear{
x: x,
y: y,
}
}
}
return nil
}
func ParseEngineMap(r io.Reader) EngineMap {
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
engineMap := make(EngineMap, 0)
y := 0
for scanner.Scan() {
line := scanner.Bytes()
engineMap = append(engineMap, make([]Point, len(line)))
for x := range line {
engineMap[y][x] = Point(line[x])
}
y++
}
return engineMap
}
func (em EngineMap) GetPartNumbers() []Number {
numbers := make([]Number, 0)
for y := range em {
n := NewNumber(y)
for x := range em[y] {
p := em[y][x]
if p.IsDigit() {
if n.IsZero() {
n.Move(x)
}
n.Write(x, p)
continue
}
if n.IsZero() {
continue
}
numbers = append(numbers, *n)
n = NewNumber(y)
}
if !n.IsZero() {
numbers = append(numbers, *n)
}
}
return numbers
}
func (em EngineMap) GetGearRatiosSum() uint64 {
// NOTE(toby3d): Numbers are harder to find and take up more space than
// symbols. So, instead of looking for gears all over the map, I will
// limit myself to looking for gears around numbers and assign them to
// the same gear point.
gears := make(map[Gear][]Number)
for _, number := range em.GetPartNumbers() {
gear := em.HasGearAround(number)
if gear == nil {
continue
}
if _, ok := gears[*gear]; !ok {
gears[*gear] = make([]Number, 0)
}
gears[*gear] = append(gears[*gear], number)
}
var result uint64
for _, numbers := range gears {
if len(numbers) != 2 {
continue
}
result += uint64(numbers[0].Uint()) * uint64(numbers[1].Uint())
}
return result
}

View File

@ -0,0 +1,26 @@
package part2_test
import (
"fmt"
"strings"
"source.toby3d.me/toby3d/adventofcode/internal/day3/part2"
)
func Example() {
input := "467..114..\n" +
"...*......\n" +
"..35..633.\n" +
"......#...\n" +
"617*......\n" +
".....+.58.\n" +
"..592.....\n" +
"......755.\n" +
"...$.*....\n" +
".664.598.."
engineMap := part2.ParseEngineMap(strings.NewReader(input))
fmt.Println(engineMap.GetGearRatiosSum())
// Output: 467835
}

0
internal/day3/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,44 @@
package day4_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day4/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day4/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay4() {
result, err := part1.GetTotalWorthPoints(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get total worth:", err)
}
fmt.Println(result)
// Output: 32001
}
func ExampleDay4_PartTwo() {
result, err := part2.GetTotalScratchcards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot get total count of scratchcards:", err)
}
fmt.Println(result)
// Output: 5037841
}

0
internal/day4/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,46 @@
package day5_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day5/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day5/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay5() {
almanac, err := part1.ParseAlmanac(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse almanac:", err)
}
fmt.Println(almanac.FindLowestLocation())
// Output: 551761867
}
func ExampleDay5_PartTwo() {
almanac, err := part2.ParseAlmanac(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse almanac:", err)
}
// WARN(toby3d): Extremely slow! Took ~209 seconds to run, but returned
// the correct result BY ANY PRICE, BUT FREE.
fmt.Println(almanac.FindLowestLocation())
// Output: 57451709
}

0
internal/day5/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,44 @@
package day6_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day6/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day6/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay6() {
races, err := part1.ParseDocument(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse races document:", err)
}
fmt.Println(races.Power())
// Output: 505494
}
func ExampleDay6_PartTwo() {
race, err := part2.ParseDocument(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse race document:", err)
}
fmt.Println(race.CountWinningStrategies())
// Output: 23632299
}

0
internal/day6/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,44 @@
package day7_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day7/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day7/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay7() {
camelCards, err := part1.ParseCamelCards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse Camel Cards list:", err)
}
fmt.Println(camelCards.CountTotalWinnings())
// Output: 246795406
}
func ExampleDay7_PartTwo() {
camelCards, err := part2.ParseCamelCards(bytes.NewReader(input))
if err != nil {
log.Fatalln("cannot parse Camel Cards list:", err)
}
fmt.Println(camelCards.CountTotalWinnings())
// Output: 249356515
}

0
internal/day7/testdata/.gitkeep vendored Normal file
View File

View File

@ -0,0 +1,34 @@
package day8_test
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"testing"
"source.toby3d.me/toby3d/adventofcode/internal/day8/part1"
"source.toby3d.me/toby3d/adventofcode/internal/day8/part2"
)
var input []byte
func TestMain(m *testing.M) {
var err error
if input, err = os.ReadFile(filepath.Join("testdata", "input")); err != nil {
log.Fatalln("cannot open input file:", err)
}
os.Exit(m.Run())
}
func ExampleDay8() {
fmt.Println(part1.ParseNetworkMap(bytes.NewReader(input)).CountSteps())
// Output: 19631
}
func ExampleDay8_PartTwo() {
fmt.Println(part2.ParseNetworkMap(bytes.NewReader(input)).CountSteps())
// Output: 21003205388413
}

View File

@ -0,0 +1,90 @@
package part1
import (
"bufio"
"io"
)
type (
NetworkMap struct {
Forks map[Node]Fork
Directions []Direction
}
Fork struct {
Left Node
Right Node
}
Node [3]byte
Direction byte
)
var (
NodeUnd Node = [3]byte{0, 0, 0}
NodeStart Node = [3]byte{'A', 'A', 'A'}
NodeEnd Node = [3]byte{'Z', 'Z', 'Z'}
)
var (
DirectionLeft Direction = 'L'
DirectionRight Direction = 'R'
)
func (f Fork) Peek(d Direction) Node {
switch d {
default:
return NodeUnd
case DirectionLeft:
return f.Left
case DirectionRight:
return f.Right
}
}
func ParseNetworkMap(r io.Reader) *NetworkMap {
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
networkMap := &NetworkMap{
Directions: make([]Direction, 0),
Forks: make(map[Node]Fork),
}
scanner.Scan()
for _, b := range scanner.Text() {
networkMap.Directions = append(networkMap.Directions, Direction(b))
}
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
node := Node([3]byte{line[0], line[1], line[2]})
leftNode := Node([3]byte{line[7], line[8], line[9]})
rightNode := Node([3]byte{line[12], line[13], line[14]})
networkMap.Forks[node] = Fork{
Left: leftNode,
Right: rightNode,
}
}
return networkMap
}
func (nm NetworkMap) CountSteps() uint64 {
var steps uint64
node := NodeStart
for i := 0; node != NodeEnd; i++ {
i = i % len(nm.Directions)
node = nm.Forks[node].Peek(nm.Directions[i])
steps++
}
return steps
}

View File

@ -0,0 +1,34 @@
package part1_test
import (
"fmt"
"strings"
"source.toby3d.me/toby3d/adventofcode/internal/day8/part1"
)
func Example() {
input := "RL\n" +
"\n" +
"AAA = (BBB, CCC)\n" +
"BBB = (DDD, EEE)\n" +
"CCC = (ZZZ, GGG)\n" +
"DDD = (DDD, DDD)\n" +
"EEE = (EEE, EEE)\n" +
"GGG = (GGG, GGG)\n" +
"ZZZ = (ZZZ, ZZZ)"
fmt.Println(part1.ParseNetworkMap(strings.NewReader(input)).CountSteps())
// Output: 2
}
func ExampleRepeat() {
input := "LLR\n" +
"\n" +
"AAA = (BBB, BBB)\n" +
"BBB = (AAA, ZZZ)\n" +
"ZZZ = (ZZZ, ZZZ)"
fmt.Println(part1.ParseNetworkMap(strings.NewReader(input)).CountSteps())
// Output: 6
}

View File

@ -0,0 +1,190 @@
package part2
import (
"bufio"
"io"
"sync"
)
type (
NetworkMap struct {
Starts []Node
Forks map[Node]Fork
Directions []Direction
}
Fork struct {
Left Node
Right Node
}
Ghost struct {
Steps uint64
Position Node
}
Node [3]byte
Direction byte
)
var NodeUnd Node = [3]byte{0, 0, 0}
var (
DirectionLeft Direction = 'L'
DirectionRight Direction = 'R'
)
func (n Node) IsStart() bool {
return n[2] == 'A'
}
func (n Node) IsEnd() bool {
return n[2] == 'Z'
}
func (f Fork) Peek(d Direction) Node {
switch d {
default:
return NodeUnd
case DirectionLeft:
return f.Left
case DirectionRight:
return f.Right
}
}
func ParseNetworkMap(r io.Reader) *NetworkMap {
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
networkMap := &NetworkMap{
Directions: make([]Direction, 0),
Forks: make(map[Node]Fork),
}
scanner.Scan()
for _, b := range scanner.Text() {
networkMap.Directions = append(networkMap.Directions, Direction(b))
}
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
node := Node([3]byte{line[0], line[1], line[2]})
if node.IsStart() {
networkMap.Starts = append(networkMap.Starts, node)
}
leftNode := Node([3]byte{line[7], line[8], line[9]})
rightNode := Node([3]byte{line[12], line[13], line[14]})
networkMap.Forks[node] = Fork{
Left: leftNode,
Right: rightNode,
}
}
return networkMap
}
func (nm NetworkMap) moveGhost(g *Ghost) {
for {
g.Position = nm.Forks[g.Position].Peek(nm.Directions[int(g.Steps)%len(nm.Directions)])
g.Steps++
if !g.Position.IsEnd() {
continue
}
break
}
}
func (nm NetworkMap) CountSteps() uint64 {
ghosts := make([]*Ghost, len(nm.Starts))
wg := new(sync.WaitGroup)
for i := range ghosts {
ghosts[i] = &Ghost{
Position: nm.Starts[i],
Steps: 0,
}
wg.Add(1)
go func(ghost *Ghost) {
nm.moveGhost(ghost)
wg.Done()
}(ghosts[i])
}
wg.Wait()
steps := make([]uint64, len(ghosts))
for i := range ghosts {
steps[i] = ghosts[i].Steps
}
return lcm(steps...)
}
// NOTE(toby3d): adapted version of the python code from here:
// https://www.geeksforgeeks.org/finding-lcm-two-array-numbers-without-using-gcd/
//
// Thanks /r/adventofcode for memes about this day puzzle. Today I know what the
// fuck is LCM mean.
func lcm(inputs ...uint64) uint64 {
var maxNum uint64
for i := range inputs {
maxNum = max(maxNum, inputs[i])
}
var result, factor uint64 = 1, 2
for factor <= maxNum {
indexes := make([]int, 0, len(inputs))
for i := range inputs {
if inputs[i]%factor == 0 {
indexes = append(indexes, i)
}
}
if len(indexes) >= 2 {
for i := range indexes {
inputs[indexes[i]] = inputs[indexes[i]] / factor
}
result *= factor
} else {
factor++
}
}
for i := range inputs {
result *= inputs[i]
}
return result
}
func (d Direction) String() string {
switch d {
default:
return ""
case DirectionLeft:
return "left"
case DirectionRight:
return "right"
}
}
func (d Direction) GoString() string {
return "part2.Direction(" + string(d) + ")"
}

View File

@ -0,0 +1,24 @@
package part2_test
import (
"fmt"
"strings"
"source.toby3d.me/toby3d/adventofcode/internal/day8/part2"
)
func Example() {
input := "LR\n" +
"\n" +
"11A = (11B, XXX)\n" +
"11B = (XXX, 11Z)\n" +
"11Z = (11B, XXX)\n" +
"22A = (22B, XXX)\n" +
"22B = (22C, 22C)\n" +
"22C = (22Z, 22Z)\n" +
"22Z = (22B, 22B)\n" +
"XXX = (XXX, XXX)"
fmt.Println(part2.ParseNetworkMap(strings.NewReader(input)).CountSteps())
// Output: 6
}

0
internal/day8/testdata/.gitkeep vendored Normal file
View File