From d33b0abbf373933525668ef49f74debc010aca00 Mon Sep 17 00:00:00 2001 From: rudrajiii <rudrasaha305@gmail.com> Date: Mon, 28 Oct 2024 01:43:39 +0530 Subject: [PATCH] feat: implement Booth's algorithm for lexicographically minimal rotation of a string. --- String/BoothsAlgorithm.js | 80 +++++++++++++++++++++++++++++ String/test/BoothsAlgorithm.test.js | 53 +++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 String/BoothsAlgorithm.js create mode 100644 String/test/BoothsAlgorithm.test.js diff --git a/String/BoothsAlgorithm.js b/String/BoothsAlgorithm.js new file mode 100644 index 0000000000..b2335146e7 --- /dev/null +++ b/String/BoothsAlgorithm.js @@ -0,0 +1,80 @@ +/** + * Booth's Algorithm finds the lexicographically minimal rotation of a string. + * Time Complexity: O(n) - Linear time where n is the length of input string + * Space Complexity: O(n) - Linear space for failure function array + * For More Visit - https://en.wikipedia.org/wiki/Booth%27s_multiplication_algorithm + * @example + * Input: "baca" + * All possible rotations: + * - "baca" + * - "acab" + * - "caba" + * - "abac" + * Output: "abac" (lexicographically smallest) + * + * How it works: + * 1. Doubles the input string to handle all rotations + * 2. Uses failure function (similar to KMP) to find minimal rotation + * 3. Maintains a pointer to the start of minimal rotation found so far + + * @param {string} str - Input string to find minimal rotation + * @returns {string} - Lexicographically minimal rotation of the input string + * @throws {Error} - If input is not a string or is empty + */ +export function findMinimalRotation(str) { + if (typeof str !== 'string') { + throw new Error('Input must be a string') + } + + if (str.length === 0) { + throw new Error('Input string cannot be empty') + } + + // Double the string for rotation comparison + // This allows us to check all rotations by just sliding a window + const s = str + str + const n = s.length + + // Initialize failure function array + const f = new Array(n).fill(-1) + let k = 0 // Starting position of minimal rotation + + //Algorithm's implementation + // Iterate through the doubled string + // j is the current position we're examining + for (let j = 1; j < n; j++) { + // i is the length of the matched prefix in the current candidate + // Get the failure function value for the previous position + let i = f[j - k - 1] + // This loop handles the case when we need to update our current minimal rotation + // It compares characters and finds if there's a better (lexicographically smaller) rotation + while (i !== -1 && s[j] !== s[k + i + 1]) { + // If we find a smaller character, we've found a better rotation + // Update k to the new starting position + if (s[j] < s[k + i + 1]) { + // j-i-1 gives us the starting position of the new minimal rotation + k = j - i - 1 + } + // Update i using the failure function to try shorter prefixes + i = f[i] + } + + // This block updates the failure function and handles new character comparisons + if (i === -1 && s[j] !== s[k + i + 1]) { + // If current character is smaller, update the minimal rotation start + if (s[j] < s[k + i + 1]) { + k = j + } + //If no match found,mark failure function accordingly + f[j - k] = -1 + } else { + //If match found, extend the matched length + f[j - k] = i + 1 + } + } + // After finding k (the starting position of minimal rotation): + // 1. slice(k): Take substring from position k to end + // 2. slice(0, k): Take substring from start to position k + // 3. Concatenate them to get the minimal rotation + return str.slice(k) + str.slice(0, k) +} diff --git a/String/test/BoothsAlgorithm.test.js b/String/test/BoothsAlgorithm.test.js new file mode 100644 index 0000000000..f219820199 --- /dev/null +++ b/String/test/BoothsAlgorithm.test.js @@ -0,0 +1,53 @@ +import { findMinimalRotation } from '../BoothsAlgorithm' + +describe('BoothsAlgorithm', () => { + it('should throw an error if input is not a string', () => { + expect(() => findMinimalRotation(null)).toThrow('Input must be a string') + expect(() => findMinimalRotation(undefined)).toThrow( + 'Input must be a string' + ) + expect(() => findMinimalRotation(123)).toThrow('Input must be a string') + expect(() => findMinimalRotation([])).toThrow('Input must be a string') + }) + + it('should throw an error if input string is empty', () => { + expect(() => findMinimalRotation('')).toThrow( + 'Input string cannot be empty' + ) + }) + + it('should find minimal rotation for simple strings', () => { + expect(findMinimalRotation('abc')).toBe('abc') + expect(findMinimalRotation('bca')).toBe('abc') + expect(findMinimalRotation('cab')).toBe('abc') + }) + + it('should handle strings with repeated characters', () => { + expect(findMinimalRotation('aaaa')).toBe('aaaa') + expect(findMinimalRotation('aaab')).toBe('aaab') + expect(findMinimalRotation('baaa')).toBe('aaab') + }) + + it('should handle strings with special characters', () => { + expect(findMinimalRotation('12#$')).toBe('#$12') + expect(findMinimalRotation('@abc')).toBe('@abc') + expect(findMinimalRotation('xyz!')).toBe('!xyz') + }) + + it('should handle longer strings', () => { + expect(findMinimalRotation('algorithm')).toBe('algorithm') + expect(findMinimalRotation('rithmalgo')).toBe('algorithm') + expect(findMinimalRotation('gorithmal')).toBe('algorithm') + }) + + it('should be case sensitive', () => { + expect(findMinimalRotation('AbC')).toBe('AbC') + expect(findMinimalRotation('BcA')).toBe('ABc') + expect(findMinimalRotation('CAb')).toBe('AbC') + }) + + it('should handle palindromes', () => { + expect(findMinimalRotation('radar')).toBe('adarr') + expect(findMinimalRotation('level')).toBe('ellev') + }) +})