diff --git a/README.md b/README.md
index 3e90e45..7f1a59d 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,8 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2
- [characterFrequency](#characterfrequency) - Analyzes character frequency in a string
- [stringSimilarity](#stringsimilarity) - Calculates the percentage similarity between two strings
- [complexity](#complexity) - Analyzes string complexity including score, uniqueness, and length
+- [patternCount](#patterncount) - calculates the number of times a specific pattern occurs in a given text
+
### Formatting
@@ -737,7 +739,7 @@ stringSimilarity('flaw', 'lawn', 'Damerau-Levenshtein'); // Returns: 50
| textB | string | required | The second text to compare. |
| algorithm | string | 'Levenshtein' | The algorithm to use: 'Levenshtein' or 'Damerau-Levenshtein'. |
----
+
#### `complexity(text)`
@@ -766,6 +768,25 @@ complexity('');
- `uniqueness` (number): Measure of character uniqueness
- `length` (number): Length of the input string
+
+
+#### `patternCount(text, pattern)`
+
+Counts the number of times a substring (pattern) occurs in a string, including overlapping occurrences.
+This function uses the **Knuth–Morris–Pratt (KMP)** algorithm for efficient matching.
+
+```javascript
+patternCount('aaaa', 'aa'); // 3
+patternCount('abababa', 'aba'); // 3
+patternCount('hello world', 'o'); // 2
+patternCount('hello world', 'x'); // 0
+```
+
+| Parameter | Type | Default | Description |
+| --------- | ------ | -------- | ---------------------------------------------- |
+| text | string | required | The input string to search in |
+| pattern | string | required | The substring (pattern) to count (overlapping) |
+
---
### 🎨 Formatting
diff --git a/src/analyzing/index.ts b/src/analyzing/index.ts
index 260a99e..2d9362f 100644
--- a/src/analyzing/index.ts
+++ b/src/analyzing/index.ts
@@ -4,6 +4,7 @@ export { complexity } from './complexity';
export { readingDuration } from './readingDuration';
export { wordCount } from './wordCount';
export { stringSimilarity } from './stringSimilarity';
+export { patternCount } from './patternCount';
import { characterCount } from './characterCount';
import { characterFrequency } from './characterFrequency';
@@ -11,6 +12,7 @@ import { complexity } from './complexity';
import { readingDuration } from './readingDuration';
import { wordCount } from './wordCount';
import { stringSimilarity } from './stringSimilarity';
+import { patternCount } from './patternCount';
export const analyzing = {
characterCount,
@@ -19,4 +21,5 @@ export const analyzing = {
readingDuration,
wordCount,
stringSimilarity,
+ patternCount
};
diff --git a/src/analyzing/patternCount.ts b/src/analyzing/patternCount.ts
new file mode 100644
index 0000000..0ff927c
--- /dev/null
+++ b/src/analyzing/patternCount.ts
@@ -0,0 +1,54 @@
+/**
+ * Calculates the number of times a specific pattern occurs in a given text, including overlapping occurrences
+ *
+ * The algorithm used here is based on the Knuth-Morris-Pratt (KMP) pattern matching algorithm for better performance
+ *
+ * @param {string} text - The text for which we want to count the occurrences of a specific pattern.
+ * @param {string} pattern - The pattern to search for within the text.
+ * @returns {number} - The number of times the pattern occurs in the text (overlapping).
+ */
+export function patternCount(text: string, pattern: string): number {
+ if (pattern.length === 0) {
+ return 0; // No pattern to search for
+ }
+
+ const prefixFunction = computePrefixFunction(pattern);
+
+ let count = 0;
+ let j = 0; // Index for pattern
+
+ for (let i = 0; i < text.length; i++) {
+ while (j > 0 && text[i] !== pattern[j]) {
+ j = prefixFunction[j - 1];
+ }
+ if (text[i] === pattern[j]) {
+ j++;
+ }
+ if (j === pattern.length) {
+ count++;
+ j = prefixFunction[j - 1]; // Allow for overlapping matches
+ }
+ }
+
+ return count;
+}
+
+/**
+ * Computes the prefix function (partial match table) for KMP algorithm.
+ * @param {string} pattern - The pattern string.
+ * @returns {number[]} - The prefix function array.
+ */
+function computePrefixFunction(pattern: string): number[] {
+ const prefixFunction = new Array(pattern.length).fill(0);
+ let j = 0;
+ for (let i = 1; i < pattern.length; i++) {
+ while (j > 0 && pattern[i] !== pattern[j]) {
+ j = prefixFunction[j - 1];
+ }
+ if (pattern[i] === pattern[j]) {
+ j++;
+ }
+ prefixFunction[i] = j;
+ }
+ return prefixFunction;
+}
\ No newline at end of file
diff --git a/src/tests/analyzing/patternCount.test.ts b/src/tests/analyzing/patternCount.test.ts
new file mode 100644
index 0000000..da755bf
--- /dev/null
+++ b/src/tests/analyzing/patternCount.test.ts
@@ -0,0 +1,30 @@
+import { describe, it } from 'node:test';
+import assert from 'node:assert';
+import { patternCount } from '../../analyzing/patternCount';
+
+describe('patternCount', () => {
+ it('returns 0 for empty string', () => {
+ assert.deepStrictEqual(patternCount('', 'aa'), 0);
+ });
+ it('returns 0 for empty pattern', () => {
+ assert.deepStrictEqual(patternCount('abc', ''), 0);
+ });
+ it('returns 0 for empty string and empty pattern', () => {
+ assert.deepStrictEqual(patternCount('', ''), 0);
+ });
+ it('returns correct count for single character pattern', () => {
+ assert.strictEqual(patternCount('abcabcabc', 'a'), 3);
+ });
+ it('returns correct count for multi-character pattern', () => {
+ assert.strictEqual(patternCount('abcabcabc', 'ab'), 3);
+ });
+ it('returns correct count for overlapping patterns', () => {
+ assert.strictEqual(patternCount('ababababa', 'aba'), 4);
+ });
+ it('returns correct count for non-overlapping patterns', () => {
+ assert.strictEqual(patternCount('abababab', 'ab'), 4);
+ });
+ it('returns 0 for pattern not found', () => {
+ assert.strictEqual(patternCount('abcdefg', 'xyz'), 0);
+ });
+});