diff --git a/day04.go b/day04.go new file mode 100644 index 0000000..73d41c2 --- /dev/null +++ b/day04.go @@ -0,0 +1,123 @@ +package aoc2019 + +import ( + "io/ioutil" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func day4NumberToDigitSlice(n int64) []int { + var out []int + sn := strconv.FormatInt(n, 10) + for _, c := range sn { + d, err := strconv.Atoi(string(c)) + if err != nil { + // This should not happen as the input is a guaranteed base-10 number + panic(err) + } + out = append(out, d) + } + + return out +} + +func day4IsValidPassword(n, min, max int64) bool { + sn := day4NumberToDigitSlice(n) + + // It is a six-digit number. + if len(sn) != 6 { + return false + } + + // The value is within the range given in your puzzle input. + if n < min || n > max { + return false + } + + // Two adjacent digits are the same (like 22 in 122345). + var foundAdjacentMatch bool + for i := 1; i < len(sn); i++ { + if sn[i] == sn[i-1] { + foundAdjacentMatch = true + } + } + if !foundAdjacentMatch { + return false + } + + // Going from left to right, the digits never decrease; they only ever increase or stay the same (like 111123 or 135679). + for i := 1; i < len(sn); i++ { + if sn[i] < sn[i-1] { + return false + } + } + + return true +} + +func day4IsValidPasswordPart2(in, min, max int64) bool { + // Previous rules still apply + if !day4IsValidPassword(in, min, max) { + return false + } + + sn := day4NumberToDigitSlice(in) + + var ( + last = sn[0] + count = 1 + ) + + for i := 1; i < len(sn); i++ { + if sn[i] != last && count == 2 { + return true + } + + if sn[i] == last { + count++ + continue + } + + last = sn[i] + count = 1 + } + + return count == 2 +} + +func solveDay4WithFunction(inFile string, vf func(int64, int64, int64) bool) (int, error) { + raw, err := ioutil.ReadFile(inFile) + if err != nil { + return 0, errors.Wrap(err, "Unable to read input file") + } + + pts := strings.Split(strings.TrimSpace(string(raw)), "-") + min, err := strconv.ParseInt(pts[0], 10, 64) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse minimum") + } + + max, err := strconv.ParseInt(pts[1], 10, 64) + if err != nil { + return 0, errors.Wrap(err, "Unable to parse maximum") + } + + var count int + for i := min; i <= max; i++ { + if vf(i, min, max) { + count++ + } + } + + return count, nil +} + +func solveDay4Part1(inFile string) (int, error) { + return solveDay4WithFunction(inFile, day4IsValidPassword) +} + +func solveDay4Part2(inFile string) (int, error) { + return solveDay4WithFunction(inFile, day4IsValidPasswordPart2) +} diff --git a/day04_input.txt b/day04_input.txt new file mode 100644 index 0000000..41f9cf1 --- /dev/null +++ b/day04_input.txt @@ -0,0 +1 @@ +125730-579381 diff --git a/day04_test.go b/day04_test.go new file mode 100644 index 0000000..94ecc9a --- /dev/null +++ b/day04_test.go @@ -0,0 +1,58 @@ +package aoc2019 + +import ( + "math" + "reflect" + "testing" +) + +func TestDay4NumberToDigitSlice(t *testing.T) { + for n, expSlice := range map[int64][]int{ + 1234567: {1, 2, 3, 4, 5, 6, 7}, + 43626145: {4, 3, 6, 2, 6, 1, 4, 5}, + } { + if s := day4NumberToDigitSlice(n); !reflect.DeepEqual(expSlice, s) { + t.Errorf("Number to slice for number %d yield unexpected result: exp=%+v got=%+v", n, expSlice, s) + } + } +} + +func TestDay4ValidPassword(t *testing.T) { + for n, expValid := range map[int64]bool{ + 111111: true, + 223450: false, + 123789: false, + } { + if v := day4IsValidPassword(n, 0, math.MaxInt64); v != expValid { + t.Errorf("Number %d did not have expected validity: exp=%v got=%v", n, expValid, v) + } + } + + for n, expValid := range map[int64]bool{ + 112233: true, + 123444: false, + 111122: true, + } { + if v := day4IsValidPasswordPart2(n, 0, math.MaxInt64); v != expValid { + t.Errorf("Number %d did not have expected validity for part 2: exp=%v got=%v", n, expValid, v) + } + } +} + +func TestCalculateDay4_Part1(t *testing.T) { + count, err := solveDay4Part1("day04_input.txt") + if err != nil { + t.Fatalf("Day 4 solver failed: %s", err) + } + + t.Logf("Solution Day 4 Part 1: %d", count) +} + +func TestCalculateDay4_Part2(t *testing.T) { + count, err := solveDay4Part2("day04_input.txt") + if err != nil { + t.Fatalf("Day 4 solver failed: %s", err) + } + + t.Logf("Solution Day 4 Part 2: %d", count) +}