Skip to content

Commit bf40213

Browse files
authored
Merge pull request #3 from oleg-balunenko/refactor
cmd: Implement cli to run puzzles internal/solutions: Refactor day01 to implement Solver interface internal/puzzle: Add Solver interface and Run solutions func
2 parents 1dc62a1 + c8276f5 commit bf40213

File tree

11 files changed

+561
-202
lines changed

11 files changed

+561
-202
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13+
14+
.idea
15+
16+
bin/

cmd/main.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"strconv"
10+
11+
"github.com/pkg/errors"
12+
13+
"github.com/oleg-balunenko/advent-of-code/internal/puzzle"
14+
_ "github.com/oleg-balunenko/advent-of-code/internal/solutions/day01"
15+
)
16+
17+
var (
18+
inputDir = flag.String(
19+
"input_dir",
20+
filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "oleg-balunenko", "advent-of-code", "input"),
21+
"Path to directory with puzzles input files")
22+
)
23+
24+
func main() {
25+
flag.Parse()
26+
27+
menu()
28+
}
29+
30+
func menu() {
31+
fmt.Println("Menu:")
32+
33+
solvers := puzzle.Solvers()
34+
35+
var choices = make(map[string]string, len(solvers))
36+
37+
for i, s := range solvers {
38+
choices[strconv.Itoa(i+1)] = s
39+
fmt.Printf("%d. %s \n", i+1, s)
40+
}
41+
42+
var text string
43+
44+
for {
45+
_, err := fmt.Scanln(&text)
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
50+
if pname, ok := choices[text]; !ok {
51+
fmt.Println("wrong choice, try again")
52+
} else {
53+
run(pname)
54+
return
55+
}
56+
}
57+
}
58+
59+
func run(name string) {
60+
s, err := puzzle.GetSolver(name)
61+
if err != nil {
62+
log.Fatal(errors.Wrap(err, "failed to get solver"))
63+
}
64+
65+
input := filepath.Join(*inputDir, fmt.Sprintf("%s.txt", name))
66+
67+
if err := puzzle.Run(s, input); err != nil {
68+
log.Fatal(errors.Wrap(err, "failed to run puzzle solver"))
69+
}
70+
}

input/day02.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1,0,0,3,1,1,2,3,1,3,4,3,1,5,0,3,2,10,1,19,2,9,19,23,2,13,23,27,1,6,27,31,2,6,31,35,2,13,35,39,1,39,10,43,2,43,13,47,1,9,47,51,1,51,13,55,1,55,13,59,2,59,13,63,1,63,6,67,2,6,67,71,1,5,71,75,2,6,75,79,1,5,79,83,2,83,6,87,1,5,87,91,1,6,91,95,2,95,6,99,1,5,99,103,1,6,103,107,1,107,2,111,1,111,5,0,99,2,14,0,0

internal/puzzle/solver.go

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package puzzle
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"io/ioutil"
7+
"log"
8+
"sort"
9+
"sync"
10+
11+
"github.com/pkg/errors"
12+
)
13+
14+
// Solver represents solutions for puzzles methods.
15+
type Solver interface {
16+
Part1(input io.Reader) (string, error)
17+
Part2(input io.Reader) (string, error)
18+
Name() string
19+
}
20+
21+
var (
22+
solversMu sync.RWMutex
23+
solvers = make(map[string]Solver)
24+
)
25+
26+
// Register makes a puzzle solver available by the provided name.
27+
// If Register is called twice with the same name or if solver is nil,
28+
// it panics.
29+
func Register(name string, solver Solver) {
30+
solversMu.Lock()
31+
defer solversMu.Unlock()
32+
33+
if solver == nil {
34+
panic("puzzle: Register solver is nil")
35+
}
36+
37+
if _, dup := solvers[name]; dup {
38+
panic("puzzle: Register called twice for solver " + name)
39+
}
40+
41+
solvers[name] = solver
42+
}
43+
44+
// Solvers returns a sorted list of the names of the registered puzzle solvers.
45+
func Solvers() []string {
46+
solversMu.RLock()
47+
defer solversMu.RUnlock()
48+
49+
list := make([]string, 0, len(solvers))
50+
51+
for name := range solvers {
52+
list = append(list, name)
53+
}
54+
55+
sort.Strings(list)
56+
57+
return list
58+
}
59+
60+
// GetSolver returns registered solver by passed puzzle name.
61+
func GetSolver(name string) (Solver, error) {
62+
if name == "" {
63+
return nil, errors.New("empty puzzle name")
64+
}
65+
66+
solversMu.Lock()
67+
defer solversMu.Unlock()
68+
69+
s, exist := solvers[name]
70+
71+
if !exist {
72+
return nil, errors.Errorf("unknown puzzle name [%s]", name)
73+
}
74+
75+
return s, nil
76+
}
77+
78+
// Run uses solver of puzzle and path to input.
79+
func Run(solver Solver, filepath string) error {
80+
var (
81+
input []byte
82+
res string
83+
err error
84+
)
85+
86+
input, err = ioutil.ReadFile(filepath)
87+
if err != nil {
88+
return errors.Wrap(err, "failed to open file")
89+
}
90+
91+
log.Printf("run puzzle solver [%s]\n", solver.Name())
92+
93+
res, err = solver.Part1(bytes.NewBuffer(input))
94+
if err != nil {
95+
return errors.Wrapf(err, "failed to run Part1 for puzzle [%s]", solver.Name())
96+
}
97+
98+
log.Printf("Part1 answer: %s \n", res)
99+
100+
res, err = solver.Part2(bytes.NewBuffer(input))
101+
if err != nil {
102+
return errors.Wrapf(err, "failed to run Part2 for puzzle [%s]", solver.Name())
103+
}
104+
105+
log.Printf("Part2 answer: %s \n", res)
106+
107+
return nil
108+
}

internal/solutions/day01/fuel.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package day01
2+
3+
import (
4+
"bufio"
5+
"io"
6+
"strconv"
7+
8+
"github.com/pkg/errors"
9+
10+
"github.com/oleg-balunenko/advent-of-code/internal/puzzle"
11+
)
12+
13+
type solver struct {
14+
name string
15+
}
16+
17+
func init() {
18+
const puzzleName = "day01"
19+
20+
puzzle.Register(puzzleName, solver{
21+
name: puzzleName,
22+
})
23+
}
24+
25+
func (s solver) Part1(input io.Reader) (string, error) {
26+
return calc(input, calcPart1)
27+
}
28+
29+
func (s solver) Part2(input io.Reader) (string, error) {
30+
return calc(input, calcPart2)
31+
}
32+
33+
func (s solver) Name() string {
34+
return s.name
35+
}
36+
37+
type module struct {
38+
mass int
39+
}
40+
41+
func (m module) fuel() int {
42+
mass := m.mass
43+
44+
diff := mass % 3
45+
if diff != 0 {
46+
mass = mass - diff
47+
}
48+
49+
f := (mass / 3) - 2
50+
51+
return f
52+
}
53+
54+
type calcFunc func(in chan module, res chan int, done chan struct{})
55+
56+
func calc(input io.Reader, calcFn calcFunc) (string, error) {
57+
var (
58+
lines int
59+
mass int
60+
sum int
61+
err error
62+
)
63+
64+
in := make(chan module)
65+
res := make(chan int)
66+
done := make(chan struct{})
67+
68+
go calcFn(in, res, done)
69+
70+
scanner := bufio.NewScanner(input)
71+
72+
for scanner.Scan() {
73+
mass, err = strconv.Atoi(scanner.Text())
74+
if err != nil {
75+
return "", err
76+
}
77+
78+
in <- module{
79+
mass: mass,
80+
}
81+
lines++
82+
}
83+
84+
close(in)
85+
86+
if err = scanner.Err(); err != nil {
87+
return "", errors.Wrap(err, "scanner error")
88+
}
89+
90+
for lines > 0 {
91+
select {
92+
case r := <-res:
93+
sum += r
94+
case <-done:
95+
lines--
96+
}
97+
}
98+
99+
close(res)
100+
101+
return strconv.Itoa(sum), nil
102+
}
103+
104+
func calcPart1(in chan module, res chan int, done chan struct{}) {
105+
for i := range in {
106+
go func(m module, res chan int, done chan struct{}) {
107+
f := m.fuel()
108+
109+
res <- f
110+
111+
done <- struct{}{}
112+
}(i, res, done)
113+
}
114+
}
115+
116+
func calcPart2(in chan module, res chan int, done chan struct{}) {
117+
for i := range in {
118+
go func(m module, res chan int, done chan struct{}) {
119+
var isDone bool
120+
121+
for !isDone {
122+
f := m.fuel()
123+
124+
res <- f
125+
126+
if f/3 > 1 {
127+
m.mass = f
128+
} else {
129+
isDone = true
130+
}
131+
}
132+
133+
done <- struct{}{}
134+
}(i, res, done)
135+
}
136+
}

0 commit comments

Comments
 (0)