/**
* Counting tools for character and pattern analysis
*/
import { createVisualBreakdown, createDensitySummary, formatFrequencyTable } from '../utils/visualization.js';
// ============================================================================
// count_letter - Count occurrences of a specific letter
// ============================================================================
export interface CountLetterInput {
text: string;
letter: string;
case_sensitive: boolean;
}
export interface CountLetterOutput {
text: string;
letter: string;
count: number;
case_sensitive: boolean;
positions: number[];
visual: string;
density: string;
}
export function countLetter(input: CountLetterInput): CountLetterOutput {
const { text, letter, case_sensitive } = input;
// Normalize the letter to search for
const searchLetter = case_sensitive ? letter : letter.toLowerCase();
const searchText = case_sensitive ? text : text.toLowerCase();
const positions: number[] = [];
for (let i = 0; i < searchText.length; i++) {
if (searchText[i] === searchLetter) {
positions.push(i);
}
}
const visual = createVisualBreakdown(text, positions, letter);
const density = createDensitySummary(positions.length, text.length, letter);
return {
text,
letter,
count: positions.length,
case_sensitive,
positions,
visual,
density,
};
}
// ============================================================================
// count_letters - Count occurrences of multiple letters at once
// ============================================================================
export interface CountLettersInput {
text: string;
letters: string[];
case_sensitive: boolean;
}
export interface LetterCount {
letter: string;
count: number;
positions: number[];
}
export interface CountLettersOutput {
text: string;
letters_searched: string[];
case_sensitive: boolean;
results: LetterCount[];
total_matches: number;
}
export function countLetters(input: CountLettersInput): CountLettersOutput {
const { text, letters, case_sensitive } = input;
const results: LetterCount[] = letters.map(letter => {
const searchLetter = case_sensitive ? letter : letter.toLowerCase();
const searchText = case_sensitive ? text : text.toLowerCase();
const positions: number[] = [];
for (let i = 0; i < searchText.length; i++) {
if (searchText[i] === searchLetter) {
positions.push(i);
}
}
return {
letter,
count: positions.length,
positions,
};
});
const total_matches = results.reduce((sum, r) => sum + r.count, 0);
return {
text,
letters_searched: letters,
case_sensitive,
results,
total_matches,
};
}
// ============================================================================
// count_substring - Count occurrences of a substring/pattern
// ============================================================================
export interface CountSubstringInput {
text: string;
substring: string;
case_sensitive: boolean;
overlapping: boolean;
}
export interface CountSubstringOutput {
text: string;
substring: string;
count: number;
case_sensitive: boolean;
overlapping: boolean;
positions: number[];
}
export function countSubstring(input: CountSubstringInput): CountSubstringOutput {
const { text, substring, case_sensitive, overlapping } = input;
if (substring.length === 0) {
return {
text,
substring,
count: 0,
case_sensitive,
overlapping,
positions: [],
};
}
const searchText = case_sensitive ? text : text.toLowerCase();
const searchSubstring = case_sensitive ? substring : substring.toLowerCase();
const positions: number[] = [];
let startIndex = 0;
while (startIndex < searchText.length) {
const foundIndex = searchText.indexOf(searchSubstring, startIndex);
if (foundIndex === -1) break;
positions.push(foundIndex);
startIndex = overlapping ? foundIndex + 1 : foundIndex + searchSubstring.length;
}
return {
text,
substring,
count: positions.length,
case_sensitive,
overlapping,
positions,
};
}
// ============================================================================
// letter_frequency - Get frequency distribution of all letters
// ============================================================================
export interface LetterFrequencyInput {
text: string;
case_sensitive: boolean;
include_spaces: boolean;
include_punctuation: boolean;
letters_only: boolean;
}
export interface LetterFrequencyOutput {
text: string;
total_characters: number;
unique_characters: number;
frequency: Record<string, number>;
frequency_table: string;
most_common: Array<{ char: string; count: number }>;
least_common: Array<{ char: string; count: number }>;
}
export function letterFrequency(input: LetterFrequencyInput): LetterFrequencyOutput {
const { text, case_sensitive, include_spaces, include_punctuation, letters_only } = input;
const frequency: Record<string, number> = {};
const processedText = case_sensitive ? text : text.toLowerCase();
for (const char of processedText) {
// Filter based on options
if (letters_only && !/[a-zA-Z]/.test(char)) continue;
if (!include_spaces && char === ' ') continue;
if (!include_punctuation && /[^\w\s]/.test(char)) continue;
frequency[char] = (frequency[char] || 0) + 1;
}
const entries = Object.entries(frequency).sort((a, b) => b[1] - a[1]);
const most_common = entries.slice(0, 5).map(([char, count]) => ({ char, count }));
const least_common = entries.slice(-5).reverse().map(([char, count]) => ({ char, count }));
return {
text,
total_characters: Object.values(frequency).reduce((a, b) => a + b, 0),
unique_characters: Object.keys(frequency).length,
frequency,
frequency_table: formatFrequencyTable(frequency),
most_common,
least_common,
};
}