Capstone: CSV sales summary

Station 2.C 40 min Interchange: str_getcsv() & array_count_values() & usort() → slices

End of the Collections line. Every lesson in this unit took one job away from PHP's array and gave it to a Go type. This capstone puts them all back on the same train: a day's ticket machine log goes in, and a summary comes out.

7 sales (1 skipped)
Top stations:
  1. Bank     £18.00
  2. Oval     £18.00
  3. Angel    £8.40
Tickets: return, single, travelcard

The log is a small CSV, with a header and then one sale per line: station, ticket type, and price in pence. Like any real export, it's a little messy. There's a blank line, and one row where somebody typed "two pounds eighty" into the price column.

How it's put together

Like the Unit 1 capstone, there are two packages. sales/sales.go does the work in four functions, and main.go feeds it today's log and lays out the report. The layout is already written, so you only write the four functions. Here's how data flows through them:

flowchart TB
  accTitle: How the sales summary is built
  accDescr: The CSV text goes into sales.Parse, which returns a slice of Sale structs and a count of skipped rows. That slice feeds two functions. sales.ByStation adds up a map of pence per station, which sales.Top turns into a sorted slice of the top three Totals. sales.Tickets returns the distinct ticket types as a sorted slice of strings. The report combines the counts, the top stations and the ticket types.
  log["today's log, CSV text"] --> parse["sales.Parse<br/>[]Sale, skipped"]
  parse --> bs["sales.ByStation<br/>map[string]int"]
  bs --> top["sales.Top<br/>[]Total, sorted"]
  parse --> tk["sales.Tickets<br/>[]string"]
  top --> rep["report"]
  tk --> rep

What you'll use

You'll need From
A slice built with append, and lines[1:] to skip the header 2.2
A map that adds up totals, made before it's written to 2.4
A slice of structs, built from a map 2.5, 2.7
slices.SortFunc, cmp.Compare and cmp.Or for busiest first, ties A to Z 2.6
slices.Sort then slices.Compact to remove duplicates 2.6
strings.Split, strings.TrimSpace and strconv.Atoi 1.5, 1.C

The one function you might not have met is slices.Compact, which removes neighbouring duplicates. That's why Tickets sorts first: sorted, every duplicate sits next to its twin. The Reference panel under the task has its docs, along with strings.Split and strings.TrimSpace.

Tip

Start with the easy wins. ByStation and Tickets are a few lines each. Top is 2.6's leaderboard again. Leave Parse until last: it has the most rules, and the tests list every row it should keep or skip. Run's report stays empty until Parse works, because everything starts from its output, but the tests feed each function its own data, so the others can go green first.

Laravel corner

In Laravel this is a Collection chain: collect($rows)->groupBy('station')->map->sum('pence')->sortDesc()->take(3). The Go version spells each step out as a function with typed inputs and outputs. It's longer, but each piece is small, testable on its own, and impossible to feed the wrong shape of data.

Mind the gap

PHP has str_getcsv and Go has encoding/csv, and neither is used here. A real CSV can have quoted fields with commas inside them ("King's Cross, St Pancras"), and splitting on "," would break those. This log is simple enough for strings.Split. For real files, use encoding/csv, which reads from the readers you'll meet in lesson 7.4.

When it's all green, the Collections line is done. Unit 3 turns structs into something more: methods, interfaces and the Go take on classes.

Summarise a day's ticket machine log. Fill in the four functions in sales/sales.go: their doc comments are the spec, and the tests are grouped by function. main.go already lays out the report, so once Parse works, Run shows the summary of a real log.

Reference
$ go doc strings.Split
func Split(s, sep string) []string
    Split slices s into all substrings separated by sep and returns a slice of
    the substrings between those separators.

$ go doc strings.TrimSpace
func TrimSpace(s string) string
    TrimSpace returns a slice (substring) of the string s, with all leading and
    trailing white space removed, as defined by Unicode.

$ go doc slices.Compact
func Compact[S ~[]E, E comparable](s S) S
    Compact replaces consecutive runs of equal elements with a single copy. This
    is like the uniq command found on Unix. Compact modifies the contents of the
    slice s and returns the modified slice, which may have a smaller length.

You'll also want strconv.Atoi from the Unit 1 capstone, slices.SortFunc with cmp.Compare and cmp.Or from 2.6, and the imports for each package you use.

Ctrl+Enter Running on go1.26…

Run prints whatever main() prints. Submit runs the hidden test suite.