A web server writes one line per request, and the log never ends. A request line has five fields separated by spaces, possibly with extra spaces around the line:
2026-05-01T10:15:22 GET /api/items 200 123ms
(time, method, path, status code, duration in milliseconds). The log also contains blank lines, comment lines starting with #, and damaged lines.
Write three generator functions that can be chained:
clean(lines)yields every line stripped of surrounding whitespace, leaving out blank lines and lines that start with#.parse(lines)turns each cleaned line into a tuple(time, method, path, status, ms)withstatusandmsas integers. A line that does not have exactly five fields, whose status is not an integer, or whose duration is not an integer followed byms, is skipped.slow(records, limit)yields the records whose duration is at leastlimitmilliseconds.
The tests chain them over an endless log, for example first(slow(parse(clean(access_log(1))), 900), 3), so every stage must hand on items one at a time. Helpers available with Run: access_log(seed) (endless), stream(lines) (a list, one line at a time) and first(gen, n).
Examples
Input: first(clean(stream([" a b \n", "\n", "# note\n", "c\n"])), 5)
Output: ["a b", "c"]
Input: first(parse(stream(["t1 GET / 200 15ms", "t2 GET /x 404", "t3 PUT /y 201 7ms"])), 5)
Output: [("t1", "GET", "/", 200, 15), ("t3", "PUT", "/y", 201, 7)]
Input: first(slow(stream([("t1", "GET", "/", 200, 15), ("t2", "GET", "/a", 200, 950)]), 900), 5)
Output: [("t2", "GET", "/a", 200, 950)]
Constraints
- The log may be endless;
firstasks for at most 50 items. - Methods and paths never contain spaces; a comment starts with
#after stripping.
Goals
- Split a stream computation into small generator stages
- Chain generators so that nothing is read until the last stage asks
- Skip malformed lines by trying the conversion and handling the error