This commit is contained in:
Gabriel Bizdoc 2025-12-04 00:41:23 +02:00
commit a67735e361
No known key found for this signature in database
GPG Key ID: 3F0EDAECA5BE9ED9
6 changed files with 629 additions and 0 deletions

27
aocutils/utils.go Normal file
View File

@ -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
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module git.bizdoc.ro/public/Advent-of-Code.git
go 1.25.1

150
year25/day1.go Normal file
View File

@ -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
}

161
year25/day1_test.go Normal file
View File

@ -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
}

223
year25/day2.go Normal file
View File

@ -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()
}

65
year25/day3.go Normal file
View File

@ -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()
}