From f0530e490e0070b43dc284b630d41673f9f60122 Mon Sep 17 00:00:00 2001 From: Sajan Toor Date: Thu, 27 Jun 2024 23:41:25 -0700 Subject: [PATCH 1/4] Initial solution --- typescript/solution.ts | 124 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/typescript/solution.ts b/typescript/solution.ts index e69de29..07f5818 100644 --- a/typescript/solution.ts +++ b/typescript/solution.ts @@ -0,0 +1,124 @@ +const ENCRYPTED_TEXT = "IKEWENENXLNQLPZSLERUMRHEERYBOFNEINCHCV"; +const ENCRYPTION_KEY = "SUPERSPY"; +const OMMITED_LETTER = "J"; +const PADDING = "X"; +const ROW_SIZE = 5; + +interface Position { + row: number; + column: number; +} + +console.log(decode(ENCRYPTED_TEXT, ENCRYPTION_KEY, PADDING)); + +function decode(encryptedText: string, encryptionKey: string, padding: string): string { + const grid = generateKeyGrid(encryptionKey); + let decryptedText = ""; + + for (let i = 0; i < encryptedText.length; i += 2) { + const pair = getPair(encryptedText, i); + const decryptedPair = decryptPair(pair, grid); + decryptedText += decryptedPair; + } + + return removePadding(decryptedText, padding); +} + +function getPair(string: string, index: number) { + return (string[index] + string[index + 1]); +} + +function decryptPair(pair: string, grid: string[][]): string { + const [firstLetter, secondLetter] = pair; + + const firstLetterPosition = findLetterPosition(grid, firstLetter); + const secondLetterPosition = findLetterPosition(grid, secondLetter); + + if (firstLetterPosition.row === secondLetterPosition.row) { + return decryptRow(grid, firstLetterPosition, secondLetterPosition); + } + + if (firstLetterPosition.column === secondLetterPosition.column) { + return decryptColumn(grid, firstLetterPosition, secondLetterPosition); + } + + return decryptRectangle(grid, firstLetterPosition, secondLetterPosition); +} + + +function decryptRow(grid: string[][], firstPosition: Position, secondPosition: Position): string { + let result = ""; + + result += grid[firstPosition.row][(firstPosition.column + ROW_SIZE - 1) % ROW_SIZE]; + result += grid[secondPosition.row][(secondPosition.column + ROW_SIZE - 1) % ROW_SIZE]; + + return result; +} + + +function decryptColumn(grid: string[][], firstPosition: Position, secondPosition: Position): string { + let result = ""; + + result += grid[(firstPosition.row + ROW_SIZE - 1) % ROW_SIZE][firstPosition.column]; + result += grid[(secondPosition.row + ROW_SIZE - 1) % ROW_SIZE][secondPosition.column]; + + return result; +} + +function decryptRectangle(grid: string[][], firstPosition: Position, secondPosition: Position): string { + let result = ""; + + result += grid[firstPosition.row][secondPosition.column]; + result += grid[secondPosition.row][firstPosition.column]; + + return result; +} + +function findLetterPosition(grid: string[][], letter: string): Position { + for (let i = 0; i < grid.length; i++) { + const row = grid[i]; + const columnIndex = row.indexOf(letter); + if (columnIndex !== -1) { + return { row: i, column: columnIndex }; + } + } + + throw new Error(`Letter ${letter} not found in the grid!`); +} + +/** + * + * @param key + * @returns + */ +function generateKeyGrid(key: string): string[][] { + // Determine which letters are in the key + const keySet = new Set(key); + const gridLetters = Array.from(keySet); + let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + alphabet = alphabet.replace(OMMITED_LETTER, ""); + + for (const letter of alphabet) { + if (!keySet.has(letter)) { + gridLetters.push(letter); + } + + if (gridLetters.length === (ROW_SIZE * ROW_SIZE)) { + break; + } + } + + const grid: string[][] = []; + + for (let i = 0; i < gridLetters.length; i += ROW_SIZE) { + const row = gridLetters.slice(i, i + ROW_SIZE); + grid.push(row); + } + + return grid; +} + +function removePadding(text: string, padding: string) { + return text.replace(new RegExp(padding, "g"), ""); +} + From 9e1dc33677bd03cb517a716e32bcedb0ee2db2ff Mon Sep 17 00:00:00 2001 From: Sajan Toor Date: Thu, 27 Jun 2024 23:51:00 -0700 Subject: [PATCH 2/4] Create class instead --- typescript/solution.ts | 166 ++++++++++++++++++++++------------------- 1 file changed, 88 insertions(+), 78 deletions(-) diff --git a/typescript/solution.ts b/typescript/solution.ts index 07f5818..c06cf04 100644 --- a/typescript/solution.ts +++ b/typescript/solution.ts @@ -9,116 +9,126 @@ interface Position { column: number; } -console.log(decode(ENCRYPTED_TEXT, ENCRYPTION_KEY, PADDING)); +class Decryptor { + private readonly grid: string[][]; + private readonly padding: string; -function decode(encryptedText: string, encryptionKey: string, padding: string): string { - const grid = generateKeyGrid(encryptionKey); - let decryptedText = ""; - - for (let i = 0; i < encryptedText.length; i += 2) { - const pair = getPair(encryptedText, i); - const decryptedPair = decryptPair(pair, grid); - decryptedText += decryptedPair; + constructor(key: string, padding: string) { + this.grid = this.generateKeyGrid(key); + this.padding = padding; } - return removePadding(decryptedText, padding); -} - -function getPair(string: string, index: number) { - return (string[index] + string[index + 1]); -} + public decode(encryptedText: string): string { + let decryptedText = ""; -function decryptPair(pair: string, grid: string[][]): string { - const [firstLetter, secondLetter] = pair; - - const firstLetterPosition = findLetterPosition(grid, firstLetter); - const secondLetterPosition = findLetterPosition(grid, secondLetter); + for (let i = 0; i < encryptedText.length; i += 2) { + const pair = this.getPair(encryptedText, i); + const decryptedPair = this.decryptPair(pair); + decryptedText += decryptedPair; + } - if (firstLetterPosition.row === secondLetterPosition.row) { - return decryptRow(grid, firstLetterPosition, secondLetterPosition); + return this.removePadding(decryptedText); } - if (firstLetterPosition.column === secondLetterPosition.column) { - return decryptColumn(grid, firstLetterPosition, secondLetterPosition); + private getPair(string: string, index: number) { + return (string[index] + string[index + 1]); } - return decryptRectangle(grid, firstLetterPosition, secondLetterPosition); -} + private decryptPair(pair: string): string { + const [firstLetter, secondLetter] = pair; -function decryptRow(grid: string[][], firstPosition: Position, secondPosition: Position): string { - let result = ""; + const firstPosition = this.findLetterPosition(firstLetter); + const secondPosition = this.findLetterPosition(secondLetter); - result += grid[firstPosition.row][(firstPosition.column + ROW_SIZE - 1) % ROW_SIZE]; - result += grid[secondPosition.row][(secondPosition.column + ROW_SIZE - 1) % ROW_SIZE]; + if (firstPosition.row === secondPosition.row) { + return this.decryptRow(firstPosition, secondPosition); + } - return result; -} + if (firstPosition.column === secondPosition.column) { + return this.decryptColumn(firstPosition, secondPosition); + } + return this.decryptRectangle(firstPosition, secondPosition); + } -function decryptColumn(grid: string[][], firstPosition: Position, secondPosition: Position): string { - let result = ""; + private decryptRow(firstPosition: Position, secondPosition: Position): string { + let result = ""; - result += grid[(firstPosition.row + ROW_SIZE - 1) % ROW_SIZE][firstPosition.column]; - result += grid[(secondPosition.row + ROW_SIZE - 1) % ROW_SIZE][secondPosition.column]; + result += this.grid[firstPosition.row][(firstPosition.column + ROW_SIZE - 1) % ROW_SIZE]; + result += this.grid[secondPosition.row][(secondPosition.column + ROW_SIZE - 1) % ROW_SIZE]; - return result; -} + return result; + } -function decryptRectangle(grid: string[][], firstPosition: Position, secondPosition: Position): string { - let result = ""; - result += grid[firstPosition.row][secondPosition.column]; - result += grid[secondPosition.row][firstPosition.column]; + private decryptColumn(firstPosition: Position, secondPosition: Position): string { + let result = ""; - return result; -} + result += this.grid[(firstPosition.row + ROW_SIZE - 1) % ROW_SIZE][firstPosition.column]; + result += this.grid[(secondPosition.row + ROW_SIZE - 1) % ROW_SIZE][secondPosition.column]; -function findLetterPosition(grid: string[][], letter: string): Position { - for (let i = 0; i < grid.length; i++) { - const row = grid[i]; - const columnIndex = row.indexOf(letter); - if (columnIndex !== -1) { - return { row: i, column: columnIndex }; - } + return result; } - throw new Error(`Letter ${letter} not found in the grid!`); -} + private decryptRectangle(firstPosition: Position, secondPosition: Position): string { + let result = ""; -/** - * - * @param key - * @returns - */ -function generateKeyGrid(key: string): string[][] { - // Determine which letters are in the key - const keySet = new Set(key); - const gridLetters = Array.from(keySet); - let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - alphabet = alphabet.replace(OMMITED_LETTER, ""); - - for (const letter of alphabet) { - if (!keySet.has(letter)) { - gridLetters.push(letter); - } + result += this.grid[firstPosition.row][secondPosition.column]; + result += this.grid[secondPosition.row][firstPosition.column]; - if (gridLetters.length === (ROW_SIZE * ROW_SIZE)) { - break; + return result; + } + + private findLetterPosition(letter: string): Position { + for (let i = 0; i < this.grid.length; i++) { + const row = this.grid[i]; + const columnIndex = row.indexOf(letter); + if (columnIndex !== -1) { + return { row: i, column: columnIndex }; + } } + + throw new Error(`Letter ${letter} not found in the grid!`); } - const grid: string[][] = []; + private generateKeyGrid(key: string): string[][] { + // Determine which letters are in the key + const keySet = new Set(key); + const gridLetters = Array.from(keySet); + let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + alphabet = alphabet.replace(OMMITED_LETTER, ""); + + for (const letter of alphabet) { + if (!keySet.has(letter)) { + gridLetters.push(letter); + } + + if (gridLetters.length === (ROW_SIZE * ROW_SIZE)) { + break; + } + } + + const grid: string[][] = []; + + for (let i = 0; i < gridLetters.length; i += ROW_SIZE) { + const row = gridLetters.slice(i, i + ROW_SIZE); + grid.push(row); + } - for (let i = 0; i < gridLetters.length; i += ROW_SIZE) { - const row = gridLetters.slice(i, i + ROW_SIZE); - grid.push(row); + return grid; } - return grid; + private removePadding(text: string) { + return text.replace(new RegExp(this.padding, "g"), ""); + } } -function removePadding(text: string, padding: string) { - return text.replace(new RegExp(padding, "g"), ""); +function main() { + const decryptor = new Decryptor(ENCRYPTION_KEY, PADDING); + const decryptedText = decryptor.decode(ENCRYPTED_TEXT); + console.log(decryptedText); } +main(); + From 73f40249884e78e712464edd7274f261610361f1 Mon Sep 17 00:00:00 2001 From: Sajan Toor Date: Fri, 28 Jun 2024 00:10:04 -0700 Subject: [PATCH 3/4] Add documentation --- typescript/solution.ts | 181 ++++++++++++++++++++++++++++++++--------- 1 file changed, 144 insertions(+), 37 deletions(-) diff --git a/typescript/solution.ts b/typescript/solution.ts index c06cf04..18d6a00 100644 --- a/typescript/solution.ts +++ b/typescript/solution.ts @@ -2,23 +2,51 @@ const ENCRYPTED_TEXT = "IKEWENENXLNQLPZSLERUMRHEERYBOFNEINCHCV"; const ENCRYPTION_KEY = "SUPERSPY"; const OMMITED_LETTER = "J"; const PADDING = "X"; -const ROW_SIZE = 5; +/** + * Represents a position in the grid + */ interface Position { row: number; column: number; } +/** + * Decryptor class that decrypts a message using the Playfair cipher + * @param key Encryption key + * @param padding Padding character + * @returns Decrypted message + * @example + * + * const decryptor = new Decryptor("KEY", "X"); + * const decryptedText = decryptor.decrypt("ENCRYPTED_TEXT"); + * + */ class Decryptor { private readonly grid: string[][]; private readonly padding: string; - + private readonly rowSize = 5; + private readonly gridIndex: Map; + + /** + * Creates a new instance of the Decryptor class + * + * @param key The encryption key + * @param padding The padding character used in the encryption + */ constructor(key: string, padding: string) { this.grid = this.generateKeyGrid(key); + this.gridIndex = this.createGridIndex(); this.padding = padding; } - public decode(encryptedText: string): string { + /** + * Decrypts an encrypted message using the Playfair cipher + * + * @param encryptedText Encrypted message to decrypt + * @returns Decrypted message + */ + public decrypt(encryptedText: string): string { let decryptedText = ""; for (let i = 0; i < encryptedText.length; i += 2) { @@ -27,98 +55,178 @@ class Decryptor { decryptedText += decryptedPair; } - return this.removePadding(decryptedText); + decryptedText = this.removePadding(decryptedText); + return decryptedText; } - private getPair(string: string, index: number) { - return (string[index] + string[index + 1]); + + /** + * Decrypts an encrypted message using the Playfair cipher + * + * @param encryptedText Encrypted message to decrypt + * @param key Encryption key + * @param padding Padding character + * @returns Decrypted message + * + * @example + * + * const decryptedText = Decryptor.decrypt("ENCRYPTED_TEXT", "KEY", "X"); + */ + public static decrypt(encryptedText: string, key: string, padding: string) { + const decryptor = new Decryptor(key, padding); + return decryptor.decrypt(encryptedText); } + /** + * + * @param string String to get the pair from + * @param index Index of the first letter of the pair + * @returns A pair of letters from the string + */ + private getPair(string: string, index: number) { + return string[index] + string[index + 1]; + } + /** + * + * @param pair Pair of letters to decrypt + * @returns Decrypted pair of letters + */ private decryptPair(pair: string): string { const [firstLetter, secondLetter] = pair; - const firstPosition = this.findLetterPosition(firstLetter); - const secondPosition = this.findLetterPosition(secondLetter); + const first = this.findLetterPosition(firstLetter); + const second = this.findLetterPosition(secondLetter); - if (firstPosition.row === secondPosition.row) { - return this.decryptRow(firstPosition, secondPosition); + if (first.row === second.row) { + return this.decryptRow(first, second); } - if (firstPosition.column === secondPosition.column) { - return this.decryptColumn(firstPosition, secondPosition); + if (first.column === second.column) { + return this.decryptColumn(first, second); } - return this.decryptRectangle(firstPosition, secondPosition); + return this.decryptRectangle(first, second); } - private decryptRow(firstPosition: Position, secondPosition: Position): string { + /** + * Helper function to decrypt a pair of letters in the same row + * @param first First letter position + * @param second Second letter postion + * @returns Decrypted pair of letters + */ + private decryptRow(first: Position, second: Position): string { let result = ""; - result += this.grid[firstPosition.row][(firstPosition.column + ROW_SIZE - 1) % ROW_SIZE]; - result += this.grid[secondPosition.row][(secondPosition.column + ROW_SIZE - 1) % ROW_SIZE]; + result += this.grid[first.row][(first.column + this.rowSize - 1) % this.rowSize]; + result += this.grid[second.row][(second.column + this.rowSize - 1) % this.rowSize]; return result; } - - private decryptColumn(firstPosition: Position, secondPosition: Position): string { + /** + * Helper function to decrypt a pair of letters in the same column + * @param first First letter position + * @param second Second letter postion + * @returns Decrypted pair of letters + */ + private decryptColumn(first: Position, second: Position): string { let result = ""; - result += this.grid[(firstPosition.row + ROW_SIZE - 1) % ROW_SIZE][firstPosition.column]; - result += this.grid[(secondPosition.row + ROW_SIZE - 1) % ROW_SIZE][secondPosition.column]; + result += this.grid[(first.row + this.rowSize - 1) % this.rowSize][first.column]; + result += this.grid[(second.row + this.rowSize - 1) % this.rowSize][second.column]; return result; } - private decryptRectangle(firstPosition: Position, secondPosition: Position): string { + /** + * Helper function to decrypt a pair of letters neither in the same row nor column + * @param first First letter position + * @param second Second letter postion + * @returns Decrypted pair of letters + */ + private decryptRectangle(first: Position, second: Position): string { let result = ""; - result += this.grid[firstPosition.row][secondPosition.column]; - result += this.grid[secondPosition.row][firstPosition.column]; + result += this.grid[first.row][second.column]; + result += this.grid[second.row][first.column]; return result; } + /** + * + * @param letter Letter to find in the grid + * @returns `Position` of the letter in the grid if found, else throws an error + */ private findLetterPosition(letter: string): Position { - for (let i = 0; i < this.grid.length; i++) { - const row = this.grid[i]; - const columnIndex = row.indexOf(letter); - if (columnIndex !== -1) { - return { row: i, column: columnIndex }; - } + const position = this.gridIndex.get(letter); + if (!position) { + throw new Error(`Letter ${letter} not found in the grid!`); } - throw new Error(`Letter ${letter} not found in the grid!`); + return position; } + /** + * + * @param key Encryption key + * @returns A 5x5 grid generated from the encryption key for the Playfair cipher + */ private generateKeyGrid(key: string): string[][] { // Determine which letters are in the key const keySet = new Set(key); const gridLetters = Array.from(keySet); let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; alphabet = alphabet.replace(OMMITED_LETTER, ""); + const maxLetters = this.rowSize * this.rowSize; + // Add the remaining letters of the alphabet to the grid for (const letter of alphabet) { - if (!keySet.has(letter)) { - gridLetters.push(letter); + if (keySet.has(letter)) { + continue; } - if (gridLetters.length === (ROW_SIZE * ROW_SIZE)) { + gridLetters.push(letter); + + if (gridLetters.length === maxLetters) { break; } } const grid: string[][] = []; - for (let i = 0; i < gridLetters.length; i += ROW_SIZE) { - const row = gridLetters.slice(i, i + ROW_SIZE); + // Create the grid from the letters + for (let i = 0; i < gridLetters.length; i += this.rowSize) { + const row = gridLetters.slice(i, i + this.rowSize); grid.push(row); } return grid; } + /** + * + * @returns A map of the letters in the grid to their positions + */ + private createGridIndex(): Map { + const gridIndex = new Map(); + + for (let row = 0; row < this.grid.length; row++) { + for (let column = 0; column < this.grid[row].length; column++) { + const letter = this.grid[row][column]; + gridIndex.set(letter, { row, column }); + } + } + + return gridIndex; + } + + /** + * + * @param text Text to remove padding from + * @returns Text with all instances of the padding character removed + */ private removePadding(text: string) { return text.replace(new RegExp(this.padding, "g"), ""); } @@ -126,9 +234,8 @@ class Decryptor { function main() { const decryptor = new Decryptor(ENCRYPTION_KEY, PADDING); - const decryptedText = decryptor.decode(ENCRYPTED_TEXT); + const decryptedText = decryptor.decrypt(ENCRYPTED_TEXT); console.log(decryptedText); } main(); - From 713234f5103cf835c148b73802ff9619720a0292 Mon Sep 17 00:00:00 2001 From: Sajan Toor Date: Fri, 28 Jun 2024 00:13:57 -0700 Subject: [PATCH 4/4] Fix formatting --- typescript/solution.ts | 65 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/typescript/solution.ts b/typescript/solution.ts index 18d6a00..7354b86 100644 --- a/typescript/solution.ts +++ b/typescript/solution.ts @@ -1,8 +1,3 @@ -const ENCRYPTED_TEXT = "IKEWENENXLNQLPZSLERUMRHEERYBOFNEINCHCV"; -const ENCRYPTION_KEY = "SUPERSPY"; -const OMMITED_LETTER = "J"; -const PADDING = "X"; - /** * Represents a position in the grid */ @@ -17,22 +12,23 @@ interface Position { * @param padding Padding character * @returns Decrypted message * @example - * + * * const decryptor = new Decryptor("KEY", "X"); * const decryptedText = decryptor.decrypt("ENCRYPTED_TEXT"); - * + * */ class Decryptor { private readonly grid: string[][]; private readonly padding: string; private readonly rowSize = 5; private readonly gridIndex: Map; + private readonly ommitedLetter = "J"; /** * Creates a new instance of the Decryptor class - * + * * @param key The encryption key - * @param padding The padding character used in the encryption + * @param padding The padding character used in the encryption */ constructor(key: string, padding: string) { this.grid = this.generateKeyGrid(key); @@ -42,9 +38,9 @@ class Decryptor { /** * Decrypts an encrypted message using the Playfair cipher - * + * * @param encryptedText Encrypted message to decrypt - * @returns Decrypted message + * @returns Decrypted message */ public decrypt(encryptedText: string): string { let decryptedText = ""; @@ -59,36 +55,35 @@ class Decryptor { return decryptedText; } - - /** + /** * Decrypts an encrypted message using the Playfair cipher - * + * * @param encryptedText Encrypted message to decrypt * @param key Encryption key * @param padding Padding character * @returns Decrypted message - * + * * @example - * + * * const decryptedText = Decryptor.decrypt("ENCRYPTED_TEXT", "KEY", "X"); - */ + */ public static decrypt(encryptedText: string, key: string, padding: string) { const decryptor = new Decryptor(key, padding); return decryptor.decrypt(encryptedText); } /** - * - * @param string String to get the pair from + * + * @param string String to get the pair from * @param index Index of the first letter of the pair - * @returns A pair of letters from the string + * @returns A pair of letters from the string */ private getPair(string: string, index: number) { return string[index] + string[index + 1]; } /** - * + * * @param pair Pair of letters to decrypt * @returns Decrypted pair of letters */ @@ -112,7 +107,7 @@ class Decryptor { /** * Helper function to decrypt a pair of letters in the same row * @param first First letter position - * @param second Second letter postion + * @param second Second letter postion * @returns Decrypted pair of letters */ private decryptRow(first: Position, second: Position): string { @@ -125,11 +120,11 @@ class Decryptor { } /** - * Helper function to decrypt a pair of letters in the same column - * @param first First letter position - * @param second Second letter postion - * @returns Decrypted pair of letters - */ + * Helper function to decrypt a pair of letters in the same column + * @param first First letter position + * @param second Second letter postion + * @returns Decrypted pair of letters + */ private decryptColumn(first: Position, second: Position): string { let result = ""; @@ -142,7 +137,7 @@ class Decryptor { /** * Helper function to decrypt a pair of letters neither in the same row nor column * @param first First letter position - * @param second Second letter postion + * @param second Second letter postion * @returns Decrypted pair of letters */ private decryptRectangle(first: Position, second: Position): string { @@ -155,7 +150,7 @@ class Decryptor { } /** - * + * * @param letter Letter to find in the grid * @returns `Position` of the letter in the grid if found, else throws an error */ @@ -169,7 +164,7 @@ class Decryptor { } /** - * + * * @param key Encryption key * @returns A 5x5 grid generated from the encryption key for the Playfair cipher */ @@ -178,7 +173,7 @@ class Decryptor { const keySet = new Set(key); const gridLetters = Array.from(keySet); let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - alphabet = alphabet.replace(OMMITED_LETTER, ""); + alphabet = alphabet.replace(this.ommitedLetter, ""); const maxLetters = this.rowSize * this.rowSize; // Add the remaining letters of the alphabet to the grid @@ -206,7 +201,7 @@ class Decryptor { } /** - * + * * @returns A map of the letters in the grid to their positions */ private createGridIndex(): Map { @@ -223,7 +218,7 @@ class Decryptor { } /** - * + * * @param text Text to remove padding from * @returns Text with all instances of the padding character removed */ @@ -232,6 +227,10 @@ class Decryptor { } } +const ENCRYPTED_TEXT = "IKEWENENXLNQLPZSLERUMRHEERYBOFNEINCHCV"; +const ENCRYPTION_KEY = "SUPERSPY"; +const PADDING = "X"; + function main() { const decryptor = new Decryptor(ENCRYPTION_KEY, PADDING); const decryptedText = decryptor.decrypt(ENCRYPTED_TEXT);