commit a67735e3611d42e2b0b03339bfef759da3c4f148 Author: Gabriel Bizdoc Date: Thu Dec 4 00:41:23 2025 +0200 init diff --git a/aocutils/utils.go b/aocutils/utils.go new file mode 100644 index 0000000..50d5b84 --- /dev/null +++ b/aocutils/utils.go @@ -0,0 +1,27 @@ +package aocutils + +import ( + "bytes" + "io" +) + +func SplitComma(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, ','); i >= 0 { + return i + 1, data[0:i], nil + } + if atEOF { + return len(data), data, nil + } + return 0, nil, nil +} + +func ReadString(r io.Reader) (string, error) { + buff, err := io.ReadAll(r) + if err != nil { + return "", err + } + return string(buff), nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..68ea8ff --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.bizdoc.ro/public/Advent-of-Code.git + +go 1.25.1 diff --git a/year25/day1.go b/year25/day1.go new file mode 100644 index 0000000..c800362 --- /dev/null +++ b/year25/day1.go @@ -0,0 +1,150 @@ +package year25 + +import ( + "bufio" + "fmt" + "io" + "iter" + "log" + "strconv" +) + +func Day1Part1(r io.Reader, l *log.Logger) (any, error) { + dial := 50 + solution := 0 + for pair := range day1GetPairs(r) { + dial += pair.MustValue() + dial %= 100 + if dial < 0 { + dial = 100 + dial + } + if dial == 0 { + solution += 1 + } + l.Printf("The dial is rotated %s(%d) to point at 0 `once` \n", pair, pair.MustValue()) + } + + return solution, nil +} + +func Day1Part2Slow(r io.Reader, l *log.Logger) (any, error) { + dial := 50 + solution := 0 + for pair := range day1GetPairs(r) { + nextDial, count := day1AdvanceSimulation(dial, pair.MustValue()) + dial = nextDial + solution += count + + if count > 0 { + l.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count) + } else { + l.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial) + } + } + + return solution, nil +} + +func Day1Part2Fast(r io.Reader, l *log.Logger) (any, error) { + dial := 50 + solution := 0 + for pair := range day1GetPairs(r) { + value := pair.MustValue() + + nextDial, count := day1Advance(dial, value) + + dial = nextDial + solution += count + + if count > 0 { + l.Printf("The dial is rotated %s(%d) to point at %d, during this points to 0 (%d)\n", pair, pair.MustValue(), dial, count) + } else { + l.Printf("The dial is rotated %s(%d) to point at %d\n", pair, pair.MustValue(), dial) + } + } + + return solution, nil +} + +func day1GetPairs(r io.Reader) iter.Seq[Day1Pair] { + return func(yield func(Day1Pair) bool) { + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + pair := Day1Pair(scanner.Text()) + if !yield(pair) { + return + } + } + if err := scanner.Err(); err != nil { + panic(err) + } + } +} + +type Day1Pair string + +func (p Day1Pair) Value() (int, error) { + valueStr := string(p[1:]) + v, err := strconv.Atoi(valueStr) + if err != nil { + return 0, fmt.Errorf("failed to convert action value to int") + } + switch p[0] { + case 'L': + return -v, nil + case 'R': + return v, nil + default: + return 0, fmt.Errorf("unknown action value %q", p[0]) + } +} +func (p Day1Pair) MustValue() int { + v, err := p.Value() + if err != nil { + panic(err) + } + return v +} + +func day1AdvanceSimulation(dial int, value int) (int, int) { + solution := 0 + for value < 0 { + value += 1 + dial -= 1 + if dial == 0 { + solution += 1 + } + if dial == -1 { + dial = 99 + } + } + for value > 0 { + value -= 1 + dial += 1 + if dial == 100 { + dial = 0 + solution += 1 + } + } + + return dial, solution +} + +func day1Advance(dial, step int) (next int, count int) { + next = dial + step + count += next / 100 + if count < 0 { + count = -count + } + + next = next % 100 + if dial != 0 && step < 0 && next <= 0 { + count += 1 + } + + if next < 0 { + next += 100 + } + return +} diff --git a/year25/day1_test.go b/year25/day1_test.go new file mode 100644 index 0000000..483158a --- /dev/null +++ b/year25/day1_test.go @@ -0,0 +1,161 @@ +package year25 + +import ( + "testing" +) + +func TestMod(t *testing.T) { + a := -100 + b := 100 + t.Log(a % b) +} + +func TestDial(t *testing.T) { + type Args struct { + From int + Step int + + NextDial int + Count int + } + type TestCase struct { + Name string + Args + } + args := func(from, step int) Args { + a := Args{ + From: from, + Step: step, + } + a.NextDial, a.Count = simulateDial(from, step) + return a + } + cases := []TestCase{ + { + Name: "from 50 add 10", + Args: args(50, 10), + }, + { + Name: "from 50 add -10", + Args: args(50, -10), + }, + { + Name: "from 50 add 50", + Args: args(50, 50), + }, + { + Name: "from 50 add 70", + Args: args(50, 70), + }, + { + Name: "from 50 add 150", + Args: args(50, 150), + }, + { + Name: "from 50 add 155", + Args: args(50, 155), + }, + { + Name: "from 50 add 10_000", + Args: args(50, 10_000), + }, + { + Name: "from 50 add -1", + Args: args(50, -1), + }, + { + Name: "from 50 add -10", + Args: args(50, -10), + }, + { + Name: "from 50 add -50", + Args: args(50, -50), + }, + { + Name: "from 50 add -70", + Args: args(50, -70), + }, + { + Name: "from 50 add -100", + Args: args(50, -100), + }, + { + Name: "from 50 add -150", + Args: args(50, -150), + }, + { + Name: "from 50 add -10_000", + Args: args(50, -10_000), + }, + { + Name: "from 50 add 10_000", + Args: args(50, 10_000), + }, + { + Name: "from 0 add 50", + Args: args(0, 50), + }, + { + Name: "from 0 add -50", + Args: args(0, -50), + }, + { + Name: "from 0 add 100", + Args: args(0, 100), + }, + { + Name: "from 0 add -100", + Args: args(0, -100), + }, + { + Name: "from 0 add 10_000", + Args: args(0, 10_000), + }, + { + Name: "from 0 add -10_000", + Args: args(0, -10_000), + }, + { + Name: "from 22 add -34", + Args: args(22, -34), + }, + } + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + next, count := day1Advance(tc.From, tc.Step) + if next != tc.NextDial { + t.Errorf("next do not match got %d, want %d", next, tc.NextDial) + } + if count != tc.Count { + t.Errorf("count do not match got %d, want %d", count, tc.Count) + } + if !t.Failed() { + t.Log(tc.Name, "ok", "want:", count, "got:", count) + } + }) + } +} + +func simulateDial(dial int, value int) (int, int) { + solution := int(0) + for value < 0 { + value += 1 + dial -= 1 + if dial == 0 { + solution += 1 + } + if dial == -1 { + dial = 99 + } + } + for value > 0 { + value -= 1 + dial += 1 + if dial == 100 { + dial = 0 + solution += 1 + } + } + + return dial, solution +} diff --git a/year25/day2.go b/year25/day2.go new file mode 100644 index 0000000..7f5ef14 --- /dev/null +++ b/year25/day2.go @@ -0,0 +1,223 @@ +package year25 + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "strconv" + "strings" + + "git.bizdoc.ro/private/Advent-of-Code.git/pkg/aocutils" +) + +type day2Scanner struct { + scanner *bufio.Scanner + sep byte + a, b int + err error +} + +func (s *day2Scanner) Err() error { + return s.err +} +func (s *day2Scanner) Scan() bool { + if s.scanner.Scan() { + interval := s.scanner.Bytes() + i := bytes.IndexByte(interval, s.sep) + if i <= 0 { + s.err = fmt.Errorf("interval separator not found in line %s", string(interval)) + return false + } + start := interval[:i] + end := interval[i+1:] + + s.a, s.err = strconv.Atoi(string(start)) + if s.err != nil { + s.err = fmt.Errorf("failed to scann number: %s", s.err) + return false + } + s.b, s.err = strconv.Atoi(string(end)) + if s.err != nil { + s.err = fmt.Errorf("failed to scann number: %s", s.err) + return false + } + return true + } + return false +} +func (s *day2Scanner) Bounds() (int, int) { + return s.a, s.b +} + +func Day2Part1Simple(r io.Reader, l *log.Logger) (any, error) { + input, err := aocutils.ReadString(r) + if err != nil { + return nil, fmt.Errorf("failed to read input: %s", err) + } + ranges := strings.Split(strings.TrimSpace(input), ",") + + isRepeated := func(n int) bool { + str := strconv.Itoa(n) + if len(str)%2 != 0 { + return false + } + half := len(str) / 2 + return str[:half] == str[half:] + } + + var sum int + + for _, r := range ranges { + parts := strings.Split(r, "-") + if len(parts) != 2 { + continue + } + + start, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, fmt.Errorf("failed to parse number: %s", parts[0]) + } + + end, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse number: %s", parts[1]) + } + + for v := start; v <= end; v++ { + if isRepeated(v) { + sum += v + } + } + } + + return sum, nil +} + +func Day2Part2Simple(r io.Reader, l *log.Logger) (any, error) { + input, err := aocutils.ReadString(r) + if err != nil { + return nil, fmt.Errorf("failed to read input: %s", err) + } + ranges := strings.Split(strings.TrimSpace(input), ",") + + isRepeated := func(n int) bool { + s := strconv.Itoa(n) + l := len(s) + + // Try all possible pattern lengths + for p := 1; p*2 <= l; p++ { // p <= l/2 ensures at least 2 repeats + if l%p != 0 { + continue + } + + pat := s[:p] + ok := true + for i := 0; i < l; i += p { + if s[i:i+p] != pat { + ok = false + break + } + } + if ok { + return true + } + } + return false + } + + var sum int + + for _, r := range ranges { + parts := strings.Split(r, "-") + if len(parts) != 2 { + continue + } + + start, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, fmt.Errorf("failed to parse number: %s", parts[0]) + } + + end, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse number: %s", parts[1]) + } + + for v := start; v <= end; v++ { + if isRepeated(v) { + sum += v + } + } + } + + return sum, nil +} + +func Day2Part1(r io.Reader, logger *log.Logger) (any, error) { + inputScanner := bufio.NewScanner(r) + inputScanner.Split(aocutils.SplitComma) + scanner := &day2Scanner{scanner: inputScanner, sep: '-'} + + var sum int + + for scanner.Scan() { + start, end := scanner.Bounds() + logger.Println(start, "-", end) + + for i := start; i <= end; i++ { + str := []byte(strconv.Itoa(i)) + if len(str)%2 == 0 { + firstHalf := str[:len(str)/2] + secondHalf := str[len(str)/2:] + if bytes.Equal(firstHalf, secondHalf) { + logger.Println("found ", i) + sum += i + } + } + } + } + + return sum, scanner.Err() +} + +func Day2Part2(r io.Reader, logger *log.Logger) (any, error) { + inputScanner := bufio.NewScanner(r) + inputScanner.Split(aocutils.SplitComma) + + scanner := &day2Scanner{scanner: inputScanner, sep: '-'} + + var sum int + for scanner.Scan() { + start, end := scanner.Bounds() + logger.Println(start, "-", end) + + for i := start; i <= end; i++ { + str := []byte(strconv.Itoa(i)) + for digitIndex := 1; digitIndex*2 <= len(str); digitIndex++ { + if len(str)%digitIndex != 0 { + // skip cant compare + continue + } + + isInvalid := true + + for chunkStart := digitIndex; chunkStart+digitIndex <= len(str); chunkStart += digitIndex { + if !bytes.Equal(str[0:digitIndex], str[chunkStart:chunkStart+digitIndex]) { + isInvalid = false + break + } + } + + if isInvalid { + logger.Println("found ", i) + sum += i + break + } + } + } + } + + return sum, scanner.Err() +} diff --git a/year25/day3.go b/year25/day3.go new file mode 100644 index 0000000..07d8519 --- /dev/null +++ b/year25/day3.go @@ -0,0 +1,65 @@ +package year25 + +import ( + "bufio" + "fmt" + "io" + "log" + "strings" +) + +func Day3Part1(r io.Reader, l *log.Logger) (any, error) { + return Day3(r, l, 2) +} +func Day3Part2(r io.Reader, l *log.Logger) (any, error) { + return Day3(r, l, 12) +} +func Day3(r io.Reader, logger *log.Logger, size int) (int64, error) { + scanner := bufio.NewScanner(r) + scanner.Split(bufio.ScanLines) + var sum int64 + + findLargetDigit := func(digits []rune) (rune, int) { + largestDigit := rune(0) + index := 0 + + for i := len(digits) - 1; i >= 0; i-- { + if digits[i] >= largestDigit { + largestDigit = digits[i] + index = i + } + } + return rune(largestDigit) - '0', index + } + + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if text == "" { + continue + } + line := []rune(text) + parts := make([]byte, size) + + for index := range parts { + digit, start := findLargetDigit(line[:len(line)+index-len(parts)+1]) + line = line[start+1:] + if digit < 0 || digit > 9 { + return 0, fmt.Errorf("ivalid char '%c' in line %s", digit, text) + } else { + parts[index] = byte(digit) + } + } + logger.Println("line: ", text) + + var partSum int64 + for _, part := range parts { + partSum *= 10 + partSum += int64(part) + } + logger.Println("got: ", partSum) + logger.Println() + sum += partSum + } + + return sum, scanner.Err() +}