Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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'. |

---


#### <a id="complexity"></a>`complexity(text)`

Expand Down Expand Up @@ -766,6 +768,25 @@ complexity('');
- `uniqueness` (number): Measure of character uniqueness
- `length` (number): Length of the input string



#### <a id="patterncount"></a>`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
Expand Down
3 changes: 3 additions & 0 deletions src/analyzing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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';
import { complexity } from './complexity';
import { readingDuration } from './readingDuration';
import { wordCount } from './wordCount';
import { stringSimilarity } from './stringSimilarity';
import { patternCount } from './patternCount';

export const analyzing = {
characterCount,
Expand All @@ -19,4 +21,5 @@ export const analyzing = {
readingDuration,
wordCount,
stringSimilarity,
patternCount
};
54 changes: 54 additions & 0 deletions src/analyzing/patternCount.ts
Original file line number Diff line number Diff line change
@@ -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;
}
30 changes: 30 additions & 0 deletions src/tests/analyzing/patternCount.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});