1 Counting

Read the tutorial. Build it yourself is the other way to read this part.

just setup

tally must count the words of a text. First we write a function that counts, and try it alone. Then the command reads a text, and prints what the function counts.

The code before this part. The code after it.

1.1 A function that counts

A word counter needs a way to count. A function, count, takes a text and gives a dictionary from each word to how many times it appears. It goes in src/tally/__init__.py, above main. The command does not use it yet.

A patch to src/tally/__init__.py.

In src/tally/__init__.py, count now stands above main. It splits the text at every space, tab and end of line. For each word, it adds 1 to the count so far, and counts.get(word, 0) gives 0 for a word it has not seen.

uv run python -c "from tally import count; print(count('a b a'))"

It prints {'a': 2, 'b': 1}, two of a and one of b. So the function works, though the command still has nothing to count.

What changed

  • src/tally/__init__.py: a dictionary from each word to how many times it appears. 7 lines added. The file.
    • look at counts[word] = counts.get(word, 0) + 1
The diff of src/tally/__init__.py
@@ -1,5 +1,12 @@
 """tally: count the words in a text."""
 
 
+def count(text):
+    counts = {}
+    for word in text.split():
+        counts[word] = counts.get(word, 0) + 1
+    return counts
+
+
 def main() -> None:
     print("tally: nothing to count yet")

This change on GitHub. The code after it.

1.2 The command counts

count works, but the command still prints that it has nothing to count. main must read a text and print the counts. It reads the file that the command names, or the standard input when the command names no file. The module sys gives both the arguments of the command and the standard input.

A patch to src/tally/__init__.py.

In src/tally/__init__.py, import sys now stands under the docstring of the module. The first line of main reads the text, from the file named in sys.argv[1] or from the standard input. The loop prints each word with its count. sorted puts the words in alphabetical order.

echo "a b a" | uv run tally

It prints 2 a and then 1 b. The command now counts.

What changed

  • src/tally/__init__.py: main reads the text and prints the counts. 5 lines added, 1 removed. The file.
    • look at for word, n in sorted(count(text).items()):
The diff of src/tally/__init__.py
@@ -1,5 +1,7 @@
 """tally: count the words in a text."""
 
+import sys
+
 
 def count(text):
     counts = {}
@@ -9,4 +11,6 @@ def count(text):
 
 
 def main() -> None:
-    print("tally: nothing to count yet")
+    text = open(sys.argv[1]).read() if len(sys.argv) > 1 else sys.stdin.read()
+    for word, n in sorted(count(text).items()):
+        print(n, word)

This change on GitHub. The code after it.