diff --git a/.gitea/workflows/github-mirror.yml b/.gitea/workflows/github-mirror.yml new file mode 100644 index 0000000..ae11073 --- /dev/null +++ b/.gitea/workflows/github-mirror.yml @@ -0,0 +1,30 @@ +name: Go Bump + +on: + push: + branches: [ "main" ] + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Deploy to external repository + uses: https://git.sour.is/actions/github-action-push-to-another-repository@main + env: + API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }} + with: + # GitHub Action output files + source-directory: . + destination-github-username: sour-is + destination-repository-name: go-pkg + user-email: jon@xuu.cc + # It defaults to `main` + target-branch: "main" + + - run: echo "🍏 This job's status is ${{ job.status }}." + diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml new file mode 100644 index 0000000..08ea354 --- /dev/null +++ b/.gitea/workflows/test.yml @@ -0,0 +1,33 @@ +name: Go Test + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo "πŸŽ‰ The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" + - run: echo "πŸ”Ž The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." + + - uses: actions/checkout@v3 + - run: echo "πŸ’‘ The ${{ gitea.repository }} repository has been cloned to the runner." + - run: echo "πŸ–₯️ The workflow is now ready to test your code on the runner." + + - name: List files in the repository + run: | + ls ${{ gitea.workspace }} + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21.3 + + - name: Test + run: go test --race -cover ./... + + - run: echo "🍏 This job's status is ${{ job.status }}." diff --git a/day01/example1.txt b/day01/example1.txt new file mode 100644 index 0000000..1ba8437 --- /dev/null +++ b/day01/example1.txt @@ -0,0 +1,4 @@ +1abc2 +pqr3stu8vwx +a1b2c3d4e5f +treb7uchet \ No newline at end of file diff --git a/day01/example2.txt b/day01/example2.txt new file mode 100644 index 0000000..4316a6b --- /dev/null +++ b/day01/example2.txt @@ -0,0 +1,7 @@ +two1nine +eightwothree +abcone2threexyz +xtwone3four +4nineeightseven2 +zoneight234 +7pqrstsixteen \ No newline at end of file diff --git a/day01/main.go b/day01/main.go index ea46084..4877958 100644 --- a/day01/main.go +++ b/day01/main.go @@ -2,25 +2,41 @@ package main import ( "bufio" - "bytes" _ "embed" "fmt" + "os" "strings" + + aoc "go.sour.is/advent-of-code-2023" ) -//go:embed input.txt -var input []byte - func main() { - buf := bytes.NewReader(input) - scan := bufio.NewScanner(buf) + result, err := aoc.Runner(run) + if err != nil { + fmt.Println("ERR", err) + os.Exit(1) + } + + fmt.Println(result) +} + +type result struct { + sum int + sum2 int +} + +func (r result) String() string { + return fmt.Sprintln("result pt1:", r.sum, "\nresult pt2:", r.sum2) +} + +var numbers = []string{"zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"} + +func run(scan *bufio.Scanner) (*result, error) { + result := &result{} - sum := 0 for scan.Scan() { var first, last int - - orig := scan.Text() - _ = orig + var first2, last2 int text := scan.Text() @@ -30,110 +46,66 @@ func main() { switch { case slice[0] >= '0' && slice[0] <= '9': - first = int(slice[0] - '0') - case strings.HasPrefix(string(slice), "one"): - first = 1 - case strings.HasPrefix(string(slice), "two"): - first = 2 - case strings.HasPrefix(string(slice), "three"): - first = 3 - case strings.HasPrefix(string(slice), "four"): - first = 4 - case strings.HasPrefix(string(slice), "five"): - first = 5 - case strings.HasPrefix(string(slice), "six"): - first = 6 - case strings.HasPrefix(string(slice), "seven"): - first = 7 - case strings.HasPrefix(string(slice), "eight"): - first = 8 - case strings.HasPrefix(string(slice), "nine"): - first = 9 + if first == 0 { + first = int(slice[0] - '0') + } + if first2 == 0 { + first2 = int(slice[0] - '0') + } + default: + if first2 != 0 { + continue + } + for i, s := range numbers { + if strings.HasPrefix(string(slice), s) { + first2 = i + break + } + } } - if first != 0 { + if first != 0 && first2 != 0 { break } } - text = string(reverse([]rune(text))) + text = string(aoc.Reverse([]rune(text))) for i := range text { copy(slice, []rune(text[i:])) - slice = reverse(slice) + slice = aoc.Reverse(slice) switch { case slice[4] >= '0' && slice[4] <= '9': - last = int(slice[4] - '0') - case strings.HasSuffix(string(slice), "one"): - last = 1 - case strings.HasSuffix(string(slice), "two"): - last = 2 - case strings.HasSuffix(string(slice), "three"): - last = 3 - case strings.HasSuffix(string(slice), "four"): - last = 4 - case strings.HasSuffix(string(slice), "five"): - last = 5 - case strings.HasSuffix(string(slice), "six"): - last = 6 - case strings.HasSuffix(string(slice), "seven"): - last = 7 - case strings.HasSuffix(string(slice), "eight"): - last = 8 - case strings.HasSuffix(string(slice), "nine"): - last = 9 + if last == 0 { + last = int(slice[4] - '0') + } + if last2 == 0 { + last2 = int(slice[4] - '0') + } + default: + if last2 != 0 { + continue + } + for i, s := range numbers { + if strings.HasSuffix(string(slice), s) { + last2 = i + break + } + } } - if last != 0 { + if last != 0 && last2 != 0 { break } } - sum += first*10 + last + result.sum += first*10 + last + result.sum2 += first2*10 + last2 } if err := scan.Err(); err != nil { - panic(err) + return nil, err } - fmt.Println(sum) + return result, nil } - -func reverse[T any](arr []T) []T{ - for i := 0; i < len(arr)/2; i++ { - arr[i], arr[len(arr)-i-1] = arr[len(arr)-i-1], arr[i] - } - return arr -} - - -// type sorter[T rune | int] []T - -// func (s sorter[T]) Less(i, j int) bool { return s[i] < s[j] } -// func (s sorter[T]) Len() int { return len(s) } -// func (s sorter[T]) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -/* -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -one -two -three -four -five -six -seven -eight -nine -ten - - -*/ diff --git a/day01/main_test.go b/day01/main_test.go new file mode 100644 index 0000000..75dbfc5 --- /dev/null +++ b/day01/main_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "bufio" + "bytes" + "testing" + + _ "embed" + + "github.com/matryer/is" +) + +//go:embed example1.txt +var example1 []byte + +//go:embed example2.txt +var example2 []byte + +//go:embed input.txt +var input []byte + +func TestExample1(t *testing.T) { + is := is.New(t) + scan := bufio.NewScanner(bytes.NewReader(example1)) + + result, err := run(scan) + is.NoErr(err) + + t.Log(result) + is.Equal(result.sum, 142) +} + + +func TestExample2(t *testing.T) { + is := is.New(t) + scan := bufio.NewScanner(bytes.NewReader(example2)) + + result, err := run(scan) + is.NoErr(err) + + t.Log(result) + is.Equal(result.sum2, 281) +} +func TestInput(t *testing.T) { + is := is.New(t) + scan := bufio.NewScanner(bytes.NewReader(input)) + + result, err := run(scan) + is.NoErr(err) + + t.Log(result) + is.Equal(result.sum, 54573) + is.Equal(result.sum2, 54591) +} diff --git a/day02/main.go b/day02/main.go index 7f2f2b2..f9759c2 100644 --- a/day02/main.go +++ b/day02/main.go @@ -2,24 +2,39 @@ package main import ( "bufio" - "bytes" _ "embed" "fmt" + "os" "strconv" "strings" + + aoc "go.sour.is/advent-of-code-2023" ) -//go:embed input.txt -var input []byte +func main() { + result, err := aoc.Runner(run) + if err != nil { + aoc.Log("ERR", err) + os.Exit(1) + } + + aoc.Log(result) +} + +type result struct { + sum int + powerSum int +} + +func (r result) String() string { + return fmt.Sprintln("result pt1:", r.sum, "\nresult pt2:", r.powerSum) +} type gameResult struct { red, green, blue int } -func main() { - buf := bytes.NewReader(input) - scan := bufio.NewScanner(buf) - +func run(scan *bufio.Scanner) (*result, error) { // only 12 red cubes, 13 green cubes, and 14 blue cubes maxCounts := gameResult{ red: 12, @@ -62,8 +77,8 @@ func main() { } } - fmt.Println(games) - fmt.Println(len(games)) + aoc.Log(games) + aoc.Log(len(games)) sum := 0 powerSum := 0 @@ -81,24 +96,24 @@ func main() { mins.blue = max(mins.blue, round.blue) if maxCounts.red < round.red { - fmt.Println("game", i, round, "too many red", round.red) + aoc.Log("game", i, round, "too many red", round.red) ok = false } else if maxCounts.blue < round.blue { - fmt.Println("game", i, round, "too many blue", round.blue) + aoc.Log("game", i, round, "too many blue", round.blue) ok = false } else if maxCounts.green < round.green { - fmt.Println("game", i, round, "too many green", round.green) + aoc.Log("game", i, round, "too many green", round.green) ok = false } - fmt.Println("game", i, round, ok) + aoc.Log("game", i, round, ok) } if ok { sum += i - fmt.Println("game", i, "passes", sum) + aoc.Log("game", i, "passes", sum) } - power := mins.red*mins.blue*mins.green - fmt.Println("game", i, "mins", mins, power) + power := mins.red * mins.blue * mins.green + aoc.Log("game", i, "mins", mins, power) powerSum += power } - fmt.Println("sum", sum, "power", powerSum) + return &result{sum, powerSum}, nil } diff --git a/day02/main_test.go b/day02/main_test.go new file mode 100644 index 0000000..23215eb --- /dev/null +++ b/day02/main_test.go @@ -0,0 +1,41 @@ +package main + +import ( + "bufio" + "bytes" + "testing" + + _ "embed" + + "github.com/matryer/is" +) + +//go:embed example.txt +var example []byte + +//go:embed input.txt +var input []byte + +func TestExample(t *testing.T) { + is := is.New(t) + scan := bufio.NewScanner(bytes.NewReader(example)) + + result, err := run(scan) + is.NoErr(err) + + t.Log(result) + is.Equal(result.sum, 8) + is.Equal(result.powerSum, 2286) +} + +func TestSolution(t *testing.T) { + is := is.New(t) + scan := bufio.NewScanner(bytes.NewReader(input)) + + result, err := run(scan) + is.NoErr(err) + + t.Log(result) + is.Equal(result.sum, 2317) + is.Equal(result.powerSum, 74804) +} diff --git a/day08/main.go b/day08/main.go index 3a9cb66..ec9f332 100644 --- a/day08/main.go +++ b/day08/main.go @@ -5,21 +5,12 @@ import ( "fmt" "os" "strings" + + aoc "go.sour.is/advent-of-code-2023" ) func main() { - if len(os.Args) != 2 { - fmt.Fprintln(os.Stderr, "Usage: day08 FILE") - } - - input, err := os.Open(os.Args[1]) - if err != nil { - fmt.Fprintln(os.Stderr, err) - } - - scan := bufio.NewScanner(input) - - result, err := run(scan) + result, err := aoc.Runner(run) if err != nil { fmt.Println("ERR", err) os.Exit(1) @@ -29,10 +20,14 @@ func main() { } type result struct { - stepsPT1 int + stepsPT1 uint64 stepsPT2 uint64 } +func (r result) String() string { + return fmt.Sprintf("solution 1: %v\nsolution 2: %v\n", r.stepsPT1, r.stepsPT2) +} + func run(scan *bufio.Scanner) (*result, error) { var path []rune m := make(nodeMap) @@ -64,8 +59,8 @@ func run(scan *bufio.Scanner) (*result, error) { return nil, err } - steps1 := SolutionPT1(m, path) - steps2 := SolutionPT2(m, path) + steps1 := m.SolvePT1(path) + steps2 := m.SolvePT2(path) return &result{steps1, steps2}, nil } @@ -96,26 +91,25 @@ func (m nodeMap) mapNodes() error { return nil } -func SolutionPT1(m nodeMap, path []rune) int { - fmt.Println("---- PART 1 BEGIN ----") - position, ok := m["AAA"] +func (m nodeMap) solver(start string, isEnd func(string) bool, path []rune) uint64 { + position, ok := m[start] if !ok { return 0 } var i int - var steps int + var steps uint64 - for steps < 100000 { + for steps < ^uint64(0) { steps++ if path[i] == 'R' { - fmt.Println("step", steps, position.value, "R->", position.rvalue) + // fmt.Println("step", steps, position.value, "R->", position.rvalue) position = position.right } else { - fmt.Println("step", steps, position.value, "L->", position.lvalue) + // fmt.Println("step", steps, position.value, "L->", position.lvalue) position = position.left } - if position.value == "ZZZ" { + if isEnd(position.value) { break } @@ -124,126 +118,33 @@ func SolutionPT1(m nodeMap, path []rune) int { i = 0 } } - fmt.Println("---- PART 1 END ----") return steps } -func SolutionPT2(m nodeMap, path []rune) uint64 { +func (m nodeMap) SolvePT1(path []rune) uint64 { + fmt.Println("---- PART 1 BEGIN ----") + defer fmt.Println("---- PART 1 END ----") + + return m.solver("AAA", func(s string) bool { return s == "ZZZ" }, path) +} + +func (m nodeMap) SolvePT2(path []rune) uint64 { fmt.Println("---- PART 2 BEGIN ----") + defer fmt.Println("---- PART 2 END ----") - type loop struct { - start, position, end *node - steps uint64 - } - loops := make(map[*node]loop) + var starts []*node - endpoints := make(map[*node]struct{}) for k, n := range m { if strings.HasSuffix(k, "A") { fmt.Println("start", k) - loops[n] = loop{start: n, position: n} - } - - if strings.HasSuffix(k, "Z") { - fmt.Println("stop", k) - endpoints[n] = struct{}{} + starts = append(starts, n) } } - var i int - var steps uint64 - var stops int - maxUint := ^uint64(0) - loopsFound := 0 - - for steps < maxUint { - steps++ - if path[i] == 'R' { - for k, loop := range loops { - // fmt.Println("step", steps, position.value, "R->", position.rvalue) - loop.position = loop.position.right - loops[k] = loop - } - } else { - for k, loop := range loops { - // fmt.Println("step", steps, position.value, "L->", position.lvalue) - loop.position = loop.position.left - loops[k] = loop - } - } - - done := true - s := 0 - for k, loop := range loops { - if _, ok := endpoints[loop.position]; !ok { - // fmt.Println("no stop", i, position.value) - done = false - // break - } else { - // fmt.Println("stop", i, position.value) - if loop.end == nil { - loop.end = loop.position - loop.steps = steps - fmt.Println("loop found", loop.position.value, "steps", steps) - loops[k] = loop - loopsFound++ - } - s++ - } - } - - if loopsFound == len(loops) { - var values []uint64 - for _, loop := range loops { - values = append(values, loop.steps) - } - return LCM(values...) - } - - if s > stops { - stops = s - fmt.Println("stops", stops, "steps", steps) - } - - if done { - break - } - - i++ - if i > len(path)-1 { - i = 0 - } + loops := make([]uint64, len(starts)) + for i, n := range starts { + loops[i] = m.solver(n.value, func(s string) bool { return strings.HasSuffix(s, "Z") }, path) } - - fmt.Println("---- PART 2 END ----") - return steps + return aoc.LCM(loops...) } -// greatest common divisor (GCD) via Euclidean algorithm -func GCD(a, b uint64) uint64 { - for b != 0 { - t := b - b = a % b - a = t - } - return a -} - -// find Least Common Multiple (LCM) via GCD -func LCM(integers ...uint64) uint64 { - if len(integers) == 0 { - return 0 - } - if len(integers) == 1 { - return integers[0] - } - - a, b := integers[0], integers[1] - result := a * b / GCD(a, b) - - for _, c := range integers[2:] { - result = LCM(result, c) - } - - return result -} diff --git a/day08/main_test.go b/day08/main_test.go index 70b0675..4550f4a 100644 --- a/day08/main_test.go +++ b/day08/main_test.go @@ -30,7 +30,7 @@ func TestExample1(t *testing.T) { is.NoErr(err) t.Log(result.stepsPT1) - is.Equal(result.stepsPT1, 2) + is.Equal(result.stepsPT1, uint64(2)) } func TestExample2(t *testing.T) { @@ -41,7 +41,7 @@ func TestExample2(t *testing.T) { is.NoErr(err) t.Log(result.stepsPT1) - is.Equal(result.stepsPT1, 6) + is.Equal(result.stepsPT1, uint64(6)) } func TestExample3(t *testing.T) { @@ -63,10 +63,18 @@ func TestInput(t *testing.T) { is.NoErr(err) t.Log("part1 solution", result.stepsPT1) - is.Equal(result.stepsPT1, 14429) + is.Equal(result.stepsPT1, uint64(14429)) + t.Log("part2 solution", result.stepsPT2) is.Equal(result.stepsPT2, uint64(10921547990923)) } // first: 14429 -// second: 10921547990923 \ No newline at end of file +// second: 10921547990923 + +// BrΓΌt +// stops 1 steps 13201 +// stops 2 steps 620447 +// stops 3 steps 36606373 +// stops 4 steps 2232988753 +// stops 5 steps 149610246451 diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..05f87aa --- /dev/null +++ b/tools.go @@ -0,0 +1,76 @@ +package aoc + +import ( + "bufio" + "fmt" + "os" + "strings" +) + +func Runner[R any, F func(*bufio.Scanner) (R, error)](run F) (R, error) { + if len(os.Args) != 2 { + Log("Usage:", os.Args[0], "FILE") + os.Exit(22) + } + + input, err := os.Open(os.Args[1]) + if err != nil { + Log(err) + os.Exit(1) + } + + scan := bufio.NewScanner(input) + return run(scan) +} + +func Log(v ...any) { fmt.Fprintln(os.Stderr, v...) } +func Logf(format string, v ...any) { + if !strings.HasSuffix(format, "\n") { + format += "\n" + } + fmt.Fprintf(os.Stderr, format, v...) +} + +func Reverse[T any](arr []T) []T { + for i := 0; i < len(arr)/2; i++ { + arr[i], arr[len(arr)-i-1] = arr[len(arr)-i-1], arr[i] + } + return arr +} + +type integer interface { + int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 +} +// type float interface { +// complex64 | complex128 | float32 | float64 +// } +// type number interface{ integer | float } + +// greatest common divisor (GCD) via Euclidean algorithm +func GCD[T integer](a, b T) T { + for b != 0 { + t := b + b = a % b + a = t + } + return a +} + +// find Least Common Multiple (LCM) via GCD +func LCM[T integer](integers ...T) T { + if len(integers) == 0 { + return 0 + } + if len(integers) == 1 { + return integers[0] + } + + a, b := integers[0], integers[1] + result := a * b / GCD(a, b) + + for _, c := range integers[2:] { + result = LCM(result, c) + } + + return result +}