From c93cda32b70e2943411c7da7b03e65e255de68c9 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 02:50:40 +0200 Subject: [PATCH 01/10] feat: add Substitution Cipher algorithm and tests --- Ciphers/SubstitutionCipher.js | 59 +++++++++++++++++++++++++ Ciphers/test/SubstitutionCipher.test.js | 30 +++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 Ciphers/SubstitutionCipher.js create mode 100644 Ciphers/test/SubstitutionCipher.test.js diff --git a/Ciphers/SubstitutionCipher.js b/Ciphers/SubstitutionCipher.js new file mode 100644 index 0000000000..2383a99796 --- /dev/null +++ b/Ciphers/SubstitutionCipher.js @@ -0,0 +1,59 @@ +/** + * Substitution Cipher + * + * A monoalphabetic substitution cipher replaces each letter of the plaintext + * with another letter based on a fixed permutation (key) of the alphabet. + * https://en.wikipedia.org/wiki/Substitution_cipher + */ + +const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM' + +/** + * Encrypts a string using a monoalphabetic substitution cipher + * @param {string} text - The text to encrypt + * @param {string} key - The substitution key (must be 26 uppercase letters) + * @returns {string} + */ +export function substitutionCipherEncryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = alphabet.indexOf(char) + if (index !== -1) { + result += key[index] + } else { + result += char + } + } + return result +} +/** + * Decrypts a string encrypted with the substitution cipher + * @param {string} text - The encrypted text + * @param {string} key - The substitution key used during encryption + * @returns {string} + */ +export function substitutionCipherDecryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = key.indexOf(char) + if (index !== -1) { + result += alphabet[index] + } else { + result += char + } + } + return result +} diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js new file mode 100644 index 0000000000..583ea99fb4 --- /dev/null +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -0,0 +1,30 @@ +import { describe, it, expect } from 'vitest' +import { + substitutionCipherEncryption, + substitutionCipherDecryption +} from './SubstitutionCipher' + +describe('Substitution Cipher', () => { + const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' + + it('correctly encrypts a message', () => { + const encrypted = substitutionCipherEncryption('HELLO WORLD', key) + expect(encrypted).toBe('ITSSG VGKSR') + }) + + it('correctly decrypts a message', () => { + const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key) + expect(decrypted).toBe('HELLO WORLD') + }) + + it('handles non-alphabetic characters', () => { + const encrypted = substitutionCipherEncryption('Test! 123', key) + expect(encrypted).toBe('ZTLZ! 123') + }) + + it('throws error for invalid key', () => { + expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow( + RangeError + ) + }) +}) From 9f44c2f34a2e15af641cf0f3d290765727338ed3 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 02:59:15 +0200 Subject: [PATCH 02/10] fix: correct import path in SubstitutionCipher test --- Ciphers/test/SubstitutionCipher.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index 583ea99fb4..c608b534c2 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest' import { substitutionCipherEncryption, substitutionCipherDecryption -} from './SubstitutionCipher' +} from '../ciphers/SubstitutionCipher.js' describe('Substitution Cipher', () => { const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' From 417fb61dd093ca437caae44269296df73ed5165f Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:02:57 +0200 Subject: [PATCH 03/10] fix: correct casing in import path for CI compatibility --- Ciphers/test/SubstitutionCipher.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index c608b534c2..cd377cf059 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest' import { substitutionCipherEncryption, substitutionCipherDecryption -} from '../ciphers/SubstitutionCipher.js' +} from '../Ciphers/SubstitutionCipher.js' describe('Substitution Cipher', () => { const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' From 73c15ce69dc0029a21a343222b11e39c2c2781c5 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:08:01 +0200 Subject: [PATCH 04/10] fix: correct import path and folder name for CI --- Ciphers/test/SubstitutionCipher.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index cd377cf059..c608b534c2 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest' import { substitutionCipherEncryption, substitutionCipherDecryption -} from '../Ciphers/SubstitutionCipher.js' +} from '../ciphers/SubstitutionCipher.js' describe('Substitution Cipher', () => { const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' From 7adf020c9a05e8262611643b9cb82bfdafc26284 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:18:45 +0200 Subject: [PATCH 05/10] fix: update import path in SubstitutionCipher test for correct folder structure --- {Ciphers => ciphers}/SubstitutionCipher.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {Ciphers => ciphers}/SubstitutionCipher.js (100%) diff --git a/Ciphers/SubstitutionCipher.js b/ciphers/SubstitutionCipher.js similarity index 100% rename from Ciphers/SubstitutionCipher.js rename to ciphers/SubstitutionCipher.js From 56542d68a647252e13531028df6a5751006138a4 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:25:27 +0200 Subject: [PATCH 06/10] fix: update import path in SubstitutionCipher test for correct folder structure --- Ciphers/test/SubstitutionCipher.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index c608b534c2..871ee40671 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -2,7 +2,8 @@ import { describe, it, expect } from 'vitest' import { substitutionCipherEncryption, substitutionCipherDecryption -} from '../ciphers/SubstitutionCipher.js' +} from '../SubstitutionCipher.js' + describe('Substitution Cipher', () => { const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' From 5f1ed3ee0985248cb12a9db7ae6c0f5464b99bcd Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:53:19 +0200 Subject: [PATCH 07/10] removed --- Ciphers/test/SubstitutionCipher.test.js | 31 ------------- ciphers/SubstitutionCipher.js | 59 ------------------------- 2 files changed, 90 deletions(-) delete mode 100644 Ciphers/test/SubstitutionCipher.test.js delete mode 100644 ciphers/SubstitutionCipher.js diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js deleted file mode 100644 index 871ee40671..0000000000 --- a/Ciphers/test/SubstitutionCipher.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { - substitutionCipherEncryption, - substitutionCipherDecryption -} from '../SubstitutionCipher.js' - - -describe('Substitution Cipher', () => { - const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' - - it('correctly encrypts a message', () => { - const encrypted = substitutionCipherEncryption('HELLO WORLD', key) - expect(encrypted).toBe('ITSSG VGKSR') - }) - - it('correctly decrypts a message', () => { - const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key) - expect(decrypted).toBe('HELLO WORLD') - }) - - it('handles non-alphabetic characters', () => { - const encrypted = substitutionCipherEncryption('Test! 123', key) - expect(encrypted).toBe('ZTLZ! 123') - }) - - it('throws error for invalid key', () => { - expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow( - RangeError - ) - }) -}) diff --git a/ciphers/SubstitutionCipher.js b/ciphers/SubstitutionCipher.js deleted file mode 100644 index 2383a99796..0000000000 --- a/ciphers/SubstitutionCipher.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Substitution Cipher - * - * A monoalphabetic substitution cipher replaces each letter of the plaintext - * with another letter based on a fixed permutation (key) of the alphabet. - * https://en.wikipedia.org/wiki/Substitution_cipher - */ - -const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' -const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM' - -/** - * Encrypts a string using a monoalphabetic substitution cipher - * @param {string} text - The text to encrypt - * @param {string} key - The substitution key (must be 26 uppercase letters) - * @returns {string} - */ -export function substitutionCipherEncryption(text, key = defaultKey) { - if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { - throw new RangeError('Key must be 26 uppercase English letters.') - } - - let result = '' - const textUpper = text.toUpperCase() - for (let i = 0; i < textUpper.length; i++) { - const char = textUpper[i] - const index = alphabet.indexOf(char) - if (index !== -1) { - result += key[index] - } else { - result += char - } - } - return result -} -/** - * Decrypts a string encrypted with the substitution cipher - * @param {string} text - The encrypted text - * @param {string} key - The substitution key used during encryption - * @returns {string} - */ -export function substitutionCipherDecryption(text, key = defaultKey) { - if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { - throw new RangeError('Key must be 26 uppercase English letters.') - } - - let result = '' - const textUpper = text.toUpperCase() - for (let i = 0; i < textUpper.length; i++) { - const char = textUpper[i] - const index = key.indexOf(char) - if (index !== -1) { - result += alphabet[index] - } else { - result += char - } - } - return result -} From c4035dee32e740c0f242c282552c0d636aeb2d3a Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 03:57:48 +0200 Subject: [PATCH 08/10] added Substitution Cipher and its test --- Ciphers/SubstitutionCipher.js | 59 +++++++++++++++++++++++++ Ciphers/test/SubstitutionCipher.test.js | 31 +++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 Ciphers/SubstitutionCipher.js create mode 100644 Ciphers/test/SubstitutionCipher.test.js diff --git a/Ciphers/SubstitutionCipher.js b/Ciphers/SubstitutionCipher.js new file mode 100644 index 0000000000..2383a99796 --- /dev/null +++ b/Ciphers/SubstitutionCipher.js @@ -0,0 +1,59 @@ +/** + * Substitution Cipher + * + * A monoalphabetic substitution cipher replaces each letter of the plaintext + * with another letter based on a fixed permutation (key) of the alphabet. + * https://en.wikipedia.org/wiki/Substitution_cipher + */ + +const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +const defaultKey = 'QWERTYUIOPASDFGHJKLZXCVBNM' + +/** + * Encrypts a string using a monoalphabetic substitution cipher + * @param {string} text - The text to encrypt + * @param {string} key - The substitution key (must be 26 uppercase letters) + * @returns {string} + */ +export function substitutionCipherEncryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = alphabet.indexOf(char) + if (index !== -1) { + result += key[index] + } else { + result += char + } + } + return result +} +/** + * Decrypts a string encrypted with the substitution cipher + * @param {string} text - The encrypted text + * @param {string} key - The substitution key used during encryption + * @returns {string} + */ +export function substitutionCipherDecryption(text, key = defaultKey) { + if (key.length !== 26 || !/^[A-Z]+$/.test(key)) { + throw new RangeError('Key must be 26 uppercase English letters.') + } + + let result = '' + const textUpper = text.toUpperCase() + for (let i = 0; i < textUpper.length; i++) { + const char = textUpper[i] + const index = key.indexOf(char) + if (index !== -1) { + result += alphabet[index] + } else { + result += char + } + } + return result +} diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js new file mode 100644 index 0000000000..871ee40671 --- /dev/null +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest' +import { + substitutionCipherEncryption, + substitutionCipherDecryption +} from '../SubstitutionCipher.js' + + +describe('Substitution Cipher', () => { + const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' + + it('correctly encrypts a message', () => { + const encrypted = substitutionCipherEncryption('HELLO WORLD', key) + expect(encrypted).toBe('ITSSG VGKSR') + }) + + it('correctly decrypts a message', () => { + const decrypted = substitutionCipherDecryption('ITSSG VGKSR', key) + expect(decrypted).toBe('HELLO WORLD') + }) + + it('handles non-alphabetic characters', () => { + const encrypted = substitutionCipherEncryption('Test! 123', key) + expect(encrypted).toBe('ZTLZ! 123') + }) + + it('throws error for invalid key', () => { + expect(() => substitutionCipherEncryption('HELLO', 'BADKEY')).toThrow( + RangeError + ) + }) +}) From b9432a02e087b59fd1e740c18fbcf785fab85e74 Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 04:00:58 +0200 Subject: [PATCH 09/10] edited the style --- Ciphers/test/SubstitutionCipher.test.js | 1 - Maths/MobiusFunction.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index 871ee40671..599dfea8fc 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -4,7 +4,6 @@ import { substitutionCipherDecryption } from '../SubstitutionCipher.js' - describe('Substitution Cipher', () => { const key = 'QWERTYUIOPASDFGHJKLZXCVBNM' diff --git a/Maths/MobiusFunction.js b/Maths/MobiusFunction.js index bd268b8bbd..4239d6ab31 100644 --- a/Maths/MobiusFunction.js +++ b/Maths/MobiusFunction.js @@ -28,6 +28,6 @@ export const mobiusFunction = (number) => { return primeFactorsArray.length !== new Set(primeFactorsArray).size ? 0 : primeFactorsArray.length % 2 === 0 - ? 1 - : -1 + ? 1 + : -1 } From 25add224447e68fe534b44298ebb5284ceb28d0f Mon Sep 17 00:00:00 2001 From: mmohamedkhaled <144614579+mmohamedkhaled@users.noreply.github.com> Date: Fri, 4 Apr 2025 04:16:05 +0200 Subject: [PATCH 10/10] "test: add missing coverage for Substitution Cipher decryption edge cases" --- Ciphers/test/SubstitutionCipher.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Ciphers/test/SubstitutionCipher.test.js b/Ciphers/test/SubstitutionCipher.test.js index 599dfea8fc..820d0303d6 100644 --- a/Ciphers/test/SubstitutionCipher.test.js +++ b/Ciphers/test/SubstitutionCipher.test.js @@ -27,4 +27,18 @@ describe('Substitution Cipher', () => { RangeError ) }) + it('encrypts using default key if none provided', () => { + const encrypted = substitutionCipherEncryption('HELLO WORLD') + expect(encrypted).toBe('ITSSG VGKSR') + }) + + it('decrypts using default key if none provided', () => { + const decrypted = substitutionCipherDecryption('ITSSG VGKSR') + expect(decrypted).toBe('HELLO WORLD') + }) + + it('throws error for invalid key in decryption', () => { + expect(() => substitutionCipherDecryption('HELLO', 'BADKEY')).toThrow(RangeError) + }) + })