THE SYLLABUS TASK
What you need to solve
Implement the Boyer–Moore string pattern-matching algorithm.
Two full lines: text, then nonempty pattern; at most 1000 bytes per line. Zero-based byte indexes. This is the manual’s simplified bad-character variant, without the good-suffix rule.
UNDERSTAND THE IDEA
Explanation
The pattern is compared from its rightmost byte toward its leftmost byte. A mismatch can shift the alignment farther than one position.
A table stores the last occurrence of each byte in the pattern, or −1 if absent. A mismatch at pattern index j suggests a shift of j − last[mismatched byte], but the shift must be at least one.
This page deliberately teaches the bad-character rule from the manual. Full Boyer–Moore variants can add good-suffix preprocessing; their bounds must not be silently attributed to this simpler version.
TURN THE IDEA INTO STEPS
Step-by-step algorithm
- Initialize all last-occurrence entries to −1 and fill them from the pattern.
- Align the pattern at text index zero.
- Compare right to left until a mismatch or complete match.
- On mismatch, shift by max(1, j−last[byte]).
- On a full match, report the start and use the following text byte, when present, to choose the next alignment.
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.
Bad-character alignment and shift
#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);
}
/* Boyer-Moore's bad-character rule only; no good-suffix preprocessing. */
void searchBM(const char text[], const char pattern[]) {
int last[256], n = (int)strlen(text), m = (int)strlen(pattern), found = 0;
for (int c = 0; c < 256; ++c) last[c] = -1;
for (int j = 0; j < m; ++j) last[(unsigned char)pattern[j]] = j;
for (int start = 0; start <= n - m;) {
int j = m - 1;
while (j >= 0 && pattern[j] == text[start + j]) --j;
if (j < 0) {
printf("Match at index %d\n",start); found = 1;
start += start + m < n ? m - last[(unsigned char)text[start + m]] : 1;
} else {
int shift = j - last[(unsigned char)text[start + j]];
start += shift > 0 ? shift : 1;
}
}
if (!found) puts("No match.");
}
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();
searchBM(text,pattern); return 0;
}
Local compile: gcc -std=c17 boyer-moore.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–53Specify the implemented Boyer–Moore variant
/* Boyer-Moore's bad-character rule only; no good-suffix preprocessing. */
void searchBM(const char text[], const char pattern[]) {
int last[256], n = (int)strlen(text), m = (int)strlen(pattern), found = 0;This program implements the bad-character rule only; it does not build a good-suffix table. last has one entry per byte value. n and m are text/pattern byte lengths, and found starts at zero.
Lines 54–55Record each byte’s rightmost pattern position
for (int c = 0; c < 256; ++c) last[c] = -1;
for (int j = 0; j < m; ++j) last[(unsigned char)pattern[j]] = j;Start every last entry at -1, meaning absent. Scan the pattern left to right and overwrite the entry, leaving its final index. Cast the byte to unsigned char for a nonnegative table index.
Lines 56–58Compare a candidate alignment from right to left
for (int start = 0; start <= n - m;) {
int j = m - 1;
while (j >= 0 && pattern[j] == text[start + j]) --j;start is the text alignment offset. Try only alignments where the full pattern fits. j begins at the final pattern character and moves left while bytes match.
Lines 59–61Report a match and choose a safe following shift
if (j < 0) {
printf("Match at index %d\n",start); found = 1;
start += start + m < n ? m - last[(unsigned char)text[start + m]] : 1;j < 0 means all characters matched. Print start and set found. If a text byte exists immediately after the match, use its last occurrence in the pattern to choose a shift; otherwise advance by one. This can still report overlapping matches.
Lines 62–66Shift after a mismatch using the bad character
} else {
int shift = j - last[(unsigned char)text[start + j]];
start += shift > 0 ? shift : 1;
}
}Subtract the mismatching text byte’s last pattern position from j. Shift at least one even when that difference is not positive, ensuring progress. The braces finish the mismatch branch and alignment loop.
Lines 67–68Print a no-match message only when needed
if (!found) puts("No match.");
}If no match set found, print the message. Otherwise the earlier match reports are sufficient.
Lines 69–73Read complete lines and run the search
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();
searchBM(text,pattern); return 0;
}Allow at most 1000 bytes per line, reject failed reads and an empty pattern, then call this bad-character implementation. The text itself may be empty; a longer pattern simply has no fitting alignment.
FOLLOW THE VALUES
Dry run
- Text ABCXABC and pattern ABC begin aligned at index 0.
- C, B and A match right to left, so index 0 is reported.
- The following byte X is absent from the pattern, giving shift 3−(−1) = 4.
- At index 4, ABC matches again; both occurrences are printed.
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
HERE IS A SIMPLE EXAMPLE
EXAMPLE
Match at index 17
Sample 2
AAAAA
AAA
Match at index 0
Match at index 1
Match at index 2
Sample 3
ABCXABC
ABC
Match at index 0
Match at index 4
Common mistakes
- Use unsigned char when indexing the byte table; signed char can create negative indexes.
- Never allow a zero or negative shift.
- Do not label this variant as implementing the good-suffix rule.
WHY THIS GROWTH RATE?
Time and space complexity
Preprocessing O(M + σ); worst-case search O(NM); auxiliary table O(σ), σ = 256.
The last-occurrence table is initialized for every byte value and updated once per pattern byte.
In repetitive adversarial inputs, an alignment may compare M bytes and then shift by just one. Up to O(N) alignments therefore give O(NM) worst-case work for this implementation.
Favorable inputs can skip many alignments, but faster typical behavior is not a guaranteed linear worst-case bound. Text and pattern buffers are additional input storage.
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
Try answering aloud, then expand the answer to check your reasoning. These questions focus on the concepts used in this program.
Why does Boyer–Moore start each comparison at the pattern’s right edge?
A mismatch near that edge can immediately identify a useful skip. The algorithm uses the location of the mismatched text byte in the pattern to move the alignment.
What does −1 in the bad-character table signify?
The byte never occurs in the pattern. A mismatch with that byte can move the pattern past its current position, since no earlier alignment at that byte can match.
Why is the proposed bad-character shift clamped to at least one?
The byte’s last pattern occurrence may lie to the right of the mismatch position. That can produce a nonpositive difference, which would stall or move the search backward.
Why are bytes cast to unsigned char before table lookup?
On some C implementations, ordinary char is signed. Casting maps input bytes to 0 through 255, the valid range of this table.
Which Boyer–Moore heuristic is omitted from this teaching version?
The good-suffix heuristic is omitted. The page uses only the bad-character rule, so its stated behavior and complexity describe that particular variant.
Where is EXAMPLE found in HERE IS A SIMPLE EXAMPLE?
It begins at zero-based index 17. Counting the spaces as text bytes is essential; the manual’s shared index-10 output is not the output for this Boyer–Moore example.