DEV Community

dev.to staff
dev.to staff

Posted on

Daily Challenge #42 - Caesar Cipher

A Caesar cipher, or a shift cipher, is one of the most well known encryption techniques. It works by substituting the letters in the message with letters some fixed number of positions down the alphabet.

Your challenge is to write a function that will accept a string and a shift key. It will return the encrypted text. If something in the string is not in the alphabet (e.g. punctuation, spaces) leave it as is.

key: 1
plain: defend the east wall of the castle
cipher: efgfoe uif fbtu xbmm pg uif dbtumf

Bonus
Write a function that will crack the cipher without knowing the shift. The shift key can only be in range of [1, 26].

key: 3
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: attack from the woods at dawn

I've used the brute-force method before (printing every possible key and manually searching for the right one). I can't wait to see other solutions to cracking this easy cipher.

Good luck!


Want to propose a challenge for a future post? Email yo+challenge@dev.to with your suggestions!

Top comments (14)

Collapse
 
ynndvn profile image
La blatte

Here we go:

f=(s)=>[...Array(26)].map((_,i)=>26-i).map(n=>`key: ${26-n}\ncipher: ${s}\nplain: ${[...s].map(l=>(a=l.charCodeAt(0),String.fromCharCode(a<123&&a>96?97+((a-97+n)%26):a))).join('')}`).join`\n---\n`

Without any proper dictionary, I guess I'll stick to the "bruteforce" method! Here's how it works:

  • First, build an Array containing numbers from 26 to 1 ([...Array(26)].map((_,i)=>26-i))
  • Print the current index (26-n) and the cipher (s)
  • Build the associated original text: For each character, if its charCode is between 97 and 122 (hence, if it is a valid lowercase character), add the current index to it, else just return it.
  • Finally, print the complete result
f('dwwdfn iurp wkh zrrgv dw gdzq');

"key: 0
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: dwwdfn iurp wkh zrrgv dw gdzq
---
key: 1
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: cvvcem htqo vjg yqqfu cv fcyp
---
key: 2
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: buubdl gspn uif xppet bu ebxo
---
key: 3
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: attack from the woods at dawn
---
key: 4
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: zsszbj eqnl sgd vnncr zs czvm
---
key: 5
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: yrryai dpmk rfc ummbq yr byul
---
key: 6
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: xqqxzh colj qeb tllap xq axtk
---
key: 7
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: wppwyg bnki pda skkzo wp zwsj
---
key: 8
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: voovxf amjh ocz rjjyn vo yvri
---
key: 9
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: unnuwe zlig nby qiixm un xuqh
---
key: 10
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: tmmtvd ykhf max phhwl tm wtpg
---
key: 11
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: sllsuc xjge lzw oggvk sl vsof
---
key: 12
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: rkkrtb wifd kyv nffuj rk urne
---
key: 13
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: qjjqsa vhec jxu meeti qj tqmd
---
key: 14
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: piiprz ugdb iwt lddsh pi splc
---
key: 15
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: ohhoqy tfca hvs kccrg oh rokb
---
key: 16
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: nggnpx sebz gur jbbqf ng qnja
---
key: 17
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: mffmow rday ftq iaape mf pmiz
---
key: 18
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: leelnv qczx esp hzzod le olhy
---
key: 19
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: kddkmu pbyw dro gyync kd nkgx
---
key: 20
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: jccjlt oaxv cqn fxxmb jc mjfw
---
key: 21
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: ibbiks nzwu bpm ewwla ib liev
---
key: 22
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: haahjr myvt aol dvvkz ha khdu
---
key: 23
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: gzzgiq lxus znk cuujy gz jgct
---
key: 24
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: fyyfhp kwtr ymj bttix fy ifbs
---
key: 25
cipher: dwwdfn iurp wkh zrrgv dw gdzq
plain: exxego jvsq xli asshw ex hear"
Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler • Edited

I’m learning Erlang and I love it.

My solution for cracking the message tries all keys, performs frequency analysis on each result, and sorts the candidates by closeness to the frequencies of letters in the English language.

-module( caesar ).
-compile( export_all ).
-export( [ caesar/2 ] ).

-include_lib("eunit/include/eunit.hrl").

shift_char( Char, Key ) when Char >= $a, Char =< $z ->
    $a + ( Char - $a + Key ) rem 26;
shift_char( Char, Key ) when Char >= $A, Char =< $Z ->
    $A + ( Char - $A + Key ) rem 26;
shift_char( Char, _ ) ->
    Char.

caesar( Message, Key ) when is_integer( Key ), Key > 0, Key < 26 ->
    lists:map( fun( C ) -> shift_char( C, Key ) end, Message ).

shift_char_test_() -> [
    ?_assertEqual( $b, shift_char( $a, 1 ) ),
    ?_assertEqual( $f, shift_char( $a, 5 ) ),
    ?_assertEqual( $a, shift_char( $z, 1 ) ),
    ?_assertEqual( $z, shift_char( $r, 8 ) ),
    ?_assertEqual( $b, shift_char( $r, 10 ) ),
    ?_assertEqual( $M, shift_char( $K, 2 ) ),
    ?_assertEqual( $D, shift_char( $W, 7 ) )
].

casear_test_() -> [
    ?_assertEqual( "b", caesar( "a", 1 ) ),
    ?_assertEqual( "e", caesar( "a", 4 ) ),
    ?_assertEqual( "a", caesar( "z", 1 ) ),
    ?_assertEqual( "efgfoe uif fbtu xbmm pg uif dbtumf", caesar( "defend the east wall of the castle", 1 ) ),
    ?_assertEqual( "dwwdfn iurp wkh zrrgv dw gdzq", caesar( "attack from the woods at dawn", 3 ) ),
    ?_assertEqual( "Dro aesmu, lbygx pyh TEWZON yfob dro vkji nyq!", caesar( "The quick, brown fox JUMPED over the lazy dog!", 10 ) )
].

% calculate the frequencies of the letters in the message
freqs( Message ) ->
    freqs( string:lowercase( Message ), #{}, 0 ).
freqs( [ C | Rest ], Freqs, Count ) when C >= $a, C =< $z ->
    NewFreqs = maps:put( C, maps:get( C, Freqs, 0 ) + 1, Freqs ),
    freqs( Rest, NewFreqs, Count + 1 );
freqs( [ _ | Rest ], Freqs, Count ) ->
    freqs( Rest, Freqs, Count );
freqs( [], Freqs, Count ) ->
    maps:map( fun( _C, Freq ) -> 100 * Freq / Count end, Freqs ).

% calculate the distance between the frequency of a letter in the message
% and the frequency of the same letter in the English language
freq_distance( C, Freq ) ->
    EnglishFreq = maps:get( C, english_frequencies() ),
    math:pow( EnglishFreq - Freq, 2 ).

% calculate the distance between frequencies in the message and frequencies in the English language
distance( Freqs ) ->
    lists:foldr(
        fun( { _, Dist }, Acc ) -> Acc + Dist end,
        0,
        maps:to_list(
            maps:map( fun freq_distance/2, Freqs )
        )
    ).

% try all keys and sort them starting with the decrypted message that has the least distance
% with the frequencies of the English language
crack( Message ) ->
    Keys = lists:seq( 1, 25 ),
    Candidates = lists:map( fun( Key ) -> { 26 - Key, caesar( Message, Key ) } end, Keys ),
    Freqs = lists:map( fun( { Key, Msg } ) -> { Key, Msg, freqs( Msg ) } end, Candidates ),
    Distances = lists:map( fun( { Key, Msg, Frq } ) -> { Key, Msg, distance( Frq ) } end, Freqs ),
    _BestKeys = lists:sort( fun( { _, _, Dist1 }, { _, _, Dist2 } ) -> ( Dist1 - Dist2 ) =< 0 end, Distances ).

% frequencies of the letters in the English language, from
% http://pi.math.cornell.edu/~mec/2003-2004/cryptography/subs/frequencies.html
english_frequencies() ->
    _Frequencies = #{
        $e => 12.02, $t => 9.10, $a => 8.12, $o => 7.68, $i => 7.31, $n => 6.95, $s => 6.28,
        $r => 6.02, $h => 5.92, $d => 4.32, $l => 3.98, $u => 2.88, $c => 2.71, $m => 2.61,
        $f => 2.30, $y => 2.11, $w => 2.09, $g => 2.03, $p => 1.82, $b => 1.49, $v => 1.11,
        $k => 0.69, $x => 0.17, $q => 0.11, $j => 0.10, $z => 0.07
    }.
Enter fullscreen mode Exit fullscreen mode

To try:

% erl
1> c(caesar).
{ok,caesar}

2> caesar:test().
  All 13 tests passed.
ok

3> caesar:caesar( "attack from the woods at dawn", 3 ).
"dwwdfn iurp wkh zrrgv dw gdzq"

4> caesar:crack("efgfoe uif fbtu xbmm pg uif dbtumf" ).
[{1,"defend the east wall of the castle",232.3872551020408},
 {12,"stutcs iwt tphi lpaa du iwt rphiat",339.90096938775514},
 {13,"rstsbr hvs sogh kozz ct hvs qoghzs",514.9585122448979},
 ...

5> caesar:crack("dwwdfn iurp wkh zrrgv dw gdzq").
[{3,"attack from the woods at dawn",309.13214444444446},
 {18,"leelnv qczx esp hzzod le olhy",409.1672777777778},
 {25,"exxego jvsq xli asshw ex hear",415.19877777777776},

6> caesar:crack("Dro aesmu, lbygx pyh TEWZON yfob dro vkji nyq!").
[{10,"The quick, brown fox JUMPED over the lazy dog!",
  142.00382345679012},
 {20,"Jxu gkysa, rhemd ven ZKCFUT eluh jxu bqpo tew!",
  281.83945679012345},
 {21,"Iwt fjxrz, qgdlc udm YJBETS dktg iwt apon sdv!",
  307.0069345679012},
Enter fullscreen mode Exit fullscreen mode
Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler

Of course, instead of decrypting the message 25 times, I should perform frequency analysis, shift the keys of the map 25 times, take the three shortest distances and then decrypt the message with the corresponding keys. I’ll update my solution later.

Collapse
 
alvaromontoro profile image
Alvaro Montoro

JavaScript

const cipher = (s, k) => s.toLowerCase()
                          .split('')
                          .map(c => c.charCodeAt(0) < 'a'.charCodeAt(0) || c.charCodeAt(0) > 'z'.charCodeAt(0) 
                                    ? c
                                    : c.charCodeAt(0) > 122 - k
                                      ? String.fromCharCode(97 + (c.charCodeAt(0) + k - 1) % 122)
                                      : String.fromCharCode(c.charCodeAt(0) + k))
                          .join('');

I imagine that for now, the only way to decypher a phrase would be to check each word to verify that it is valid using either a dictionary or an API. Which may be a pain.

A thing that is not going to work now, but maybe it could work in the future (big "maybe" here, but bear with me for a second):

  • Browsers contain their own dictionary for spell checking.
  • CSS has pseudo-elements for grammar and spelling errors that allows to target content within an element that the browser has identified as having grammar mistakes (::grammar-error) or being misspelled (::spelling-error).
  • We could create a hidden input, add the content of the decyphered text and check if there is a pseudo-element that fulfills one of the error pseudo-elements.

It would be super hacky, but it would allow us to use the browser dictionary (if it's not open already, in which case, we should use it) instead of having our own dictionary or having to call an API.

Unfortunately, ::grammar-error and ::spelling-error are not currently supported by any browser, so I cannot test this crazy idea at the moment.

Collapse
 
jacksoncds profile image
Jackson DaSilva

C#

Rept.it

using System;

class MainClass {
  public static void Main (string[] args) {
    var caesarCipher = new CaesarCipher();

    caesarCipher.Encrypt(3, "attack from the woods at dawn");
  }
}

public class CaesarCipher {
  public void Encrypt(int key, string text){

    if(key < 1 || key > 26){
      throw new Exception("Key must be between 1 and 26 inclusive.");
    }

    Console.WriteLine("Plain:");
    Console.WriteLine(text);

    var decText = new char[text.Length];

    for(int i = 0; i < text.Length; i++){
      int dec = (int)text[i];

      if(dec >= 65 && dec <= 90 || dec >= 97 && dec <= 122){
        decText[i] = (char)(dec + key);
      } else {
        decText[i] = (char)dec;
      }
    }

    Console.WriteLine("Encrypted:");
    Console.WriteLine(decText);
  }
}
Collapse
 
lordapple profile image
LordApple • Edited

C++

#include <iostream>

int main(){
    unsigned key = 1;
    std::string toEncrypt = "defend the east wall of the castle";

    if(key < 1 || key > 26){
        std::cout << "Key must be in range of 1-26" << std::endl;
        return 0;
    }

    for(char& c : toEncrypt){
        if(c < 'A' || c > 'z'){
            continue;
        }else if(c + key > 'z'){
            c = 'a' + (c + key) % 122 - 1;
            continue;
        }else if(c + key > 'Z' && c < 'a'){
            c = 'A' + (c + key) % 90 - 1;
            continue;
        }

        c += key;
    }
    std::cout << toEncrypt;
    return 0;
}
Collapse
 
kvharish profile image
K.V.Harish • Edited

My solution in js

const encode = (sentence, key) => sentence.split(' ')
                                          .map((word) => word.split('')
                                          .map((char) => (/[a-z]/i.test(char)) ? String.fromCharCode(char.charCodeAt(0) + key) : char)
                                          .join(''))
                                          .join(' ');
Collapse
 
nyanafell profile image
Gael Roussel • Edited

Ruby

class String
  def cesar_encrypt(key)
    raise ArgumentError, 'Key must be between 1 and 26 inclusive.' if key < 1 || key > 26

    letters_min = ('a'..'z').to_a
    letters_maj = ('A'..'Z').to_a

    result = tr!(letters_min.join, letters_min.rotate(key).join)
    result.tr(letters_maj.join, letters_maj.rotate(key).join)
  end

  def cesar_decrypt(key)
    raise ArgumentError, 'Key must be between 1 and 26 inclusive.' if key < 1 || key > 26

    letters_min = ('a'..'z').to_a
    letters_maj = ('A'..'Z').to_a

    result = tr!(letters_min.join, letters_min.rotate(-key).join)
    result.tr(letters_maj.join, letters_maj.rotate(-key).join)
  end
end

pp 'efgfoe uif fbtu xbmm pg uif dbtumfabAB'.cesar_decrypt(1)
pp 'defend the east wall of the castlezaZA'.cesar_encrypt(1)
Collapse
 
vince_tblt profile image
Vincent Thibault • Edited

Using English frequency analysis (but you actually need to know the language source), here is a javascript version:

// English letter frequency a-z
// source: https://en.wikipedia.org/wiki/Letter_frequency
const ENGLISH_FREQUENCIES = new Float32Array([
  0.0816, 0.0149, 0.0278, 0.0425, 0.1270, 0.0222, 0.0201, 0.0609, 0.0696, 0.0015, 0.0077, 0.0402, 0.0240,
  0.0674, 0.0750, 0.0192, 0.0009, 0.0598, 0.0632, 0.0905, 0.0275, 0.0097, 0.0236, 0.0015, 0.0197, 0.0007,
].map(v => Math.log(v) / Math.LN2));

// Here we are !
const decipher = str =>
  Array.from({ length: 26 }, (_, key) => {
    // Rotate text
    const text = str
      .toLowerCase()
      .replace(/[a-z]/g, $0 =>
        String.fromCharCode(97 + (($0.charCodeAt(0) - 97 + key) % 26))
      );

    // Get entropy information based on Shannon
    // https://en.wikipedia.org/wiki/Entropy_(information_theory)
    const entropy = text
      .match(/[a-z]/g)
      .map(c => ENGLISH_FREQUENCIES[c.charCodeAt(0) - 97])
      .reduce((sum, frequency, _, arr) => sum - frequency / arr.length, 0);

    return { key, text, entropy };
  }).sort((a, b) => a.entropy - b.entropy); // Sort results

You'll get results sort by entropy score:

console.log(
  decipher("dwwdfn iurp wkh zrrgv dw gdzq")
    .map(({ key, text, entropy }) => `${entropy.toFixed(3)} - ${text} (${key})`)
    .join("\n")
);

// 4.227 - attack from the woods at dawn (23)
// 4.936 - piiprz ugdb iwt lddsh pi splc (12)
// 4.974 - ohhoqy tfca hvs kccrg oh rokb (11)
// 5.098 - tmmtvd ykhf max phhwl tm wtpg (16)
Collapse
 
fayedev97 profile image
Faye • Edited

JavaScript

//-----------------------------------------------------------------------
//-----------------------------Caesar Cipher-----------------------------
//-----------------------------------------------------------------------
//a == 97 z == 122
//A == 65 Z == 90

const cypher = function(msg,key)
{
    let inRange = (number, x, y) =>
    {
        if(number >= x && number <= y)
        {
            return true;
        }
        return false;
    }
    let newMsg = "";
    for(let i = 0; i < msg.length; i++)
    {
        let charUnicode = msg.codePointAt(i);
        if(inRange(charUnicode, 97, 122) || inRange(charUnicode, 65, 90))
        {
            newMsg += String.fromCodePoint(charUnicode + key);
        }else
        {
            newMsg += msg[i];
        }
    }
    return newMsg;
}

let message = "This took hella lotta time !";
console.log(cypher(message, 4));
Collapse
 
hanachin profile image
Seiei Miyagi

ruby <3

def decrypt_caesar_cipher(s, shift)
  decrypter = (?a..?z).cycle.lazy.then { @1.zip(@1.drop(26 - shift)).take(26).to_h }
  decrypter.default_proc = -> { @1.fetch(@2.downcase, nil)&.upcase || @2 }
  s.each_char.inject('') { @1 + decrypter[@2] }
end

def brute_force_caesar_cipher(s)
  0...26 |> to_h { [@1, decrypt_caesar_cipher(s, @1)] }
end

p decrypt_caesar_cipher('dwwdfn iurp wkh zrrgv dw gdzq', 3)
pp brute_force_caesar_cipher('dwwdfn iurp wkh zrrgv dw gdzq')
Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler

My updated Erlang solution.

I now perform frequency analysis on the encrypted message, calculate the correlation with the frequencies of letters in the English language, and use the best N correlation values to decrypt the message.

-module( caesar ).
-export( [ caesar/2, crack/1, crack/2 ] ).

-include_lib("eunit/include/eunit.hrl").

wrap( Char, Length ) when is_integer( Length ), Length > 0 ->
    ( Char rem Length + Length ) rem Length.

shift_char( Char, Key ) when Char >= $a, Char =< $z ->
    $a + wrap( Char - $a + Key, 26 );
shift_char( Char, Key ) when Char >= $A, Char =< $Z ->
    $A + wrap( Char - $A + Key, 26 );
shift_char( Char, _ ) ->
    Char.

caesar( Message, Key ) when is_integer( Key ) ->
    lists:map( fun( C ) -> shift_char( C, Key ) end, Message ).

% calculate the frequencies of the letters in the message
freqs( Message ) ->
    freqs( string:lowercase( Message ), #{}, 0 ).
freqs( [ C | Rest ], Freqs, Count ) when C >= $a, C =< $z ->
    NewFreqs = maps:put( C, maps:get( C, Freqs, 0 ) + 1, Freqs ),
    freqs( Rest, NewFreqs, Count + 1 );
freqs( [ _ | Rest ], Freqs, Count ) ->
    freqs( Rest, Freqs, Count );
freqs( [], Freqs, Count ) ->
    maps:map( fun( _C, Freq ) -> 100 * Freq / Count end, Freqs ).

freq_array( FreqMap ) ->
    lists:map(
        fun( X ) -> maps:get( $a + X - 1, FreqMap, 0 ) end,
        lists:seq( 1, 26 )
    ).

shift_fun( F, Tau, Period ) ->
    { F1, F2 } = lists:split( wrap( Tau, Period ), F ),
    F2 ++ F1.

correlation( F, G ) when length( F ) == length ( G ) ->
    Period = length( F ),
    lists:map(
        fun( Tau ) ->
            Z = lists:zip( F, shift_fun( G, Tau, Period ) ),
            lists:foldl( fun( { X, Y }, Acc ) -> X * Y / Period + Acc end, 0, Z )
        end,
        lists:seq( 0, Period - 1 )
    ).

% determine the most likely keys by correlation with the frequencies in the English language
crack( Message ) ->
    crack( Message, 1 ).

crack( Message, Attempts ) ->
    Freqs = freqs( Message ),
    Correlation = correlation( freq_array( english_frequencies() ), freq_array( Freqs ) ),
    Points = lists:zip( lists:seq( 0, 25 ), Correlation ),
    BestKeys = lists:reverse( lists:ukeysort( 2, Points ) ),
    lists:sublist(
        lists:map( fun( { Key, C } ) -> { Key, C, caesar( Message, - Key ) } end, BestKeys ), Attempts
    ).

% frequencies of the letters in the English language, from
% http://pi.math.cornell.edu/~mec/2003-2004/cryptography/subs/frequencies.html
english_frequencies() ->
    _Frequencies = #{
        $e => 12.02, $t => 9.10, $a => 8.12, $o => 7.68, $i => 7.31, $n => 6.95, $s => 6.28,
        $r => 6.02, $h => 5.92, $d => 4.32, $l => 3.98, $u => 2.88, $c => 2.71, $m => 2.61,
        $f => 2.30, $y => 2.11, $w => 2.09, $g => 2.03, $p => 1.82, $b => 1.49, $v => 1.11,
        $k => 0.69, $x => 0.17, $q => 0.11, $j => 0.10, $z => 0.07
    }.

wrap_test_() -> [
        ?_assertEqual(  0, wrap( 0, 26 ) ),
        ?_assertEqual(  3, wrap( 3, 26 ) ),
        ?_assertEqual(  0, wrap( 26, 26 ) ),
        ?_assertEqual( 25, wrap( -1, 26 ) ),
        ?_assertEqual( 10, wrap( -42, 26 ) ),
        ?_assertEqual( 25, wrap( 77, 26 ) ),
        ?_assertEqual( 5, wrap( 15, 10 ) ),
        ?_assertEqual( 0, wrap( 7, 1 ) )
    ].

shift_char_test_() -> [
    ?_assertEqual( $b, shift_char( $a, 1 ) ),
    ?_assertEqual( $f, shift_char( $a, 5 ) ),
    ?_assertEqual( $a, shift_char( $z, 1 ) ),
    ?_assertEqual( $z, shift_char( $r, 8 ) ),
    ?_assertEqual( $b, shift_char( $r, 10 ) ),
    ?_assertEqual( $M, shift_char( $K, 2 ) ),
    ?_assertEqual( $D, shift_char( $W, 7 ) ),
    ?_assertEqual( $a, shift_char( $a, 0 ) ),
    ?_assertEqual( $a, shift_char( $b, -1 ) ),
    ?_assertEqual( $c, shift_char( $a, 28 ) ),
    ?_assertEqual( $r, shift_char( $c, -11 ) )
].

casear_test_() -> [
    ?_assertEqual( "b", caesar( "a", 1 ) ),
    ?_assertEqual( "e", caesar( "a", 4 ) ),
    ?_assertEqual( "a", caesar( "z", 1 ) ),
    ?_assertEqual( "efgfoe uif fbtu xbmm pg uif dbtumf", caesar( "defend the east wall of the castle", 1 ) ),
    ?_assertEqual( "dwwdfn iurp wkh zrrgv dw gdzq", caesar( "attack from the woods at dawn", 3 ) ),
    ?_assertEqual( "Dro aesmu, lbygx pyh TEWZON yfob dro vkji nyq!", caesar( "The quick, brown fox JUMPED over the lazy dog!", 10 ) ),
    ?_assertEqual( caesar( "defend the east wall of the castle", 15 ), caesar( "defend the east wall of the castle", -11 ) )
].

freqs_test_() -> [
    ?_assertEqual( #{ $a => 100.0 }, freqs( "a" ) ),
    ?_assertEqual( #{ $b => 50.0, $f => 50.0 }, freqs( "bf fb" ) ),
    ?_assertEqual( #{ $a => 50.0, $b => 30.0, $c => 20.0 }, freqs( "baac baba bacca cabba aa" ) )
].

freq_array_test_() -> [
    ?_assertEqual( 100.0, lists:nth( 1, freq_array( #{ $a => 100.0 } ) ) ),
    ?_assertEqual(  50.0, lists:nth( 2, freq_array( #{ $b => 50.0, $f => 50.0 } ) ) ),
    ?_assertEqual(  50.0, lists:nth( 6, freq_array( #{ $b => 50.0, $f => 50.0 } ) ) ),
    ?_assertEqual(  50.0, lists:nth( 1, freq_array( #{ $a => 50.0, $b => 30.0, $c => 20.0 } ) ) ),
    ?_assertEqual(  30.0, lists:nth( 2, freq_array( #{ $a => 50.0, $b => 30.0, $c => 20.0 } ) ) ),
    ?_assertEqual(  20.0, lists:nth( 3, freq_array( #{ $a => 50.0, $b => 30.0, $c => 20.0 } ) ) )
].

correlation_test_() ->
    F = [ 0, 0, 2, 0, 0 ],
    G = [ 0, 1, 2, 3, 0 ],
    H = [ 1, 2, 1, 4, 6 ],
    Appr = fun( L ) -> lists:map( fun( X ) -> round( X * 100 ) end, L ) end,
    [
        ?_assertEqual( Appr( [ 4/5, 0.0, 0.0, 0.0, 0.0 ] ), Appr( correlation( F, F ) ) ),
        ?_assertEqual( Appr( [ 4/5, 6/5, 0.0, 0.0, 2/5 ] ), Appr( correlation( F, G ) ) ),
        ?_assertEqual( Appr( [ 2/5, 8/5, 12/5, 2/5, 4/5 ] ), Appr( correlation( F, H ) ) ),
        ?_assertEqual( Appr( [ 4/5, 2/5, 0.0, 0.0, 6/5 ] ), Appr( correlation( G, F ) ) ),
        ?_assertEqual( Appr( [ 14/5, 8/5, 3/5, 3/5, 8/5 ] ), Appr( correlation( G, G ) ) ),
        ?_assertEqual( Appr( [ 16/5, 27/5, 19/5, 14/5, 8/5 ] ), Appr( correlation( G, H ) ) ),
        ?_assertEqual( Appr( [ 2/5, 4/5, 2/5, 12/5, 8/5 ] ), Appr( correlation( H, F ) ) ),
        ?_assertEqual( Appr( [ 16/5, 8/5, 14/5, 19/5, 27/5 ] ), Appr( correlation( H, G ) ) ),
        ?_assertEqual( Appr( [ 58/5, 38/5, 31/5, 31/5, 38/5 ] ), Appr( correlation( H, H ) ) )
    ].

To try:

% erl
1> c(caesar).
{ok,caesar}

2> caesar:crack("efgfoe uif fbtu xbmm pg uif dbtumf").                                                               [{1,27.732142857142858, "defend the east wall of the castle"}]

3> caesar:crack("dwwdfn iurp wkh zrrgv dw gdzq").
[{3,24.07692307692308,"attack from the woods at dawn"}]

4> caesar:crack("Dro aesmu, lbygx pyh TEWZON yfob dro vkji nyq!", 3).
[{10,19.342948717948715, "The quick, brown fox JUMPED over the lazy dog!"},
 {0,16.821581196581192, "Dro aesmu, lbygx pyh TEWZON yfob dro vkji nyq!"},
 {20,16.384615384615383, "Jxu gkysa, rhemd ven ZKCFUT eluh jxu bqpo tew!"}]
Collapse
 
hectorpascual profile image

Python :

def cypher(m, key):
    m_cypher = ''
    for i in m:
        if i != ' ' and i != '.':
            m_cypher += chr(ord(i) + key)
        else:
            m_cypher += i
    return m_cypher

One line :

cypher = lambda m, key : ''.join(chr(ord(i) + key) if i != ' ' and i != '.' else i for i in m)
Collapse
 
codeguppy profile image
Adrian

This is an interesting one. It can be easily solved if you implement first the modulo function in JavaScript.

I have an article about this:
dev.to/codeguppy/javascript-modulo...