// Exercise 04 — Log Crunching: Top-N Endpoints // // ASSIGNMENT // ---------- // Log lines look like: // // 2026-08-28T10:00:01Z GET /api/users 200 123ms // // Implement TopN(r, n) so that it: // // 1. Streams the input (must handle logs far larger than memory — no // io.ReadAll). // 2. Counts requests per endpoint (3rd whitespace-separated field). // 3. Skips malformed lines (fewer than 3 fields) without failing. // 4. Handles lines longer than 64KB (hint: bufio.Scanner's default limit). // 5. Returns the n most frequent endpoints, most frequent first. // Ties: order among equal counts is unspecified. // // Run the tests: go test -race ./04-log-topn/ package main import ( "bufio" "cmp" "fmt" "io" "slices" "strings" ) type KV struct { Endpoint string Count int } // TopN returns the n most frequent endpoints in the log stream. // TODO: implement me. func TopN(r io.Reader, n int) ([]KV, error) { // TODO: // - bufio.Scanner with an enlarged buffer // - map[string]int for counting // - copy map into []KV and sort by count desc (maps can't be sorted) // - truncate to n m := make(map[string]int) sc := bufio.NewScanner(r) sc.Buffer(make([]byte, 0, 64*1024), 1024*1024) for sc.Scan() { //fmt.Println(sc.Text()) fields := strings.Fields(sc.Text()) //fmt.Printf("len: %v, fields: %v\n", len(fields), fields) if len(fields) < 3 { continue } m[fields[2]]++ } // copy over map into list of KV l := make([]KV, len(m)) i := 0 for k, v := range m { // println(i, k, v, len(l), len(m)) l[i] = KV{k, v} i++ } // sort by count desc slices.SortFunc(l, func(a, b KV) int { return cmp.Compare(b.Count, a.Count) }) if len(l) > n { l = l[:n] } return l, nil } func main() { logs := `2026-08-28T10:00:01Z GET /api/users 200 12ms 2026-08-28T10:00:02Z GET /api/orders 200 40ms 2026-08-28T10:00:03Z GET /api/users 500 3ms garbage line 2026-08-28T10:00:04Z POST /api/users 201 88ms` top, err := TopN(strings.NewReader(logs), 2) fmt.Println(top, err) }