Abulafia: a Markov-Chain text generator

A small C program that reads a .txt file, builds a Markov chain from it, and uses that chain to answer questions typed interactively by the user with generated, plausible-sounding sentences.

What it does

You give Abulafia a text file (a book, a collection of articles, any reasonably-sized body of text). It learns which words tend to follow which others in that text. Then, in a chat-like loop, you type a question and Abulafia replies with a sentence generated by walking through the learned word transitions, loosely "anchored" to a word from your question.

The output is not real natural-language understanding - it's a statistical mimicry of the input text's style. With a small corpus, answers will look very close to verbatim excerpts (few alternatives exist at each step); with a large, varied corpus, the generated sentences become much more novel.

How it works

flowchart TD
    A[Start: ./abulafia corpus.txt] --> B[Load and tokenize corpus.txt]
    B --> C["Build the chain: for every word pair w1,w2,<br/>record the words seen right after it"]
    C --> D["Prompt: You: ..."]
    D --> E{User input}
    E -->|"exit / quit / EOF"| Z[Exit]
    E -->|question| F["pick_seed(): look for a chain pair<br/>starting with a word from the question"]
    F --> G{Match found?}
    G -->|yes| H["Seed = matching pair (w1, w2)"]
    G -->|no| I["Seed = random pair (w1, w2)"]
    H --> J[Print w1 w2]
    I --> J
    J --> K["Look up next words for (w1, w2)"]
    K --> L{Any known next word?}
    L -->|no| P[Stop: dead end]
    L -->|yes| M[Pick one at random,<br/>weighted by frequency]
    M --> N[Print it]
    N --> O{Sentence-ending punctuation<br/>and min length reached?<br/>or max length reached?}
    O -->|yes| P
    O -->|no| Q["Slide window: (w1, w2) -> (w2, next word)"]
    Q --> K
    P --> D

Building

Requires a C compiler (gcc by default) and make.

make

This produces an abulafia executable. To remove it:

make clean

Usage

./abulafia corpus.txt

or, via the Makefile (uses corpus.txt by default, override with FILE=):

make run FILE=corpus.txt

Once started, the program prints how many words and word-pairs it learned, then prompts for input:

Markov chain ready (1532 words, 1140 unique pairs).
Ask a question (type 'exit' to quit):

You: where does the cat live?
> the black cat sleeps on the roof of the house near the garden.

You: exit

Type exit or quit (or send EOF, e.g. Ctrl-D) to leave.

Test corpus

moby.txt is the full text of Moby-Dick; or, The Whale by Herman Melville, downloaded from Project Gutenberg (public domain) and kept in the repo as a ready-to-use, sufficiently large corpus for trying out and demoing the chain (~216k words, ~1.2 MB). A small input produces answers that closely echo the source almost verbatim, since few alternative continuations exist at each step; a corpus this size gives the chain enough alternatives per word pair to produce noticeably more varied, novel-sounding sentences.

Try it with:

make run FILE=moby.txt

Implementation details

The whole program lives in a single file, abulafia.c.

TODO / possible improvements