Skip to content
Open
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
240 changes: 240 additions & 0 deletions Search/test/StringSearch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { stringSearch } from '../StringSearch'

describe('StringSearch', () => {
// Test basic pattern matching functionality in different positions
describe('Basic functionality', () => {
it('should find a pattern at the beginning of the text', () => {
const text = 'ABCDEFG'
const pattern = 'ABC'
expect(stringSearch(text, pattern)).toStrictEqual([0])
})

it('should find a pattern in the middle of the text', () => {
const text = 'ABCDEFG'
const pattern = 'CDE'
expect(stringSearch(text, pattern)).toStrictEqual([2])
})

it('should find a pattern at the end of the text', () => {
const text = 'ABCDEFG'
const pattern = 'EFG'
expect(stringSearch(text, pattern)).toStrictEqual([4])
})

// Test single character pattern matching
it('should find a single character pattern', () => {
const text = 'ABCDEFG'
const pattern = 'D'
expect(stringSearch(text, pattern)).toStrictEqual([3])
})
})

// Test scenarios where pattern appears multiple times in the text
describe('Multiple occurrences', () => {
it('should find all occurrences of non-overlapping patterns', () => {
const text = 'ABC DEF ABC GHI ABC'
const pattern = 'ABC'
expect(stringSearch(text, pattern)).toStrictEqual([0, 8, 16])
})

// This tests KMP's ability to find overlapping matches correctly
// Pattern "ABAB" at position 0 overlaps with pattern at position 2
it('should find all occurrences of overlapping patterns', () => {
const text = 'ABABAB'
const pattern = 'ABAB'
expect(stringSearch(text, pattern)).toStrictEqual([0, 2])
})

it('should find multiple single character occurrences', () => {
const text = 'ABCDABCDABCD'
const pattern = 'A'
expect(stringSearch(text, pattern)).toStrictEqual([0, 4, 8])
})

// Test pattern with all same characters - creates many overlapping matches
it('should find patterns with repeated characters', () => {
const text = 'AAAAA'
const pattern = 'AAA'
expect(stringSearch(text, pattern)).toStrictEqual([0, 1, 2])
})
})

// Test cases where pattern should not be found
describe('Pattern not found', () => {
it('should return empty array when pattern is not in text', () => {
const text = 'ABCDEFG'
const pattern = 'XYZ'
expect(stringSearch(text, pattern)).toStrictEqual([])
})

// Edge case: pattern longer than text should return empty immediately
it('should return empty array when pattern is longer than text', () => {
const text = 'ABC'
const pattern = 'ABCDEFG'
expect(stringSearch(text, pattern)).toStrictEqual([])
})

// Test partial match that fails - pattern starts similar but differs
it('should return empty array for similar but different pattern', () => {
const text = 'ABCDEFG'
const pattern = 'ABX'
expect(stringSearch(text, pattern)).toStrictEqual([])
})
})

// Test edge cases and boundary conditions
describe('Edge cases', () => {
// Empty text should always return empty array
it('should handle empty text', () => {
const text = ''
const pattern = 'ABC'
expect(stringSearch(text, pattern)).toStrictEqual([])
})

// Empty pattern should return empty array (no valid matches)
// Note: Skipped due to infinite loop bug in current implementation
it.skip('should handle empty pattern', () => {
const text = 'ABCDEFG'
const pattern = ''
expect(stringSearch(text, pattern)).toStrictEqual([])
})

// When text exactly matches pattern, should find at index 0
it('should handle text equal to pattern', () => {
const text = 'ABC'
const pattern = 'ABC'
expect(stringSearch(text, pattern)).toStrictEqual([0])
})

// Minimum valid case: single character match
it('should handle single character text and pattern match', () => {
const text = 'A'
const pattern = 'A'
expect(stringSearch(text, pattern)).toStrictEqual([0])
})

// Minimum invalid case: single character mismatch
it('should handle single character text and pattern mismatch', () => {
const text = 'A'
const pattern = 'B'
expect(stringSearch(text, pattern)).toStrictEqual([])
})
})

// Test complex patterns that demonstrate KMP algorithm's efficiency
// KMP excels at patterns with repeating substrings (prefix-suffix overlap)
describe('Complex patterns (KMP advantage)', () => {
// Pattern has overlapping prefix and suffix, testing KMP's prefix table efficiency
it('should handle patterns with prefix-suffix overlap', () => {
const text = 'ABABCABABABD'
const pattern = 'ABABCABAB'
expect(stringSearch(text, pattern)).toStrictEqual([0])
})

// Pattern repeats completely, creates many potential matches
it('should handle patterns with repeated substrings', () => {
const text = 'ABCABCABCABC'
const pattern = 'ABCABC'
expect(stringSearch(text, pattern)).toStrictEqual([0, 3, 6])
})

// Classic KMP example: pattern "ABCDABD" has prefix "AB" that matches suffix "AB"
// This tests that KMP correctly uses the prefix table to skip unnecessary comparisons
it('should find pattern in longer text', () => {
const text = 'ABC ABCDAB ABCDABCDABDE'
const pattern = 'ABCDABD'
expect(stringSearch(text, pattern)).toStrictEqual([15])
})

// Multiple occurrences of complex pattern with prefix-suffix overlap
it('should find multiple occurrences with prefix-suffix overlap', () => {
const text = 'ABC ABCDABD ABCDABCDABDE'
const pattern = 'ABCDABD'
expect(stringSearch(text, pattern)).toStrictEqual([4, 16])
})
})

// Test handling of special characters and case sensitivity
describe('Special characters and cases', () => {
// Test that spaces in text don't break pattern matching
it('should handle patterns with spaces', () => {
const text = 'Hello World Hello'
const pattern = 'Hello'
expect(stringSearch(text, pattern)).toStrictEqual([0, 12])
})

// Test that special characters (@, #, etc.) are treated as regular characters
it('should handle patterns with special characters', () => {
const text = 'abc@def@ghi@jkl'
const pattern = '@def'
expect(stringSearch(text, pattern)).toStrictEqual([3])
})

// Verify case sensitivity - 'Hello' should not match 'hello'
it('should be case-sensitive', () => {
const text = 'Hello World'
const pattern = 'hello'
expect(stringSearch(text, pattern)).toStrictEqual([])
})

// Verify correct case-sensitive matches are found
it('should find case-sensitive matches', () => {
const text = 'Hello World Hello'
const pattern = 'Hello'
expect(stringSearch(text, pattern)).toStrictEqual([0, 12])
})
})

// Test realistic use cases for string search algorithms
describe('Real-world examples', () => {
// Common use case: searching for DNA subsequences (ATCG nucleotides)
it('should find DNA sequence patterns', () => {
const text = 'ATCGATCGATCG'
const pattern = 'ATCG'
expect(stringSearch(text, pattern)).toStrictEqual([0, 4, 8])
})

// Common use case: finding word occurrences in text
it('should find words in a sentence', () => {
const text = 'the cat in the hat'
const pattern = 'the'
expect(stringSearch(text, pattern)).toStrictEqual([0, 11])
})

// Test with numeric strings (e.g., searching for patterns in phone numbers, IDs)
it('should handle numeric patterns', () => {
const text = '123456789012345'
const pattern = '123'
expect(stringSearch(text, pattern)).toStrictEqual([0, 10])
})
})

// Test cases that stress-test the algorithm's performance characteristics
describe('Performance-related cases', () => {
// Test with many matches - ensures algorithm handles large result sets efficiently
it('should handle pattern that appears many times', () => {
const text = 'A'.repeat(100) + 'B'
const pattern = 'A'
const result = stringSearch(text, pattern)
expect(result.length).toBe(100)
expect(result[0]).toBe(0)
expect(result[99]).toBe(99)
})

// Test worst-case scenario: pattern at the very end of long text
// KMP should efficiently skip through repeated characters
it('should handle long text with pattern at end', () => {
const text = 'A'.repeat(100) + 'XYZ'
const pattern = 'XYZ'
expect(stringSearch(text, pattern)).toStrictEqual([100])
})

// Test with pattern containing no repeated characters
// In this case, KMP's prefix table will mostly be zeros, but still works correctly
it('should handle pattern with no repeated characters', () => {
const text = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
const pattern = 'MNOP'
expect(stringSearch(text, pattern)).toStrictEqual([12])
})
})
})