Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Substitution Cipher algorithm and tests #1773

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
59 changes: 59 additions & 0 deletions Ciphers/SubstitutionCipher.js
Original file line number Diff line number Diff line change
@@ -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
}
44 changes: 44 additions & 0 deletions Ciphers/test/SubstitutionCipher.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
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
)
})
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)
})

})
4 changes: 2 additions & 2 deletions Maths/MobiusFunction.js
Original file line number Diff line number Diff line change
@@ -28,6 +28,6 @@ export const mobiusFunction = (number) => {
return primeFactorsArray.length !== new Set(primeFactorsArray).size
? 0
: primeFactorsArray.length % 2 === 0
? 1
: -1
? 1
: -1
}

Unchanged files with check annotations Beta

* A Subsequence is sequence obtained by deleting some or no elements without changing the order of elements
* Example: Given a string = "abcd"
* 1. "abc" is a subsequence
* 2. "abd" is a subsequence

Check failure on line 8 in Recursive/SubsequenceRecursive.js

GitHub Actions / Check for spelling errors

abd ==> and, bad
* 3. But "ba" is not a subsequence (because order is changed)
*
* What is lexicographical order?
* In simple terms, lexicographical order is dictionary order.
* Example: Given a string = "abcd"
* 1. "abc" will come before "abcd".
* 2. "abd" will come before "ac".

Check failure on line 15 in Recursive/SubsequenceRecursive.js

GitHub Actions / Check for spelling errors

abd ==> and, bad
*
* References for meaning of subsequence & lexicographical:
* https://en.wikipedia.org/wiki/Subsequence
* like "2A3*3a2", "2A3 3a2", and "2_A3*3#A2"
*
* But the catch is, we have to check only if the alphanumeric characters
* are palindrome i.e remove spaces, symbols, punctuations etc

Check failure on line 14 in String/AlphaNumericPalindrome.js

GitHub Actions / Check for spelling errors

punctuations ==> punctuation, punctuation's
* and the case of the characters doesn't matter
*/
const alphaNumericPalindrome = (str) => {