Skip to content

Commit a81cf08

Browse files
authored
Implement 2021/day06 puzzle solution (#70)
* feat: Add spec. tests and skeleton for 2021/day06 puzzle * feat: Register 2021/day06 solver * tests: Update regression tests * feat: Implement 2021/day06 part1 feat(): Implement 2021/day06 part1 feat: Implement 2021/day06 part1 * feat(2021/day06): Refactor population algorithm; Implement part2 * fix: Linter warnings * tests: Update regression tests
1 parent 47a134e commit a81cf08

File tree

5 files changed

+301
-3
lines changed

5 files changed

+301
-3
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Package day06 contains solution for https://adventofcode.com/2021/day/6 puzzle.
2+
package day06
3+
4+
import (
5+
"bufio"
6+
"fmt"
7+
"io"
8+
"strconv"
9+
"strings"
10+
11+
"github.com/obalunenko/advent-of-code/internal/puzzles"
12+
)
13+
14+
func init() {
15+
puzzles.Register(solution{})
16+
}
17+
18+
type solution struct{}
19+
20+
func (solution) Day() string {
21+
return puzzles.Day06.String()
22+
}
23+
24+
func (solution) Year() string {
25+
return puzzles.Year2021.String()
26+
}
27+
28+
func (solution) Part1(input io.Reader) (string, error) {
29+
const daysObserve = 80
30+
31+
return observeFishSchool(input, daysObserve)
32+
}
33+
34+
func (solution) Part2(input io.Reader) (string, error) {
35+
const daysObserve = 256
36+
37+
return observeFishSchool(input, daysObserve)
38+
}
39+
40+
func observeFishSchool(input io.Reader, days int) (string, error) {
41+
states, err := parseSchoolFishesStates(input)
42+
if err != nil {
43+
return "", fmt.Errorf("parse school fishes states: %w", err)
44+
}
45+
46+
sch := newSchool(days)
47+
sch.addElderFishes(states)
48+
49+
sch.populate()
50+
51+
fishes := sch.getFishes()
52+
53+
return strconv.Itoa(fishes), nil
54+
}
55+
56+
func parseSchoolFishesStates(input io.Reader) ([]int, error) {
57+
scanner := bufio.NewScanner(input)
58+
59+
var res []int
60+
61+
for scanner.Scan() {
62+
line := scanner.Text()
63+
64+
states := strings.Split(line, ",")
65+
for _, s := range states {
66+
n, err := strconv.Atoi(s)
67+
if err != nil {
68+
return nil, fmt.Errorf("parse fish state: %w", err)
69+
}
70+
71+
res = append(res, n)
72+
}
73+
}
74+
75+
if err := scanner.Err(); err != nil {
76+
return nil, fmt.Errorf("scanner error: %w", err)
77+
}
78+
79+
return res, nil
80+
}
81+
82+
type school struct {
83+
days int
84+
fishes map[int]int
85+
}
86+
87+
func newSchool(daysToReproduce int) *school {
88+
return &school{
89+
days: daysToReproduce,
90+
fishes: make(map[int]int),
91+
}
92+
}
93+
94+
func (s *school) addElderFishes(fishes []int) {
95+
for _, st := range fishes {
96+
s.fishes[st]++
97+
}
98+
}
99+
100+
func (s *school) getFishes() int {
101+
var res int
102+
103+
for _, f := range s.fishes {
104+
res += f
105+
}
106+
107+
return res
108+
}
109+
110+
func (s *school) populate() {
111+
for d := s.days; d > 0; d-- {
112+
for i := 0; i <= 8; i++ {
113+
s.fishes[i-1] += s.fishes[i]
114+
s.fishes[i] = 0
115+
}
116+
117+
s.fishes[8] += s.fishes[-1]
118+
s.fishes[6] += s.fishes[-1]
119+
s.fishes[-1] = 0
120+
}
121+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package day06
2+
3+
import (
4+
"io"
5+
"strings"
6+
"testing"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func Test_solution_Year(t *testing.T) {
12+
var s solution
13+
14+
want := "2021"
15+
got := s.Year()
16+
17+
assert.Equal(t, want, got)
18+
}
19+
20+
func Test_solution_Day(t *testing.T) {
21+
var s solution
22+
23+
want := "6"
24+
got := s.Day()
25+
26+
assert.Equal(t, want, got)
27+
}
28+
29+
func Test_solution_Part1(t *testing.T) {
30+
var s solution
31+
32+
type args struct {
33+
input io.Reader
34+
}
35+
36+
tests := []struct {
37+
name string
38+
args args
39+
want string
40+
wantErr assert.ErrorAssertionFunc
41+
}{
42+
{
43+
name: "test example from description",
44+
args: args{
45+
input: strings.NewReader("3,4,3,1,2"),
46+
},
47+
want: "5934",
48+
wantErr: assert.NoError,
49+
},
50+
}
51+
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
got, err := s.Part1(tt.args.input)
55+
if !tt.wantErr(t, err) {
56+
return
57+
}
58+
59+
assert.Equal(t, tt.want, got)
60+
})
61+
}
62+
}
63+
64+
func Test_solution_Part2(t *testing.T) {
65+
var s solution
66+
67+
type args struct {
68+
input io.Reader
69+
}
70+
71+
tests := []struct {
72+
name string
73+
args args
74+
want string
75+
wantErr assert.ErrorAssertionFunc
76+
}{
77+
{
78+
name: "test example from description",
79+
args: args{
80+
input: strings.NewReader("3,4,3,1,2"),
81+
},
82+
want: "26984457539",
83+
wantErr: assert.NoError,
84+
},
85+
}
86+
87+
for _, tt := range tests {
88+
t.Run(tt.name, func(t *testing.T) {
89+
got, err := s.Part2(tt.args.input)
90+
if !tt.wantErr(t, err) {
91+
return
92+
}
93+
94+
assert.Equal(t, tt.want, got)
95+
})
96+
}
97+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# --- Day 6: Lanternfish ---
2+
3+
## --- Part One ---
4+
5+
The sea floor is getting steeper. Maybe the sleigh keys got carried this way?
6+
7+
A massive school of glowing lanternfish swims past. They must spawn quickly to reach such large numbers -
8+
maybe exponentially quickly? You should model their growth rate to be sure.
9+
10+
Although you know nothing about this specific species of lanternfish, you make some guesses about their attributes.
11+
Surely, each lanternfish creates a new lanternfish once every 7 days.
12+
13+
However, this process isn't necessarily synchronized between every lanternfish - one lanternfish might have 2 days
14+
left until it creates another lanternfish, while another might have 4. So, you can model each fish as a single number
15+
that represents the number of days until it creates a new lanternfish.
16+
17+
Furthermore, you reason, a new lanternfish would surely need slightly longer before it's capable of producing more
18+
lanternfish: two more days for its first cycle.
19+
20+
So, suppose you have a lanternfish with an internal timer value of 3:
21+
22+
- After one day, its internal timer would become 2.
23+
- After another day, its internal timer would become 1.
24+
- After another day, its internal timer would become 0.
25+
- After another day, its internal timer would reset to 6, and it would create a new lanternfish with an
26+
internal timer of 8.
27+
- After another day, the first lanternfish would have an internal timer of 5, and the second lanternfish would have
28+
an internal timer of 7.
29+
30+
A lanternfish that creates a new fish resets its timer to 6, not 7 (because 0 is included as a valid timer value).
31+
The new lanternfish starts with an internal timer of 8 and does not start counting down until the next day.
32+
33+
Realizing what you're trying to do, the submarine automatically produces a list of the ages of several hundred nearby
34+
lanternfish (your puzzle input). For example, suppose you were given the following list:
35+
36+
```text
37+
3,4,3,1,2
38+
```
39+
40+
This list means that the first fish has an internal timer of 3, the second fish has an internal timer of 4,
41+
and so on until the fifth fish, which has an internal timer of 2. Simulating these fish over several days would
42+
proceed as follows:
43+
44+
```text
45+
Initial state: 3,4,3,1,2
46+
After 1 day: 2,3,2,0,1
47+
After 2 days: 1,2,1,6,0,8
48+
After 3 days: 0,1,0,5,6,7,8
49+
After 4 days: 6,0,6,4,5,6,7,8,8
50+
After 5 days: 5,6,5,3,4,5,6,7,7,8
51+
After 6 days: 4,5,4,2,3,4,5,6,6,7
52+
After 7 days: 3,4,3,1,2,3,4,5,5,6
53+
After 8 days: 2,3,2,0,1,2,3,4,4,5
54+
After 9 days: 1,2,1,6,0,1,2,3,3,4,8
55+
After 10 days: 0,1,0,5,6,0,1,2,2,3,7,8
56+
After 11 days: 6,0,6,4,5,6,0,1,1,2,6,7,8,8,8
57+
After 12 days: 5,6,5,3,4,5,6,0,0,1,5,6,7,7,7,8,8
58+
After 13 days: 4,5,4,2,3,4,5,6,6,0,4,5,6,6,6,7,7,8,8
59+
After 14 days: 3,4,3,1,2,3,4,5,5,6,3,4,5,5,5,6,6,7,7,8
60+
After 15 days: 2,3,2,0,1,2,3,4,4,5,2,3,4,4,4,5,5,6,6,7
61+
After 16 days: 1,2,1,6,0,1,2,3,3,4,1,2,3,3,3,4,4,5,5,6,8
62+
After 17 days: 0,1,0,5,6,0,1,2,2,3,0,1,2,2,2,3,3,4,4,5,7,8
63+
After 18 days: 6,0,6,4,5,6,0,1,1,2,6,0,1,1,1,2,2,3,3,4,6,7,8,8,8,8
64+
```
65+
66+
Each day, a 0 becomes a 6 and adds a new 8 to the end of the list, while each other number decreases by 1 if it was
67+
present at the start of the day.
68+
69+
In this example, after 18 days, there are a total of 26 fish. After 80 days, there would be a total of 5934.
70+
71+
Find a way to simulate lanternfish. How many lanternfish would there be after 80 days?
72+
73+
## --- Part Two ---
74+
Suppose the lanternfish live forever and have unlimited food and space. Would they take over the entire ocean?
75+
76+
After 256 days in the example above, there would be a total of 26984457539 lanternfish!
77+
78+
How many lanternfish would there be after 256 days?

internal/puzzles/solutions/register_2021.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,6 @@ import (
1414
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2021/day04"
1515
// register day05 solution.
1616
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2021/day05"
17+
// register day06 solution.
18+
_ "github.com/obalunenko/advent-of-code/internal/puzzles/solutions/2021/day06"
1719
)

tests/regression_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,10 +2330,10 @@ func testcases2021() []testcase {
23302330
want: puzzles.Result{
23312331
Year: year,
23322332
Name: puzzles.Day06.String(),
2333-
Part1: "",
2334-
Part2: "",
2333+
Part1: "388739",
2334+
Part2: "1741362314973",
23352335
},
2336-
wantErr: true,
2336+
wantErr: false,
23372337
},
23382338
{
23392339
name: "2021/day07",

0 commit comments

Comments
 (0)