init
This commit is contained in:
commit
a67735e361
27
aocutils/utils.go
Normal file
27
aocutils/utils.go
Normal 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
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module git.bizdoc.ro/public/Advent-of-Code.git
|
||||
|
||||
go 1.25.1
|
||||
150
year25/day1.go
Normal file
150
year25/day1.go
Normal 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
161
year25/day1_test.go
Normal 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
223
year25/day2.go
Normal 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
65
year25/day3.go
Normal 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()
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user