5
\$\begingroup\$

I am learning Go by working through programming puzzles. There is shared boilerplate between puzzles that I want to factor out into into some kind of helper structure:

  1. Read the input (always the same, may change later, but in the same manner for all puzzles, eg. moving from reading files to HTTP)
  2. Prepare the input (may differ, but often identical)
  3. Process the input to arrive at a solution (always different)

I have come up with the solution shown below.

My specific questions are:

  1. Is there an idiomatic way to provide a "default method" for PuzzleSolver.Prepare() that can be overwritten, if required? I suspect the "correct" thing to do may be to write a function with the default implementation and wire each method to call that instead, but I'm not sure.
  2. I am unsure about Solve(Puzzle) vs Puzzle.Solve(). Are there common reasons to prefer one over the other?
package main

import "fmt"

type PuzzleSolver interface {
    Prepare(*PuzzleInput)
    Do() string
}

type Puzzle struct {
    input  *PuzzleInput
    solver PuzzleSolver
}

func (p *Puzzle) Solve() string {
    fmt.Println("==== Solving", p.input.ID, "====")
    p.solver.Prepare(p.input)
    ans := p.solver.Do()
    return ans
}

type PuzzleInput struct {
    ID    int
    input string
}

func (pi *PuzzleInput) Read() {
    fmt.Println("Reading Puzzle with ID", pi.ID, "-- Reading is always the same.")
    pi.input = fmt.Sprintf("Input for Puzle %d", pi.ID)
}

func NewPuzzle(ID int, solver PuzzleSolver) *Puzzle {
    input := &PuzzleInput{ID: ID}
    return &Puzzle{input: input, solver: solver}
}

type SolverOne struct{}

func (p *SolverOne) Prepare(pi *PuzzleInput) {
    pi.Read()
    fmt.Println("Preparing input for the first puzzle")
}

func (p *SolverOne) Do() string {
    ans := fmt.Sprintf("The answer to the first puzzle!")
    return ans
}

type SolverTwo struct{}

func (p *SolverTwo) Prepare(pi *PuzzleInput) {
    pi.Read()
    fmt.Println("Preparing input for the second puzzle. This behaviour is actually shared with SolverOne. Can I avoid implementing SolverTwo.Prepare() explicitly?")
}

func (p *SolverTwo) Do() string {
    ans := fmt.Sprintf("The answer to the second puzzle!")
    return ans
}

type SolverThree struct {
    a, b int
}

func (p *SolverThree) Prepare(pi *PuzzleInput) {
    pi.Read()
    // process input ...
    p.a = 10
    p.b = 20
    fmt.Println("Preparing input for the third puzzle. This performs an action distinct from SolverOne and SolverTwo.")
}

func (p *SolverThree) Do() string {
    ans := fmt.Sprintf("The answer to the third puzzle is %d!", p.a+p.b)
    return ans
}

func Solve(ps *Puzzle) string {
    return ps.Solve()
}

func main() {
    p1 := NewPuzzle(1, &SolverOne{})
    fmt.Println(Solve(p1))
    p2 := NewPuzzle(2, &SolverTwo{})
    fmt.Println(Solve(p2))
    p3 := NewPuzzle(3, &SolverThree{})
    fmt.Println(Solve(p3))
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Alternate design approach: minimal subset of language as needed for the problem. This immensely helps readability.

My own insight is that mostly puzzles involve input/output in other types than string (e.g. graph puzzles, tree puzzles, ...).

func Act[Input, Output any](prepareFn func() Input, solveFn func(input Input) Output) Output {
    input := prepareFn()
    return solveFn(input)
}

func Read() string {
    return "x"
}

func funkyPrepare() string {
    return Read() + "but more funky"
}

func solve1(input string) string {
    ...
}

func main() {
    Act(Read, solve1)
    Act(Read, solve2)
    Act(funkyPrepare, solve3)
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.