From 6a362c20264edcea8186cda4af9351c918af9a8f Mon Sep 17 00:00:00 2001 From: Oleg Balunenko Date: Mon, 16 Dec 2024 15:35:23 +0400 Subject: [PATCH] feat: Implement solution for 2024 day 1 --- .../puzzles/solutions/2024/day01/solution.go | 100 ++++- .../solutions/2024/day01/solution_test.go | 4 +- internal/puzzles/solutions/2024/day01/spec.md | 36 +- tests/regression_2024_test.go | 364 ++++++++++++++++++ tests/regression_test.go | 1 + 5 files changed, 500 insertions(+), 5 deletions(-) create mode 100644 tests/regression_2024_test.go diff --git a/internal/puzzles/solutions/2024/day01/solution.go b/internal/puzzles/solutions/2024/day01/solution.go index 893a23a6..94c0627c 100755 --- a/internal/puzzles/solutions/2024/day01/solution.go +++ b/internal/puzzles/solutions/2024/day01/solution.go @@ -2,7 +2,12 @@ package day01 import ( + "bufio" + "fmt" "io" + "slices" + "strconv" + "strings" "github.com/obalunenko/advent-of-code/internal/puzzles" ) @@ -22,9 +27,100 @@ func (s solution) Day() string { } func (s solution) Part1(input io.Reader) (string, error) { - return "", puzzles.ErrNotImplemented + l, err := parseInput(input) + if err != nil { + return "", fmt.Errorf("failed to parse input: %w", err) + } + + slices.Sort(l.itemsA) + slices.Sort(l.itemsB) + + var sum int + + for i := 0; i < len(l.itemsA); i++ { + d := l.itemsA[i] - l.itemsB[i] + if d < 0 { + d = -d + } + + sum += d + } + + return strconv.Itoa(sum), nil } func (s solution) Part2(input io.Reader) (string, error) { - return "", puzzles.ErrNotImplemented + l, err := parseInput(input) + if err != nil { + return "", fmt.Errorf("failed to parse input: %w", err) + } + + seenA := make(map[int]int) + + for _, a := range l.itemsA { + seenA[a] = 0 + + for _, b := range l.itemsB { + if a == b { + seenA[a]++ + } + } + } + + var sum int + + for _, a := range l.itemsA { + sum += a * seenA[a] + } + + return strconv.Itoa(sum), nil +} + +type lists struct { + itemsA []int + itemsB []int +} + +func parseInput(input io.Reader) (lists, error) { + const ( + listsNum = 2 + listAIdx = 0 + listBIdx = 1 + ) + + l := lists{ + itemsA: make([]int, 0), + itemsB: make([]int, 0), + } + + scanner := bufio.NewScanner(input) + for scanner.Scan() { + line := scanner.Text() + + parts := strings.Split(line, " ") + if len(parts) != listsNum { + return lists{}, fmt.Errorf("invalid input line: %s", line) + } + + // Parse parts[0] and parts[1] to integers and append them to l.itemsA and l.itemsB respectively. + a, err := strconv.Atoi(parts[listAIdx]) + if err != nil { + return lists{}, fmt.Errorf("failed to parse int: %w", err) + } + + b, err := strconv.Atoi(parts[listBIdx]) + if err != nil { + return lists{}, fmt.Errorf("failed to parse int: %w", err) + } + + l.itemsA = append(l.itemsA, a) + + l.itemsB = append(l.itemsB, b) + } + + if scanner.Err() != nil { + return lists{}, fmt.Errorf("scanner error: %w", scanner.Err()) + } + + return l, nil } diff --git a/internal/puzzles/solutions/2024/day01/solution_test.go b/internal/puzzles/solutions/2024/day01/solution_test.go index d5957ada..af9659e7 100755 --- a/internal/puzzles/solutions/2024/day01/solution_test.go +++ b/internal/puzzles/solutions/2024/day01/solution_test.go @@ -87,11 +87,11 @@ func Test_solution_Part2(t *testing.T) { wantErr assert.ErrorAssertionFunc }{ { - name: "", + name: "test example from description", args: args{ input: utils.ReaderFromFile(t, filepath.Join("testdata", "input.txt")), }, - want: "", + want: "31", wantErr: assert.NoError, }, { diff --git a/internal/puzzles/solutions/2024/day01/spec.md b/internal/puzzles/solutions/2024/day01/spec.md index 30ce28fb..2545ebca 100755 --- a/internal/puzzles/solutions/2024/day01/spec.md +++ b/internal/puzzles/solutions/2024/day01/spec.md @@ -66,5 +66,39 @@ Your actual left and right lists contain many location IDs. What is the total di ## --- Part Two --- - +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: + +```text +3 4 +4 3 +2 5 +1 3 +3 9 +3 3 +``` + +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)`. + +Once again consider your left and right lists. What is their similarity score? diff --git a/tests/regression_2024_test.go b/tests/regression_2024_test.go new file mode 100644 index 00000000..09341465 --- /dev/null +++ b/tests/regression_2024_test.go @@ -0,0 +1,364 @@ +package tests_test + +import ( + "testing" + + "github.com/obalunenko/advent-of-code/internal/puzzles" +) + +func testcases2024(tb testing.TB) []testcase { + year := puzzles.Year2024 + + return []testcase{ + { + name: tcName(tb, year, puzzles.Day01), + args: args{ + year: year.String(), + name: puzzles.Day01.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day01.String(), + Part1: "936063", + Part2: "23150395", + }, + wantErr: false, + }, + { + name: tcName(tb, year, puzzles.Day02), + args: args{ + year: year.String(), + name: puzzles.Day02.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day02.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day03), + args: args{ + year: year.String(), + name: puzzles.Day03.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day03.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day04), + args: args{ + year: year.String(), + name: puzzles.Day04.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day04.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day05), + args: args{ + year: year.String(), + name: puzzles.Day05.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day05.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day06), + args: args{ + year: year.String(), + name: puzzles.Day06.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day06.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day07), + args: args{ + year: year.String(), + name: puzzles.Day07.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day07.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day08), + args: args{ + year: year.String(), + name: puzzles.Day08.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day08.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day09), + args: args{ + year: year.String(), + name: puzzles.Day09.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day09.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day10), + args: args{ + year: year.String(), + name: puzzles.Day10.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day10.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day11), + args: args{ + year: year.String(), + name: puzzles.Day11.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day11.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day12), + args: args{ + year: year.String(), + name: puzzles.Day12.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day12.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day13), + args: args{ + year: year.String(), + name: puzzles.Day13.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day13.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day14), + args: args{ + year: year.String(), + name: puzzles.Day14.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day14.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day15), + args: args{ + year: year.String(), + name: puzzles.Day15.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day15.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day16), + args: args{ + year: year.String(), + name: puzzles.Day16.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day16.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day17), + args: args{ + year: year.String(), + name: puzzles.Day17.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day17.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day18), + args: args{ + year: year.String(), + name: puzzles.Day18.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day18.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day19), + args: args{ + year: year.String(), + name: puzzles.Day19.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day19.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day20), + args: args{ + year: year.String(), + name: puzzles.Day20.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day20.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day21), + args: args{ + year: year.String(), + name: puzzles.Day21.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day21.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day22), + args: args{ + year: year.String(), + name: puzzles.Day22.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day22.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day23), + args: args{ + year: year.String(), + name: puzzles.Day23.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day23.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day24), + args: args{ + year: year.String(), + name: puzzles.Day24.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day24.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + { + name: tcName(tb, year, puzzles.Day25), + args: args{ + year: year.String(), + name: puzzles.Day25.String(), + }, + want: puzzles.Result{ + Year: year.String(), + Name: puzzles.Day25.String(), + Part1: "", + Part2: "", + }, + wantErr: true, + }, + } +} diff --git a/tests/regression_test.go b/tests/regression_test.go index dc025d71..9d3f1088 100644 --- a/tests/regression_test.go +++ b/tests/regression_test.go @@ -55,6 +55,7 @@ func TestRun(t *testing.T) { tests = append(tests, testcases2021(t)...) tests = append(tests, testcases2022(t)...) tests = append(tests, testcases2023(t)...) + tests = append(tests, testcases2024(t)...) for i := range tests { tt := tests[i]