Skip to content

feat: add Substitution Cipher algorithm and tests #1773

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

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
Expand Up @@ -28,6 +28,6 @@ export const mobiusFunction = (number) => {
return primeFactorsArray.length !== new Set(primeFactorsArray).size
? 0
: primeFactorsArray.length % 2 === 0
? 1
: -1
? 1
: -1
}
Loading