Move code out of cmd
This commit is contained in:
22
day2/digits.go
Normal file
22
day2/digits.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
func digits(n uint) []uint8 {
|
||||
if n == 0 {
|
||||
return []uint8{0}
|
||||
}
|
||||
var digits []uint8
|
||||
for ; n != 0; n /= 10 {
|
||||
digits = append(digits, uint8(n%10))
|
||||
}
|
||||
return digits
|
||||
}
|
||||
|
||||
func number(digits []uint8) uint {
|
||||
var n uint
|
||||
base := uint(1)
|
||||
for _, d := range digits {
|
||||
n += uint(d) * base
|
||||
base *= 10
|
||||
}
|
||||
return n
|
||||
}
|
||||
30
day2/digits_test.go
Normal file
30
day2/digits_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_digits_number(t *testing.T) {
|
||||
tests := []struct {
|
||||
n uint
|
||||
d []uint8
|
||||
}{
|
||||
{n: 0, d: []uint8{0}},
|
||||
{n: 1234, d: []uint8{4, 3, 2, 1}},
|
||||
{n: 12345, d: []uint8{5, 4, 3, 2, 1}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := strconv.FormatUint(uint64(tt.n), 10)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("digits", func(t *testing.T) {
|
||||
assert.Equal(t, tt.d, digits(tt.n))
|
||||
})
|
||||
t.Run("number", func(t *testing.T) {
|
||||
assert.Equal(t, tt.n, number(tt.d))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
1
day2/example.txt
Normal file
1
day2/example.txt
Normal file
@@ -0,0 +1 @@
|
||||
11-22,95-115,998-1012,1188511880-1188511890,222220-222224,1698522-1698528,446443-446449,38593856-38593862,565653-565659,824824821-824824827,2121212118-2121212124
|
||||
1
day2/input.txt
Normal file
1
day2/input.txt
Normal file
@@ -0,0 +1 @@
|
||||
959516-995437,389276443-389465477,683-1336,15687-26722,91613-136893,4-18,6736-12582,92850684-93066214,65-101,6868676926-6868700146,535033-570760,826141-957696,365650-534331,1502-2812,309789-352254,79110404-79172400,18286593-18485520,34376-65398,26-63,3333208697-3333457635,202007-307147,1859689-1936942,9959142-10053234,2318919-2420944,5142771457-5142940464,1036065-1206184,46314118-46413048,3367-6093,237-481,591751-793578
|
||||
97
day2/main.go
Normal file
97
day2/main.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
)
|
||||
|
||||
func main() {
|
||||
debug := flag.Bool("debug", false, "Debug.")
|
||||
flag.Parse()
|
||||
|
||||
ranges, err := readRanges(os.Stdin)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var sum1, sum2 uint
|
||||
for _, r := range ranges {
|
||||
invalid1 := handleRange(r, scanPart1)
|
||||
invalid2 := handleRange(r, scanPart2)
|
||||
|
||||
if *debug {
|
||||
fmt.Printf("range: %d-%d\n", r.from, r.to)
|
||||
}
|
||||
if *debug && len(invalid1) > 0 {
|
||||
fmt.Println("invalid (part 1):")
|
||||
for _, id := range invalid1 {
|
||||
fmt.Printf(" %d\n", id)
|
||||
}
|
||||
}
|
||||
if *debug && len(invalid2) > 0 {
|
||||
fmt.Println("invalid (part 2):")
|
||||
for _, id := range invalid2 {
|
||||
fmt.Printf(" %d\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range invalid1 {
|
||||
sum1 += id
|
||||
}
|
||||
for _, id := range invalid2 {
|
||||
sum2 += id
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("sum (part 1):", sum1)
|
||||
fmt.Println("sum (part 2):", sum2)
|
||||
}
|
||||
|
||||
func handleRange(r idRange, scan func(id uint) (advance uint, ok bool)) []uint {
|
||||
var invalid []uint
|
||||
for id := r.from; id <= r.to; {
|
||||
advance, ok := scan(id)
|
||||
if !ok {
|
||||
invalid = append(invalid, id)
|
||||
}
|
||||
id += advance
|
||||
}
|
||||
return invalid
|
||||
}
|
||||
|
||||
func scanPart1(id uint) (advance uint, ok bool) {
|
||||
digits := digits(id)
|
||||
if len(digits)%2 == 1 { // odd is always valid
|
||||
nextSmallestEven := uint(math.Pow10(len(digits)))
|
||||
return nextSmallestEven - id, true
|
||||
}
|
||||
var (
|
||||
x = uint(math.Pow10(len(digits) / 2))
|
||||
left = id / x
|
||||
right = id % x
|
||||
)
|
||||
return 1, left != right
|
||||
}
|
||||
|
||||
func scanPart2(id uint) (advance uint, ok bool) {
|
||||
digits := digits(id)
|
||||
if len(digits) <= 1 {
|
||||
return 1, true
|
||||
}
|
||||
|
||||
loop:
|
||||
for i := 1; i <= len(digits)/2; i++ {
|
||||
ref := digits[:i]
|
||||
for chunk := range slices.Chunk(digits, i) {
|
||||
if !slices.Equal(chunk, ref) {
|
||||
continue loop
|
||||
}
|
||||
}
|
||||
return 1, false
|
||||
}
|
||||
return 1, true
|
||||
}
|
||||
77
day2/main_test.go
Normal file
77
day2/main_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_scanPart1(t *testing.T) {
|
||||
tests := []struct {
|
||||
id uint
|
||||
advance uint
|
||||
ok bool
|
||||
}{
|
||||
// {
|
||||
// id: 9,
|
||||
// advance: 1,
|
||||
// ok: true,
|
||||
// },
|
||||
// {
|
||||
// id: 10,
|
||||
// advance: 1,
|
||||
// ok: true,
|
||||
// },
|
||||
// {
|
||||
// id: 11,
|
||||
// advance: 1,
|
||||
// ok: false,
|
||||
// },
|
||||
// {
|
||||
// id: 99,
|
||||
// advance: 1,
|
||||
// ok: false,
|
||||
// },
|
||||
// {
|
||||
// id: 100,
|
||||
// advance: 900,
|
||||
// ok: true,
|
||||
// },
|
||||
{
|
||||
id: 1234,
|
||||
advance: 1,
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := strconv.FormatUint(uint64(tt.id), 10)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
advance, ok := scanPart1(tt.id)
|
||||
assert.Equal(t, tt.advance, advance, "advance")
|
||||
assert.Equal(t, tt.ok, ok, "ok")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_scanPart2(t *testing.T) {
|
||||
tests := []struct {
|
||||
id uint
|
||||
advance uint
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
id: 111,
|
||||
advance: 1,
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
name := strconv.FormatUint(uint64(tt.id), 10)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
advance, ok := scanPart2(tt.id)
|
||||
assert.Equal(t, tt.advance, advance, "advance")
|
||||
assert.Equal(t, tt.ok, ok, "ok")
|
||||
})
|
||||
}
|
||||
}
|
||||
62
day2/range.go
Normal file
62
day2/range.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type idRange struct {
|
||||
from, to uint
|
||||
}
|
||||
|
||||
func readRanges(r io.Reader) ([]idRange, error) {
|
||||
sc := bufio.NewScanner(r)
|
||||
sc.Split(scanSimpleCSV)
|
||||
|
||||
var ranges []idRange
|
||||
for sc.Scan() {
|
||||
before, after, ok := strings.Cut(sc.Text(), "-")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("range %q: missing dash", sc.Text())
|
||||
}
|
||||
from, err := strconv.ParseUint(before, 10, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range %q: %w", sc.Text(), err)
|
||||
}
|
||||
to, err := strconv.ParseUint(after, 10, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("range %q: %w", sc.Text(), err)
|
||||
}
|
||||
if from > to {
|
||||
return nil, fmt.Errorf("range %q: refers from higher to lower", sc.Text())
|
||||
}
|
||||
ranges = append(ranges, idRange{
|
||||
from: uint(from),
|
||||
to: uint(to),
|
||||
})
|
||||
}
|
||||
return ranges, sc.Err()
|
||||
}
|
||||
|
||||
func scanSimpleCSV(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[:i], nil
|
||||
}
|
||||
if atEOF {
|
||||
if trimmed, ok := bytes.CutSuffix(data, []byte("\r\n")); ok {
|
||||
return len(data), trimmed, nil
|
||||
}
|
||||
if trimmed, ok := bytes.CutSuffix(data, []byte{'\n'}); ok {
|
||||
return len(data), trimmed, nil
|
||||
}
|
||||
return len(data), data, nil
|
||||
}
|
||||
return 0, nil, nil
|
||||
}
|
||||
Reference in New Issue
Block a user