# exklamationmark's solution

## to Tournament in the Go Track

Published at Aug 24 2018 · 0 comments
Instructions
Test suite
Solution

Tally the results of a small football competition.

Based on an input file containing which team played against which and what the outcome was, create a file with a table like this:

``````Team                           | MP |  W |  D |  L |  P
Devastating Donkeys            |  3 |  2 |  1 |  0 |  7
Allegoric Alaskans             |  3 |  2 |  0 |  1 |  6
Blithering Badgers             |  3 |  1 |  0 |  2 |  3
Courageous Californians        |  3 |  0 |  1 |  2 |  1
``````

What do those abbreviations mean?

• MP: Matches Played
• W: Matches Won
• D: Matches Drawn (Tied)
• L: Matches Lost
• P: Points

A win earns a team 3 points. A draw earns 1. A loss earns 0.

The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically.

Input

``````Allegoric Alaskans;Blithering Badgers;win
Devastating Donkeys;Courageous Californians;draw
``````

The result of the match refers to the first team listed. So this line

``````Allegoric Alaskans;Blithering Badgers;win
``````

This line:

``````Courageous Californians;Blithering Badgers;loss
``````

Means that the Blithering Badgers beat the Courageous Californians.

And this line:

``````Devastating Donkeys;Courageous Californians;draw
``````

Means that the Devastating Donkeys and Courageous Californians tied.

## Running the tests

To run the tests run the command `go test` from within the exercise directory.

If the test suite contains benchmarks, you can run these with the `--bench` and `--benchmem` flags:

``````go test -v --bench . --benchmem
``````

Keep in mind that each reviewer will run benchmarks on a different machine, with different specs, so the results from these benchmark tests may vary.

## Further information

For more detailed information about the Go track, including how to get help if you're having trouble, please visit the exercism.io Go language page.

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### tournament_test.go

``````package tournament

import (
"bytes"
"io"
"strings"
"testing"
)

// Define a function Tally(io.Reader, io.Writer) error.
//
// Note that unlike other tracks the Go version of the tally function
// should not ignore errors. It's not idiomatic Go to ignore errors.

var _ func(io.Reader, io.Writer) error = Tally

// These test what testers call the happy path, where there's no error.
var happyTestCases = []struct {
description string
input       string
expected    string
}{
{
description: "good",
input: `
Devastating Donkeys;Courageous Californians;draw
`,
expected: `
Team                           | MP |  W |  D |  L |  P
Devastating Donkeys            |  3 |  2 |  1 |  0 |  7
Allegoric Alaskians            |  3 |  2 |  0 |  1 |  6
Blithering Badgers             |  3 |  1 |  0 |  2 |  3
Courageous Californians        |  3 |  0 |  1 |  2 |  1
`[1:], // [1:] = strip initial readability newline
},
{
input: `

# Catastrophic Loss of the Californians

Devastating Donkeys;Courageous Californians;draw

`,
expected: `
Team                           | MP |  W |  D |  L |  P
Devastating Donkeys            |  3 |  2 |  1 |  0 |  7
Allegoric Alaskians            |  3 |  2 |  0 |  1 |  6
Blithering Badgers             |  3 |  1 |  0 |  2 |  3
Courageous Californians        |  3 |  0 |  1 |  2 |  1
`[1:],
},
{
// A complete competition has all teams play eachother once or twice.
description: "incomplete competition",
input: `
`,
expected: `
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskians            |  3 |  2 |  0 |  1 |  6
Blithering Badgers             |  2 |  1 |  0 |  1 |  3
Devastating Donkeys            |  1 |  1 |  0 |  0 |  3
Courageous Californians        |  2 |  0 |  0 |  2 |  0
`[1:],
},
{
description: "tie for first and last place",
input: `
Courageous Californians;Devastating Donkeys;win
`,
expected: `
Team                           | MP |  W |  D |  L |  P
Allegoric Alaskians            |  3 |  2 |  1 |  0 |  7
Courageous Californians        |  3 |  2 |  1 |  0 |  7
Blithering Badgers             |  3 |  0 |  1 |  2 |  1
Devastating Donkeys            |  3 |  0 |  1 |  2 |  1
`[1:],
},
}

var errorTestCases = []string{
"Bla;Bla;Bla",
"Devastating Donkeys_Courageous Californians;draw",
"Devastating Donkeys@Courageous Californians;draw",
}

func TestTallyHappy(t *testing.T) {
for _, tt := range happyTestCases {
var buffer bytes.Buffer
actual := buffer.String()
// We don't expect errors for any of the test cases
if err != nil {
t.Fatalf("Tally for input named %q returned error %q. Error not expected.",
tt.description, err)
}
if actual != tt.expected {
t.Fatalf("Tally for input named %q was expected to return...\n%s\n...but returned...\n%s",
tt.description, tt.expected, actual)
}
}
}

func TestTallyError(t *testing.T) {
for _, s := range errorTestCases {
var buffer bytes.Buffer
if err == nil {
t.Fatalf("Tally for input %q should have failed but didn't.", s)
}
var _ error = err
}
}

func BenchmarkTally(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, tt := range happyTestCases {
var buffer bytes.Buffer
}
for _, s := range errorTestCases {
var buffer bytes.Buffer
}
}
}``````
``````package tournament

import (
"bufio"
"fmt"
"io"
"sort"
"strings"
)

// teamStats stores the stats of each team.
type teamStats struct {
played, won, drawn, lost, points int
}

// Tally parses the score file && prints the aggregated result.
func Tally(rr io.Reader, ww io.Writer) error {
stats := make(map[string]*teamStats)

for {
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("cannot read line; err= %v", err)
}
if isEmpty := len(l) < 1; isEmpty {
continue
}
if isComment := l[0] == '#'; isComment {
continue
}

if err := updateStatsWithResult(stats, string(l)); err != nil {
return err
}
}

teams := byDescPointAndAscName(stats)

header := "Team                           | MP |  W |  D |  L |  P\n"
tmpl := "%-30s | %2d | %2d | %2d | %2d | %2d\n"
w := bufio.NewWriter(ww)
for _, t := range teams {
w.WriteString(fmt.Sprintf(tmpl, t, stats[t].played, stats[t].won, stats[t].drawn, stats[t].lost, stats[t].points))
}
w.Flush()

return nil
}

// updateStatsWithResult parses a score line and update the stats map.
func updateStatsWithResult(stats map[string]*teamStats, line string) error {
parts := strings.FieldsFunc(line, func(rn rune) bool {
return rn == ';'
})
if len(parts) != 3 {
return fmt.Errorf("line %q does not have 3 fields", line)
}

team0, team1, res := parts[0], parts[1], parts[2]
if _, exist := stats[team0]; !exist {
stats[team0] = &teamStats{}
}
if _, exist := stats[team1]; !exist {
stats[team1] = &teamStats{}
}
switch res {
default:
return fmt.Errorf("%q is not a valid result", res)
case "win":
stats[team0].won++
stats[team1].lost++
stats[team0].points += 3
case "draw":
stats[team0].drawn++
stats[team1].drawn++
stats[team0].points += 1
stats[team1].points += 1
case "loss":
stats[team0].lost++
stats[team1].won++
stats[team1].points += 3
}
stats[team0].played++
stats[team1].played++

return nil
}

// byDescPointAndAscName returns the list of teams, sorted by descending points && ascending name.
func byDescPointAndAscName(stats map[string]*teamStats) []string {
teams := make([]string, 0, len(stats))
for k, _ := range stats {
teams = append(teams, k)
}

sort.Slice(teams, func(i, j int) bool {
switch {
default:
return false
case stats[teams[i]].points > stats[teams[j]].points:
return true
case stats[teams[i]].points == stats[teams[j]].points && teams[i] < teams[j]:
return true
}

})

return teams
}``````