program TopWordsPascal;
{$mode objfpc}{$H+}
uses
SysUtils, StrUtils;
(*
This program finds the N most frequently appearing words in a text
after removing stopwords. It demonstrates clean structure, clear
comments, and efficient use of Free Pascal arrays and sorting.
*)
type
// Dynamic array of strings used for word tokens and stopwords
TStringArray = array of string;
// Structure holding a unique word and its frequency counter
TWordFreq = record
Word: string;
Count: Integer;
end;
// Dynamic array of word frequency records
TWordFreqArray = array of TWordFreq;
(* ---------------------------------------------------------------
Helper: check if a character is a punctuation symbol
--------------------------------------------------------------- *)
function IsPunct(c: Char): Boolean;
begin
Result := c in ['.', ',', ';', ':', '!', '?', '"', '''', '(', ')', '[', ']', '{', '}'];
end;
(* ---------------------------------------------------------------
Tokenize text into words (simple whitespace split)
--------------------------------------------------------------- *)
function Tokenize(const Text: string): TStringArray;
var
Parts: TStringArray;
W: string;
I: Integer;
begin
// Initialize dynamic array pointer to nil to eliminate compiler warnings
Result := nil;
Parts := SplitString(Text, ' ');
for I := 0 to High(Parts) do
begin
W := Parts[I];
(* Remove leading punctuation *)
while (Length(W) > 0) and IsPunct(W[1]) do
Delete(W, 1, 1);
(* Remove trailing punctuation *)
while (Length(W) > 0) and IsPunct(W[Length(W)]) do
Delete(W, Length(W), 1);
if W <> '' then
begin
W := LowerCase(W);
// Grow the dynamic array to store the valid word
SetLength(Result, Length(Result) + 1);
Result[High(Result)] := W;
end;
end;
end;
(* ---------------------------------------------------------------
Check if a word is present in the list of stopwords
--------------------------------------------------------------- *)
function IsStopword(const W: string; const Stopwords: TStringArray): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to High(Stopwords) do
if W = Stopwords[I] then
Exit(True);
end;
(* ---------------------------------------------------------------
Count word frequencies, skipping stopwords
--------------------------------------------------------------- *)
function CountWordFrequencies(const Words: TStringArray;
const Stopwords: TStringArray): TWordFreqArray;
var
I, J: Integer;
Found: Boolean;
begin
// Initialize result array pointer to nil to silence compiler warnings
Result := nil;
for I := 0 to High(Words) do
begin
// Skip words present in the stopword array
if IsStopword(Words[I], Stopwords) then
Continue;
Found := False;
// Check if word has already been recorded
for J := 0 to High(Result) do
if Result[J].Word = Words[I] then
begin
Inc(Result[J].Count);
Found := True;
Break;
end;
// Append new word record if not previously found
if not Found then
begin
SetLength(Result, Length(Result) + 1);
Result[High(Result)].Word := Words[I];
Result[High(Result)].Count := 1;
end;
end;
end;
(* ---------------------------------------------------------------
Sort frequency array in descending order by count,
using alphabetical order as a secondary tie-breaker
--------------------------------------------------------------- *)
procedure SortFreq(var Arr: TWordFreqArray);
var
I, J: Integer;
Temp: TWordFreq;
begin
for I := 0 to High(Arr) do
for J := I + 1 to High(Arr) do
if (Arr[J].Count > Arr[I].Count) or
((Arr[J].Count = Arr[I].Count) and (Arr[J].Word < Arr[I].Word)) then
begin
Temp := Arr[I];
Arr[I] := Arr[J];
Arr[J] := Temp;
end;
end;
(* ---------------------------------------------------------------
Sort and extract top N most frequent words
--------------------------------------------------------------- *)
function TopN(Freq: TWordFreqArray; N: Integer): TWordFreqArray;
var
Count, I: Integer;
begin
// Initialize result array pointer to nil
Result := nil;
Count := Length(Freq);
SortFreq(Freq);
if Count > N then
Count := N;
SetLength(Result, Count);
// Copy top elements into result array
for I := 0 to Count - 1 do
begin
Result[I].Word := Freq[I].Word;
Result[I].Count := Freq[I].Count;
end;
end;
(* ---------------------------------------------------------------
Main program execution
--------------------------------------------------------------- *)
var
Text: string;
Stopwords: TStringArray;
Words: TStringArray;
Freq: TWordFreqArray;
TopNWords: TWordFreqArray;
I, N: Integer;
begin
// Source text corpus
Text :=
'C is a general-purpose programming language created in 1972 by ' +
'Dennis Ritchie. C gives programmers direct access to the features ' +
'of CPU. It has been and continues to be used to implement ' +
'operating systems (especially kernels) and device ' +
'drivers. C programming language used on computers ranging from ' +
'supercomputers to microcontrollers and embedded systems.';
// Defined stopwords to ignore
Stopwords := TStringArray.Create(
'the','is','a','to','how','after','but','this','for','by','in',
'and','can','content','be','you','yes','no','next','about','used',
'access','been','continues'
);
// Run pipeline
Words := Tokenize(Text);
Freq := CountWordFrequencies(Words, Stopwords);
N := 7;
TopNWords := TopN(Freq, N);
// Output top results
WriteLn('Top ', N, ' most frequent non-stopwords:');
for I := 0 to High(TopNWords) do
WriteLn(TopNWords[I].Word, ' : ', TopNWords[I].Count);
end.
{
run:
Top 7 most frequent non-stopwords:
c : 3
language : 2
programming : 2
systems : 2
1972 : 1
computers : 1
cpu : 1
}