Solution

ID: past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2013/functional-programming-design-and-analysis-of-algorithms/3/b/solution

Normalize each word once by applying toLower to its characters. Maintain an association list whose keys are distinct normalized words and whose values are their frequencies:
import Data.Char (toLower)

type TextWord = String
type Text = [TextWord]
type Frequency = Integer
type Entry = (TextWord, Frequency)

count :: Text -> [Entry]
count = foldl addWord [] . map (map toLower)
  where
    -- Increment the unique matching key, or append a new key with count one.
    addWord :: [Entry] -> TextWord -> [Entry]
    addWord [] w = [(w, 1)]
    addWord ((v, n):rest) w
      | w == v    = (v, n + 1) : rest
      | otherwise = (v, n) : addWord rest w
After any input prefix, each normalized word seen so far occurs exactly once as a key, with its number of occurrences as value. This loop invariant holds initially for the empty association list. A step either increments the unique existing entry or introduces exactly one new entry, so mathematical induction proves
for every word that occurs. In particular, Baa and baa contribute to the same key baa; the three frequency-two keys in the source example are baa, yes and sir.
The association list preserves first-occurrence order, although the specification allows any order here. For input words and distinct normalized words, it performs key comparisons in the worst case. Character normalization costs the total input character count, and a string comparison can inspect multiple characters. Characterwise toLower supplies the intended case-insensitive comparison convention for the example; full Unicode case folding can require a different normalization policy.

New to topics? Read the docs here!