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

Add Ciphers #33

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions specs/CipherSpecs/CaesarCipherSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{-# LANGUAGE ScopedTypeVariables #-}
module CipherSpecs.CaesarCipherSpec where

import Test.Hspec
import Test.QuickCheck
import Ciphers.CaesarCipher
import Data.Char (toLower)

spec :: Spec
spec = do
describe "encrypt" $ do
it "does nothing when alphabet is empty" $ property $
encrypt "hello" 5 [] == "hello"

it "does nothing when key is 0" $ property $
encrypt "hello" 0 ['a'..'z'] == "hello"

it "does not transform characters not found in the provided alphabet" $ property $
encrypt "h3llo" 1 ['a'..'z'] == "i3mmp"

describe "decrypt" $ do
it "returns the original input when given the same key and alphabet as encrypt" $ property $
forAll arbitrary $
\(asciiInput :: ASCIIString, key :: Int) -> let input = map toLower $ getASCIIString asciiInput
in decrypt (encrypt input key ['a'..'z']) key ['a'..'z'] == input
22 changes: 22 additions & 0 deletions src/Ciphers/CaesarCipher.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
module Ciphers.CaesarCipher(encrypt,decrypt) where

import Data.List (elemIndex)
import Data.Maybe (fromMaybe)

-- | Encrypting a string maps each character to the character `n` elements
-- after it in the given alphabet, where `n`is the provided key
encrypt :: String -> Int -> String -> String
encrypt = helper (+)

-- | Decrypting a string maps each character to the character `n` elements
-- before it in the given alphabet, where `n` is the provided key
decrypt :: String -> Int -> String -> String
decrypt = helper (-)

helper :: (Int -> Int -> Int) -> String -> Int -> String -> String
helper _ input 0 _ = input
helper _ input _ [] = input
helper op input key alphabet = map (\x -> fromMaybe x (mappedChar $ elemIndex x alphabet)) input
where
len = length alphabet
mappedChar = fmap (\c -> alphabet !! (c `op` key `mod` len))
16 changes: 16 additions & 0 deletions src/Ciphers/Rot13.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Ciphers.Rot13 where

import Data.Char (isAsciiLower, isAsciiUpper)

-- See https://en.wikipedia.org/wiki/ROT13

-- | "Rotates" each character in a string by 13.
-- Note: This only rotates alphabetic characters, any other character is left
-- as-is
dencrypt :: String -> String
dencrypt = map rotate

rotate :: Char -> Char
rotate c | isAsciiLower c = ([c..'z'] ++ ['a'..'z']) !! 13
| isAsciiUpper c = ([c..'Z'] ++ ['A'..'Z']) !! 13
| otherwise = c