From a410a723b802d2791ea62e35814ebd0343e3453d Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 15 Oct 2023 13:57:40 -0400 Subject: [PATCH 1/2] Abbreviation --- Dynamic-Programming/Abbreviation.js | 46 +++++++++++++++++++ .../tests/Abbreviation.test.js | 20 ++++++++ 2 files changed, 66 insertions(+) create mode 100644 Dynamic-Programming/Abbreviation.js create mode 100644 Dynamic-Programming/tests/Abbreviation.test.js diff --git a/Dynamic-Programming/Abbreviation.js b/Dynamic-Programming/Abbreviation.js new file mode 100644 index 0000000000..e77aa96006 --- /dev/null +++ b/Dynamic-Programming/Abbreviation.js @@ -0,0 +1,46 @@ +/** + * @description + * Given two strings, `a` and `b`, determine if it's possible to make `a` equal + * to `b` You can perform the following operations on the string `a`: + * 1. Capitalize zero or more of `a`'s lowercase letters. + * 2. Delete all the remaining lowercase letters in `a`. + * + * ### Algorithm + * The idea is in the problem statement itself: iterate through characters of + * string `a` and `b` (for character indexes `i` and `j` respectively): + * 1. If `a[i]` and `b[j]` are equal, then move to next position + * 2. If `a[i]` is lowercase of `b[j]`, then explore two possibilities: + * a) Capitalize `a[i]` or + * b) Skip `a[i]` + * 3. If the `a[i]` is not uppercase, just discard that character, else return + * `false` + * + * Time Complexity: (O(|a|*|b|)) where `|a|` => length of string `a` + * + * @param {String} a + * @param {String} b + * @returns {Boolean} + * @see https://www.hackerrank.com/challenges/abbr/problem - Related problem on HackerRank. + */ +export const abbreviation = (a, b) => { + const n = a.length + const m = b.length + + let dp = Array.from({length: n + 1}, () => Array(m + 1).fill(false)) + dp[0][0] = true + + for (let i = 0; i < n; i++) { + for (let j = 0; j <= m; j++) { + if (dp[i][j]) { + if (j < m && a[i].toUpperCase() === b[j]) { + dp[i + 1][j + 1] = true + } + if (a[i] === a[i].toLowerCase()) { + dp[i + 1][j] = true + } + } + } + } + + return dp[n][m] +} \ No newline at end of file diff --git a/Dynamic-Programming/tests/Abbreviation.test.js b/Dynamic-Programming/tests/Abbreviation.test.js new file mode 100644 index 0000000000..30830b5cc5 --- /dev/null +++ b/Dynamic-Programming/tests/Abbreviation.test.js @@ -0,0 +1,20 @@ +import { abbreviation } from '../Abbreviation.js' + +describe('Abbreviation', () => { + test('it can abbreviate string a to b', () => { + expect(abbreviation('', '')).toBe(true) + expect(abbreviation('a', '')).toBe(true) + expect(abbreviation('', 'A')).toBe(false) + expect(abbreviation('a', 'A')).toBe(true) + expect(abbreviation('abcDE', 'ABCDE')).toBe(true) + expect(abbreviation('ABcDE', 'ABCDE')).toBe(true) + expect(abbreviation('abcde', 'ABCDE')).toBe(true) + expect(abbreviation('abcde', 'ABC')).toBe(true) + expect(abbreviation('a', 'ABC')).toBe(false) + expect(abbreviation('abcXYdefghijKLmnopqrs', 'XYKL')).toBe(true) + expect(abbreviation('aBcXYdefghijKLmnOpqrs', 'XYKLOP')).toBe(false) + expect(abbreviation('abc123', 'ABC')).toBe(true) + expect(abbreviation('abc123', 'ABC123')).toBe(true) + expect(abbreviation('abc!@#def', 'ABC')).toBe(true) + }) +}) \ No newline at end of file From 6da05a27883f10db27d92a304371a5e2a615879b Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Thu, 2 Nov 2023 16:26:00 -0400 Subject: [PATCH 2/2] Updates from code review --- Dynamic-Programming/Abbreviation.js | 65 ++++++++++--------- .../tests/Abbreviation.test.js | 46 ++++++++----- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/Dynamic-Programming/Abbreviation.js b/Dynamic-Programming/Abbreviation.js index e77aa96006..06092216b8 100644 --- a/Dynamic-Programming/Abbreviation.js +++ b/Dynamic-Programming/Abbreviation.js @@ -1,46 +1,47 @@ /** * @description - * Given two strings, `a` and `b`, determine if it's possible to make `a` equal - * to `b` You can perform the following operations on the string `a`: - * 1. Capitalize zero or more of `a`'s lowercase letters. - * 2. Delete all the remaining lowercase letters in `a`. + * Given two strings, `source` and `target`, determine if it's possible to make `source` equal + * to `target` You can perform the following operations on the string `source`: + * 1. Capitalize zero or more of `source`'s lowercase letters. + * 2. Delete all the remaining lowercase letters in `source`. * - * ### Algorithm - * The idea is in the problem statement itself: iterate through characters of - * string `a` and `b` (for character indexes `i` and `j` respectively): - * 1. If `a[i]` and `b[j]` are equal, then move to next position - * 2. If `a[i]` is lowercase of `b[j]`, then explore two possibilities: - * a) Capitalize `a[i]` or - * b) Skip `a[i]` - * 3. If the `a[i]` is not uppercase, just discard that character, else return - * `false` + * Time Complexity: (O(|source|*|target|)) where `|source|` => length of string `source` * - * Time Complexity: (O(|a|*|b|)) where `|a|` => length of string `a` - * - * @param {String} a - * @param {String} b - * @returns {Boolean} + * @param {String} source - The string to be transformed. + * @param {String} target - The string we want to transform `source` into. + * @returns {Boolean} - Whether the transformation is possible. * @see https://www.hackerrank.com/challenges/abbr/problem - Related problem on HackerRank. */ -export const abbreviation = (a, b) => { - const n = a.length - const m = b.length +export const isAbbreviation = (source, target) => { + const sourceLength = source.length + const targetLength = target.length - let dp = Array.from({length: n + 1}, () => Array(m + 1).fill(false)) - dp[0][0] = true + // Initialize a table to keep track of possible abbreviations + let canAbbreviate = Array.from({ length: sourceLength + 1 }, () => + Array(targetLength + 1).fill(false) + ) + // Empty strings are trivially abbreviatable + canAbbreviate[0][0] = true - for (let i = 0; i < n; i++) { - for (let j = 0; j <= m; j++) { - if (dp[i][j]) { - if (j < m && a[i].toUpperCase() === b[j]) { - dp[i + 1][j + 1] = true + for (let sourceIndex = 0; sourceIndex < sourceLength; sourceIndex++) { + for (let targetIndex = 0; targetIndex <= targetLength; targetIndex++) { + if (canAbbreviate[sourceIndex][targetIndex]) { + // If characters at the current position are equal, move to the next position in both strings. + if ( + targetIndex < targetLength && + source[sourceIndex].toUpperCase() === target[targetIndex] + ) { + canAbbreviate[sourceIndex + 1][targetIndex + 1] = true } - if (a[i] === a[i].toLowerCase()) { - dp[i + 1][j] = true + // If the current character in `source` is lowercase, explore two possibilities: + // a) Capitalize it (which is akin to "using" it in `source` to match `target`), or + // b) Skip it (effectively deleting it from `source`). + if (source[sourceIndex] === source[sourceIndex].toLowerCase()) { + canAbbreviate[sourceIndex + 1][targetIndex] = true } } } } - return dp[n][m] -} \ No newline at end of file + return canAbbreviate[sourceLength][targetLength] +} diff --git a/Dynamic-Programming/tests/Abbreviation.test.js b/Dynamic-Programming/tests/Abbreviation.test.js index 30830b5cc5..86e0f97336 100644 --- a/Dynamic-Programming/tests/Abbreviation.test.js +++ b/Dynamic-Programming/tests/Abbreviation.test.js @@ -1,20 +1,30 @@ -import { abbreviation } from '../Abbreviation.js' +import { isAbbreviation } from '../Abbreviation.js' -describe('Abbreviation', () => { - test('it can abbreviate string a to b', () => { - expect(abbreviation('', '')).toBe(true) - expect(abbreviation('a', '')).toBe(true) - expect(abbreviation('', 'A')).toBe(false) - expect(abbreviation('a', 'A')).toBe(true) - expect(abbreviation('abcDE', 'ABCDE')).toBe(true) - expect(abbreviation('ABcDE', 'ABCDE')).toBe(true) - expect(abbreviation('abcde', 'ABCDE')).toBe(true) - expect(abbreviation('abcde', 'ABC')).toBe(true) - expect(abbreviation('a', 'ABC')).toBe(false) - expect(abbreviation('abcXYdefghijKLmnopqrs', 'XYKL')).toBe(true) - expect(abbreviation('aBcXYdefghijKLmnOpqrs', 'XYKLOP')).toBe(false) - expect(abbreviation('abc123', 'ABC')).toBe(true) - expect(abbreviation('abc123', 'ABC123')).toBe(true) - expect(abbreviation('abc!@#def', 'ABC')).toBe(true) +const expectPositive = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(true) +const expectNegative = (word, abbr) => + expect(isAbbreviation(word, abbr)).toBe(false) + +describe('Abbreviation - Positive Tests', () => { + test('it should correctly abbreviate or transform the source string to match the target string', () => { + expectPositive('', '') + expectPositive('a', '') + expectPositive('a', 'A') + expectPositive('abcDE', 'ABCDE') + expectPositive('ABcDE', 'ABCDE') + expectPositive('abcde', 'ABCDE') + expectPositive('abcde', 'ABC') + expectPositive('abcXYdefghijKLmnopqrs', 'XYKL') + expectPositive('abc123', 'ABC') + expectPositive('abc123', 'ABC123') + expectPositive('abc!@#def', 'ABC') + }) +}) + +describe('Abbreviation - Negative Tests', () => { + test('it should fail to abbreviate or transform the source string when it is not possible to match the target string', () => { + expectNegative('', 'A') + expectNegative('a', 'ABC') + expectNegative('aBcXYdefghijKLmnOpqrs', 'XYKLOP') }) -}) \ No newline at end of file +})