Different solution for Gophercise 1

This commit is contained in:
2025-05-31 13:14:20 -04:00
parent b22ab92d79
commit 1da6e395bf
56 changed files with 3374 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,120 @@
package main
import (
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"time"
quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
const (
// FileFlag is used to set a file for the questions
FileFlag = "file"
// FileFlagValue is the value used when no FileFlag is provided
FileFlagValue = "problems.csv"
// FileFlagUsage is the help string for the FileFlag
FileFlagUsage = "Questions file"
// TimerFlag is used for setting a timer for the quiz
TimerFlag = "timer"
// TimerFlagValue is the value used when no TimerFlag is provided
TimerFlagValue = 30
// TimerFlagUsage is the help string for the TimerFlag
TimerFlagUsage = "Amount of seconds the quiz will allow"
)
// Flagger configures the flags used
type Flagger interface {
StringVar(p *string, name, value, usage string)
IntVar(p *int, name string, value int, usage string)
}
type quizFlagger struct{}
func (q *quizFlagger) StringVar(p *string, name, value, usage string) {
flag.StringVar(p, name, value, usage)
}
func (q *quizFlagger) IntVar(p *int, name string, value int, usage string) {
flag.IntVar(p, name, value, usage)
}
// Timer is used to start a timer
type Timer interface {
NewTimer(d time.Duration) *time.Timer
}
type quizTimer struct{}
func (q quizTimer) NewTimer(d time.Duration) *time.Timer {
return time.NewTimer(d)
}
// ReadCSV parses the CSV file into a Problem struct
func ReadCSV(reader io.Reader) quiz.Quiz {
csvReader := csv.NewReader(reader)
problems := []problem.Problem{}
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
log.Fatalln("Error reading CSV:", err)
}
problems = append(problems, problem.New(record))
}
return quiz.New(problems)
}
// TimerSeconds is the amount of time allowed for the quiz
var TimerSeconds int
var file string
// ConfigFlags sets all the flags used by the application
func ConfigFlags(f Flagger) {
f.StringVar(&file, FileFlag, FileFlagValue, FileFlagUsage)
f.IntVar(&TimerSeconds, TimerFlag, TimerFlagValue, TimerFlagUsage)
}
// StartTimer begins a timer once the user provides input
func StartTimer(w io.Writer, r io.Reader, timer Timer) *time.Timer {
fmt.Fprint(w, "Ready to start?")
fmt.Fscanln(r)
return timer.NewTimer(time.Second * time.Duration(TimerSeconds))
}
func init() {
flagger := &quizFlagger{}
ConfigFlags(flagger)
flag.Parse()
}
func main() {
file, err := os.Open(file)
if err != nil {
log.Fatalln("Could not open file", err)
}
quiz := ReadCSV(file)
timer := StartTimer(os.Stdout, os.Stdin, quizTimer{})
go func() {
<-timer.C
fmt.Println("")
quiz.PrintResults(os.Stdout)
os.Exit(0)
}()
quiz.Run(os.Stdout, os.Stdin)
}

View File

@@ -0,0 +1,40 @@
package quiz
import (
"fmt"
"io"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
// Quiz represents the quiz to be given to the user
type Quiz struct {
problems []problem.Problem
rightAnswers int
}
// Run runs the quiz for all the problems keeping track of correct answers
func (q *Quiz) Run(w io.Writer, r io.Reader) {
for _, problem := range q.problems {
problem.AskQuestion(w)
correct := problem.CheckAnswer(r)
if correct {
q.rightAnswers++
}
}
q.PrintResults(w)
}
// PrintResults outputs the results of the quiz
func (q Quiz) PrintResults(w io.Writer) {
fmt.Fprintf(w, "You got %d questions right!\n", q.rightAnswers)
}
// New creates a new quiz from the supplied slice of problems
func New(problems []problem.Problem) Quiz {
return Quiz{
problems: problems,
rightAnswers: 0,
}
}

View File

@@ -0,0 +1,66 @@
package quiz
import (
"bytes"
"io"
"reflect"
"strings"
"testing"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
func TestNew(t *testing.T) {
problems := sampleProblems()
want := Quiz{problems: problems, rightAnswers: 0}
got := New(problems)
if !reflect.DeepEqual(want, got) {
t.Errorf("expeted to create quiz %v got %v", want, got)
}
}
func TestRun(t *testing.T) {
t.Run("it runs the quiz", func(t *testing.T) {
buffer := &bytes.Buffer{}
quiz := createQuiz()
runQuiz(buffer, &quiz)
expectedResults := 2
results := quiz.rightAnswers
if expectedResults != results {
t.Errorf("expected right answers of %v, got %v",
expectedResults, results)
}
expectedOutput := "7+3: 1+1: You got 2 questions right!\n"
if buffer.String() != expectedOutput {
t.Errorf("expected full output %v, got %v",
expectedOutput, buffer)
}
})
}
func sampleProblems() []problem.Problem {
record1 := []string{"7+3", "10"}
record2 := []string{"1+1", "2"}
return []problem.Problem{
problem.New(record1),
problem.New(record2),
}
}
func createQuiz() Quiz {
problems := sampleProblems()
return New(problems)
}
func runQuiz(buffer io.Writer, quiz *Quiz) {
answers := strings.NewReader("10\n2\n")
quiz.Run(buffer, answers)
}

View File

@@ -0,0 +1,49 @@
package problem
import (
"fmt"
"io"
"log"
"strings"
)
// Problem represents a single question answer pair
type Problem struct {
question string
answer string
}
// CheckAnswer checks the answer against the provided input
func (p Problem) CheckAnswer(r io.Reader) bool {
answer := readAnswer(r)
if answer != p.answer {
return false
}
return true
}
func readAnswer(r io.Reader) (answer string) {
_, err := fmt.Fscanln(r, &answer)
if err != nil {
log.Fatalln("Error reading in answer", err)
}
return strings.TrimSpace(answer)
}
// AskQuestion prints out the question
func (p Problem) AskQuestion(w io.Writer) {
_, err := fmt.Fprintf(w, "%s: ", p.question)
if err != nil {
log.Fatalln("Could not ask the question", err)
}
}
// New creates a Problem from a provided CSV record
func New(record []string) Problem {
return Problem{
question: record[0],
answer: record[1],
}
}

View File

@@ -0,0 +1,68 @@
package problem
import (
"bytes"
"testing"
)
func TestNew(t *testing.T) {
record := []string{"question", "answer"}
want := Problem{"question", "answer"}
got := New(record)
if got != want {
t.Errorf("expected to create problem %v got %v", want, got)
}
}
func TestCheckAnswer(t *testing.T) {
problem := createProblem()
t.Run("it checks the correct answer", func(t *testing.T) {
answer := getAnswer(problem, "10\n")
checkAnswer(t, answer, true)
})
t.Run("it checks incorrect answer", func(t *testing.T) {
answer := getAnswer(problem, "2\n")
checkAnswer(t, answer, false)
})
}
func TestAskQuestion(t *testing.T) {
problem := createProblem()
t.Run("it asks the question", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)
problem.AskQuestion(buffer)
want := "7+3: "
got := buffer.String()
if want != got {
t.Errorf("Expected question %s, got %s", want, got)
}
})
}
func createProblem() Problem {
record := []string{"7+3", "10"}
return New(record)
}
func getAnswer(problem Problem, input string) bool {
r := bytes.NewBufferString(input)
return problem.CheckAnswer(r)
}
func checkAnswer(t *testing.T, got, want bool) {
if want != got {
t.Errorf("Expected to return %v got %v", want, got)
}
}

View File

@@ -0,0 +1,12 @@
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7
1 5+5 10
2 1+1 2
3 8+3 11
4 1+2 3
5 8+6 14
6 3+1 4
7 1+4 5
8 5+1 6
9 2+3 5
10 3+3 6
11 2+4 6
12 5+2 7

View File

@@ -0,0 +1,136 @@
package main
import (
"bytes"
"reflect"
"testing"
"time"
quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)
type flaggerMock struct {
stringVarCalls int
intVarCalls int
varNames []string
varUsages []string
varStringValues []string
varIntValues []int
}
func (f *flaggerMock) StringVar(p *string, name, value, usage string) {
f.stringVarCalls++
f.varNames = append(f.varNames, name)
f.varStringValues = append(f.varStringValues, value)
f.varUsages = append(f.varUsages, usage)
}
func (f *flaggerMock) IntVar(p *int, name string, value int, usage string) {
f.intVarCalls++
f.varNames = append(f.varNames, name)
f.varIntValues = append(f.varIntValues, value)
f.varUsages = append(f.varUsages, usage)
}
type timerMock struct {
duration int
}
func (t *timerMock) NewTimer(d time.Duration) *time.Timer {
t.duration = int(d.Seconds())
return time.NewTimer(1 * time.Millisecond)
}
func TestReadCSV(t *testing.T) {
input := "7+3,10\n1+1,2"
reader := bytes.NewBufferString(input)
record1 := []string{"7+3", "10"}
record2 := []string{"1+1", "2"}
problems := []problem.Problem{
problem.New(record1),
problem.New(record2),
}
want := quiz.New(problems)
got := ReadCSV(reader)
if !reflect.DeepEqual(want, got) {
t.Errorf("it should read in %v got %v", want, got)
}
}
func TestConfigFlags(t *testing.T) {
flagger := &flaggerMock{}
ConfigFlags(flagger)
assertStringCalls(t, flagger)
assertIntCalls(t, flagger)
assertFlags(t, flagger)
}
func TestStartTimer(t *testing.T) {
timer := &timerMock{}
w := &bytes.Buffer{}
r := bytes.NewBufferString("\n")
TimerSeconds := 30
StartTimer(w, r, timer)
if timer.duration != TimerSeconds {
t.Errorf("it should set timer for %d seconds, set for %d",
TimerSeconds, timer.duration)
}
if w.String() != "Ready to start?" {
t.Errorf("it should ask user if the user is ready, got %s", w.String())
}
}
func assertStringCalls(t *testing.T, flagger *flaggerMock) {
t.Helper()
if flagger.stringVarCalls != 1 {
t.Errorf("it should call StringVar %d times, called %d",
1, flagger.stringVarCalls)
}
}
func assertIntCalls(t *testing.T, flagger *flaggerMock) {
t.Helper()
if flagger.intVarCalls != 1 {
t.Errorf("it should call IntVar %d times, called %d",
1, flagger.intVarCalls)
}
}
func assertFlags(t *testing.T, flagger *flaggerMock) {
t.Helper()
expectedNames := []string{FileFlag, TimerFlag}
expectedUsages := []string{FileFlagUsage, TimerFlagUsage}
expectedStringValues := []string{FileFlagValue}
expectedIntValues := []int{TimerFlagValue}
if !reflect.DeepEqual(expectedNames, flagger.varNames) {
t.Errorf("it should setup flag names to be %v, got %v",
expectedNames, flagger.varNames)
}
if !reflect.DeepEqual(expectedUsages, flagger.varUsages) {
t.Errorf("it should setup flag usages to be %v, got %v",
expectedUsages, flagger.varUsages)
}
if !reflect.DeepEqual(expectedStringValues, flagger.varStringValues) {
t.Errorf("it should setup string values to be %v, got %v",
expectedStringValues, flagger.varStringValues)
}
if !reflect.DeepEqual(expectedIntValues, flagger.varIntValues) {
t.Errorf("it should setup int values to be %v, got %v",
expectedIntValues, flagger.varIntValues)
}
}