Skip to lab content

WEEK 2 · ADVANCED DATA STRUCTURES LAB

Knuth–Morris–Pratt pattern matching

B.Tech CSE & Allied Branches · II Year, I Semester · AR25C173 tested sample runs

THE SYLLABUS TASK

What you need to solve

Implement the Knuth–Morris–Pratt (KMP) string pattern-matching algorithm.
Input format & conventions

Two full lines: text, then a nonempty pattern. Each line accepts at most 1000 bytes, including spaces. Matches are reported at zero-based byte indexes, including overlapping occurrences.

UNDERSTAND THE IDEA

Explanation

KMP remembers how much of the pattern has already matched. Instead of moving the text index backward after a mismatch, it reuses a shorter prefix that is also a suffix of the matched portion.

LPS[j] stores the length of the longest proper prefix of pattern[0…j] that is also its suffix. Proper means the complete substring is excluded.

After a full match, j falls back to lps[j−1], which retains the suffix that may begin an overlapping next occurrence.

TURN THE IDEA INTO STEPS

Step-by-step algorithm

  1. Validate the two lines and reject an empty pattern.
  2. Build LPS using a prefix length and fallback links.
  3. Advance both indexes on a matching byte.
  4. On mismatch, fall back with LPS, or advance the text if no prefix remains.
  5. Report each full match and retain its usable suffix; print No match if none exist.

SEE THE CONTROL FLOW

Flowcharts

Diamonds show decisions; labeled arrows select a branch. Returning arrows repeat work. Helper-function steps group the operations explained above and in the code. These charts focus on the named core operation. On a phone, scroll within the diagram or open the full-size version.

KMP search using LPS

KMP search using LPS

Open full-size flowchart ↗

C17

Complete C program

Download .c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
    int ch; size_t used = 0; int fits = 1;
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;
    do {
        if (ch == 0) fits = 0;
        if (used + 1 < capacity) text[used++] = (char)ch;
        else fits = 0;
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));
    text[used] = '\0';
    return fits;
}
int readInt(int *value) {
    char token[64], *end;
    if (!readToken(token, sizeof token)) return 0;
    errno = 0; long result = strtol(token, &end, 10);
    if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;
    *value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }

#define MAX_TEXT 1000
int readLine(char text[], size_t capacity) {
    size_t used = 0; int ch, pending = EOF, fits = 1, seen = 0;
    while ((ch = getchar()) != EOF && ch != '\n') {
        seen = 1;
        if (ch == 0) fits = 0;
        /* Delay one byte so a final CR is a terminator, not line content. */
        if (pending != EOF) {
            if (used + 1 < capacity) text[used++] = (char)pending;
            else fits = 0;
        }
        pending = ch;
    }
    if (pending != EOF && pending != '\r') {
        if (used + 1 < capacity) text[used++] = (char)pending;
        else fits = 0;
    }
    text[used] = '\0';
    return fits && (ch == '\n' || seen);
}

void buildLPS(const char pattern[], int m, int lps[]) {
    lps[0] = 0;
    for (int i = 1, length = 0; i < m;) {
        if (pattern[i] == pattern[length]) lps[i++] = ++length;
        else if (length) length = lps[length - 1];
        else lps[i++] = 0;
    }
}
void searchKMP(const char text[], const char pattern[]) {
    int n = (int)strlen(text), m = (int)strlen(pattern), lps[MAX_TEXT], found = 0;
    buildLPS(pattern,m,lps);
    printf("LPS:"); for (int k = 0; k < m; ++k) printf(" %d", lps[k]); putchar('\n');
    for (int i = 0, j = 0; i < n;) {
        if (text[i] == pattern[j]) { ++i; ++j; }
        else if (j) j = lps[j - 1];
        else ++i;
        if (j == m) {
            printf("Match at index %d\n",i - m); found = 1; j = lps[j - 1];
        }
    }
    if (!found) puts("No match.");
}
/* Text line, then nonempty pattern line; each at most 1000 bytes. */
int main(void) {
    char text[MAX_TEXT + 1], pattern[MAX_TEXT + 1];
    if (!readLine(text,sizeof text) || !readLine(pattern,sizeof pattern) || !pattern[0]) return invalid();
    searchKMP(text,pattern); return 0;
}

Local compile: gcc -std=c17 kmp.c -o program -lm

Open in C compiler transfers the source. Copy the sample input into the compiler’s input box separately before running.

UNDERSTAND THE CODE

Line-by-line explanation

Follow the exact source above, from input handling through the algorithm and its result. Line numbers count blank lines; related statements are grouped into steps. Start with main to follow execution, then open the functions it calls.

Start with main → · Algorithm and data structure →

Lines 1–5Include input, conversion and validation declarations
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

stdio.h supplies terminal input/output. stdlib.h declares conversion and allocation functions. string.h supplies string and memory utilities. ctype.h supplies isspace, and errno.h supplies conversion error reporting. Headers declare facilities; including them does not execute an operation.

Lines 7–9Define a complete-token reader and initialise its state
/* Read one entire token; reject malformed, oversized and out-of-range input. */
int readToken(char text[], size_t capacity) {
    int ch; size_t used = 0; int fits = 1;

readToken receives a writable buffer and its capacity. ch is int so it can represent EOF as well as input characters. used counts stored characters, and fits starts as a success flag. One slot must remain for the null terminator.

Lines 10–11Skip leading whitespace and recognise end-of-file
    while ((ch = getchar()) != EOF && isspace((unsigned char)ch)) { }
    if (ch == EOF) return 0;

Read until a non-whitespace character appears. The unsigned-char cast supplies an allowed value to isspace. Return 0 if EOF arrives before a token begins.

Lines 12–15Collect a token without silently truncating it
    do {
        if (ch == 0) fits = 0;
        if (used + 1 < capacity) text[used++] = (char)ch;
        else fits = 0;

The do body handles the character already read. An embedded zero byte marks the token invalid. Store a character only while there is room for it and a null terminator; otherwise clear fits. Continue reading even after the buffer fills.

Lines 16–20Consume the full token and terminate the buffer
        ch = getchar();
    } while (ch != EOF && !isspace((unsigned char)ch));
    text[used] = '\0';
    return fits;
}

Read through the next whitespace or EOF, add a null character after the stored prefix, then return fits. A too-long token returns 0 rather than pretending that its stored prefix is the complete token.

Lines 21–23Wrap token input as a checked integer read
int readInt(int *value) {
    char token[64], *end;
    if (!readToken(token, sizeof token)) return 0;

value is an output address. A 64-slot local buffer receives the token; a failed token read returns 0 before attempting conversion.

Lines 24–25Convert base-ten text and check the whole result
    errno = 0; long result = strtol(token, &end, 10);
    if (errno || end == token || *end || result < -1000000 || result > 1000000) return 0;

Reset errno before strtol. &end receives the stopping address. Reject conversion errors, no converted digits, leftover characters, or values outside -1,000,000 to 1,000,000. Thus 12x is not accepted as the integer 12.

Lines 26–28Return the accepted value or a standard error status
    *value = (int)result; return 1;
}
int invalid(void) { puts("Invalid or incomplete input."); return 1; }

Store a validated integer through *value and return 1 from readInt. invalid is a separate helper that prints the error and returns 1 as main’s failure status; the same number has different roles in these two function contracts.

Lines 30–32Prepare a line buffer with a delayed final character
#define MAX_TEXT 1000
int readLine(char text[], size_t capacity) {
    size_t used = 0; int ch, pending = EOF, fits = 1, seen = 0;

MAX_TEXT is 1000. readLine tracks used slots, capacity status and whether any character was seen. pending starts at EOF as a marker that no character is waiting to be stored.

Lines 33–36Read through a newline while rejecting embedded null bytes
    while ((ch = getchar()) != EOF && ch != '\n') {
        seen = 1;
        if (ch == 0) fits = 0;
        /* Delay one byte so a final CR is a terminator, not line content. */

Read characters until newline or EOF and mark that input was seen. A zero byte makes fits false. The comment explains why one byte is delayed: a final carriage return can belong to the line ending rather than the text.

Lines 37–42Store the previous character and hold back the newest
        if (pending != EOF) {
            if (used + 1 < capacity) text[used++] = (char)pending;
            else fits = 0;
        }
        pending = ch;
    }

If a pending byte exists, append it only while buffer space remains; otherwise mark overflow. Then hold the newly read byte in pending. This consumes the complete line even after a capacity problem.

Lines 43–46Append the final byte unless it was a carriage return
    if (pending != EOF && pending != '\r') {
        if (used + 1 < capacity) text[used++] = (char)pending;
        else fits = 0;
    }

At the line end, preserve the pending byte only if it is real content. A final carriage return is omitted, supporting CRLF line endings. The capacity test still reserves a null-terminator slot.

Lines 47–49Terminate the line and report whether a line was present
    text[used] = '\0';
    return fits && (ch == '\n' || seen);
}

Store the null terminator. Success requires fits and either a newline or at least one seen character. A blank newline is a valid empty text line; EOF before any character is not an input line.

Lines 51–54Build the longest-prefix-suffix table
void buildLPS(const char pattern[], int m, int lps[]) {
    lps[0] = 0;
    for (int i = 1, length = 0; i < m;) {
        if (pattern[i] == pattern[length]) lps[i++] = ++length;

lps[0] is zero. For each later position i, length tracks the candidate proper-prefix length. A character match extends that prefix and stores its new length in lps before advancing i.

Lines 55–58Fall back within the pattern without discarding known matches
        else if (length) length = lps[length - 1];
        else lps[i++] = 0;
    }
}

On a mismatch with a nonzero candidate length, replace length with lps[length - 1] and retry the same i. If length is already zero, store zero and advance. A proper prefix never includes the whole current substring.

Lines 59–62Prepare the search and display its prefix table
void searchKMP(const char text[], const char pattern[]) {
    int n = (int)strlen(text), m = (int)strlen(pattern), lps[MAX_TEXT], found = 0;
    buildLPS(pattern,m,lps);
    printf("LPS:"); for (int k = 0; k < m; ++k) printf(" %d", lps[k]); putchar('\n');

Measure text and the nonempty pattern, allocate a bounded lps array, build it, then print its m entries. found begins at zero for the final no-match report.

Lines 63–66Advance text and pattern indexes using LPS on mismatch
    for (int i = 0, j = 0; i < n;) {
        if (text[i] == pattern[j]) { ++i; ++j; }
        else if (j) j = lps[j - 1];
        else ++i;

i is the text index and j the matched-pattern length. Matching characters advance both. A mismatch with j > 0 changes only j via lps, so the same text character is reconsidered. With j == 0, advance the text index.

Lines 67–70Report a complete match and allow overlapping matches
        if (j == m) {
            printf("Match at index %d\n",i - m); found = 1; j = lps[j - 1];
        }
    }

When j equals m, the starting index is i - m. Set found and fall back to lps[j - 1] rather than restarting at zero; a suffix of this match may begin the next overlapping occurrence.

Lines 71–72Report absence only if no occurrence was printed
    if (!found) puts("No match.");
}

The found flag suppresses No match after any successful occurrence. The brace ends searchKMP.

Lines 73–78Read a text line and a nonempty pattern line
/* Text line, then nonempty pattern line; each at most 1000 bytes. */
int main(void) {
    char text[MAX_TEXT + 1], pattern[MAX_TEXT + 1];
    if (!readLine(text,sizeof text) || !readLine(pattern,sizeof pattern) || !pattern[0]) return invalid();
    searchKMP(text,pattern); return 0;
}

main reserves space for 1000 bytes plus a terminator in each buffer. Text may be empty, but pattern[0] must not be null. Call KMP only after both line reads succeed, then return success. Positions count bytes, not Unicode characters.

FOLLOW THE VALUES

Dry run

  1. Pattern AAA has LPS [0,1,2].
  2. Text AAAAA first matches at index 0 after three comparisons.
  3. After that match, j becomes 2, retaining AA for the next comparison.
  4. The next two text bytes complete matches at indexes 1 and 2.

CHECK THE BEHAVIOUR

Sample input & output

These outputs were captured by running the exact C source shown above. Commands use the stated input format; the programs omit repeated menu prompts so the same sample can be pasted and run.

Sample 1

INPUT
ABABDABACDABABCABAB
ABABC
OUTPUT
LPS: 0 0 1 2 0
Match at index 10

Sample 2

INPUT
AAAAA
AAA
OUTPUT
LPS: 0 1 2
Match at index 0
Match at index 1
Match at index 2

Sample 3

INPUT
hello world
xyz
OUTPUT
LPS: 0 0 0
No match.

Common mistakes

  • Do not initialize lps[0] for an empty pattern; reject it first.
  • Do not reset j to zero after every match if overlaps must be found.
  • LPS values are lengths, not matching text indexes.

WHY THIS GROWTH RATE?

Time and space complexity

O(N + M) preprocessing and search; O(M) auxiliary space.

LPS construction advances its position or shortens the current prefix; these advances and fallbacks together are linear in M.

During search the text index never moves backward. Pattern fallbacks do not cause the text to be rescanned, producing O(N) search work after O(M) preprocessing.

The conceptual LPS storage has M integers. This bounded example reserves 1000 entries. Input buffers additionally hold N and M bytes; output costs are proportional to the number of matches.

Big-O describes growth in the stated input quantity. The educational limits keep examples safe; complexity discussions state which bounds depend on a generalized input size. Arithmetic on the accepted native numeric types is treated as constant-time.

PREPARE FOR YOUR LAB VIVA

Related viva questions & answers

6 focused questions

Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.

What does proper prefix mean in the LPS definition?

It is a prefix shorter than the entire substring being considered. For AAA, the longest proper prefix that is also a suffix is AA, with length 2.

Why does an LPS mismatch sometimes keep the same pattern-building index?

A shorter candidate prefix may still match the current character. The function follows a fallback first, so it can test that character without prematurely recording zero.

Why does KMP avoid moving the text index backward?

The matched prefix and LPS information already identify reusable comparisons. Only the pattern position needs adjustment, so previously consumed text need not be read again.

How does this KMP code find overlapping matches?

After a complete match, j becomes the last LPS value rather than zero. That suffix can act as the prefix of the next match, as in AAA inside AAAAA.

Why is an empty pattern explicitly rejected here?

The LPS routine assumes at least one pattern character. Rejecting emptiness gives the program a clear convention and prevents accessing entries for a zero-length pattern.

What is the LPS array for the manual’s pattern ABABC?

It is 0, 0, 1, 2, 0. The A and AB prefixes recur as suffixes before the final C breaks that prefix-suffix relationship.