Course 2.2Lesson 5 of 5
In this project we build a data pipeline from generators. Each stage is a small generator that takes a stream of values and gives a new stream: one gives the non-empty lines of a text, the next gives the words of each line, the next cleans each word to small letters without punctuation, and the next drops the boring words. The last stage, which is a normal function, counts what arrives. Because every stage is lazy, a value passes through all the stages one at a time, and the whole text is never held in a big list, so the same pipeline can handle a file of any size. We plan the stages and the expected counts by hand, build and test each stage alone, and extend the pipeline with a stage for bigrams and a top-ten report.
Try the code and the simulations beside the lesson.
After this lesson you can
We want the most common words of a text. You solved this with a list and a dictionary in the Essentials course. Now imagine that the text is a file of a million lines, too big to hold in a list. We need a way to handle it one piece at a time.
The idea is a pipeline, like a factory line: the text goes in at one end, and each stage does one job on each piece that passes. The pieces never pile up.
| Stage | In | Out | Job |
|---|---|---|---|
lines | a text | lines | skip the empty lines |
words | lines | words | cut each line into words |
clean | words | words | small letters, no punctuation, drop empty |
without | words | words | drop the boring words |
count | words | a dictionary | count (a normal function: it needs all the words) |
The first four are generators. Each takes a stream and gives a stream, so they can be joined: the output of one is the input of the next.
The text has three lines:
The cat sat on the mat.
The dog barked at the cat!
A cat is not a dog, and a dog is not a cat.Work out by hand. After cleaning and dropping the boring words (the, a, is, not, and, on, at), these words are left: cat, sat, mat, dog, barked, cat, cat, dog, dog, cat. So cat appears 4 times, dog 3 times, and sat, mat and barked once each.
Because the pipeline is lazy, you could replace lines(text) with a generator that reads a file, one line at a time, and the same stages would handle a file of any size, with the memory of one line.
The great gain of small stages is that you can test each one by itself, with a list as the input.
| Stage | Input | Expected |
|---|---|---|
clean | ["The", "CAT!", "...", "dog,"] | ["the", "cat", "dog"] |
without | ["a", "cat", "the"] with boring {"a", "the"} | ["cat"] |
words | ["a b", "c"] | ["a", "b", "c"] |
lines | "a\n\nb\n" | ["a", "b"] |
count | ["a", "b", "a"] | {"a": 2, "b": 1} |
| the whole pipeline | an empty text | {} |
Wrap each generator in list(...) to see its values. A list is a perfectly good stream: a stage does not care whether its input comes from a list, a file or another generator. This is the power of iterables.
Each stage is small and can be tested alone. The pipeline is read from the inside out: evens runs first.
Pick one or two, and do them without looking at a solution. Write the expected answers first.
pairs(stream) gives each pair of neighbouring words, so that you can count common pairs.file_lines(name) that yields the lines of a file one at a time, and use it as the first stage.first(stream, n) gives only the first n values, then stops.yield and using return. The stage gives one value and ends.words before lines.Start with two quick questions. Each New numbers gives you a fresh one, so you can practise as long as you like.
Then the labs and the problems:
first(stream, n).Try each one on your own first. If you are stuck, open a hint. Then check with Show the answer.
without(clean(words(lines(text))), boring) read from the inside out?count return a dictionary, and not yield?Every lesson is open to read. Enrolling also lets you:
Log in to join the discussion.
Loading…
This lesson is free to read. Enroll free to keep your place and take the tests.