ExerciseDifficulty: Intermediate3 min

Practice: Counting Words in a Short Review

Count each word's appearances, then list them in the order they first appeared.

Counting how often each word appears in a text practises the three choices a hash map asks for: what the key is, what the value is, and whether the map keeps the order its keys arrived in. This card covers that count as an exercise, with an answer to check yours against.

A hotel review reads: the room was clean and the bed was soft but the room was small.

  1. Say what the map's keys and values are, and what counting one word costs.
  2. Give each word's count, listed in the order the words first appear.
  3. Name the built-in map that gives that order with no extra work in Python, in Java and in TypeScript, and say what Java's HashMap would print instead.
An answer: a map from word to count, the nine counts in order, and the map to use in each language

The keys are the words and the values are their counts. For each word the program looks it up, adds one to the count it finds or starts from 1, and stores the result: one lookup and one insert, each O(1) expected, so a text of n words takes O(n) expected. Python's dict and TypeScript's Map keep keys in the order they were first inserted, and in Java a LinkedHashMap does. Each listing was run, and the highlighted lines update the count.

count_words.pypy
review = "the room was clean and the bed was soft but the room was small"

counts: dict[str, int] = {}
for word in review.split():
    counts[word] = counts.get(word, 0) + 1

print(" ".join(f"{word}={count}" for word, count in counts.items()))
OutputRan with python:3.14.8-slim-trixie (Python 3.14.8)
the=3 room=2 was=3 clean=1 and=1 bed=1 soft=1 but=1 small=1

Java's merge stores 1 for a word not yet counted and otherwise adds 1 to its count. The listing fills a HashMap and a LinkedHashMap the same way, to compare their order.

CountWords.javajava
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class CountWords {
    static String describe(Map<String, Integer> counts) {
        return counts.entrySet().stream()
            .map(entry -> entry.getKey() + "=" + entry.getValue())
            .collect(Collectors.joining(" "));
    }

    public static void main(String[] args) {
        String review = "the room was clean and the bed was soft but the room was small";

        Map<String, Integer> hashed = new HashMap<>();
        Map<String, Integer> linked = new LinkedHashMap<>();
        for (String word : review.split(" ")) {
            hashed.merge(word, 1, Integer::sum);
            linked.merge(word, 1, Integer::sum);
        }

        System.out.println("HashMap: " + describe(hashed));
        System.out.println("LinkedHashMap: " + describe(linked));
    }
}
OutputRan with eclipse-temurin:25.0.4.1_1-jdk-noble (Java 25.0.4.1)
HashMap: small=1 but=1 bed=1 the=3 and=1 was=3 clean=1 room=2 soft=1
LinkedHashMap: the=3 room=2 was=3 clean=1 and=1 bed=1 soft=1 but=1 small=1

In TypeScript, get returns undefined for a word not yet counted, and ?? 0 turns that into 0 before the 1 is added.

count_words.tsts
const review = "the room was clean and the bed was soft but the room was small";

const counts = new Map<string, number>();
for (const word of review.split(" ")) {
  counts.set(word, (counts.get(word) ?? 0) + 1);
}

console.log([...counts].map(([word, count]) => `${word}=${count}`).join(" "));
OutputRan with node:24.22.0-trixie-slim (Node 24.22.0), TypeScript 5.9.3
the=3 room=2 was=3 clean=1 and=1 bed=1 soft=1 but=1 small=1

The counts, in order of first appearance, are the=3, room=2, was=3, clean=1, and=1, bed=1, soft=1, but=1 and small=1. A word seen again updates its value and keeps its place, so room stays second. Java's HashMap held the same counts but listed them in its bucket order, starting with small, which is neither the order of the text nor alphabetical.