# 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!

Posted on by:

### dev.to staff

The hardworking team behind dev.to ❤️

### Discussion

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"  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. 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 }.  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},  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. 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; }  ### C# 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); } }  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')  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));  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)  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)


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!"}]


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(' ');


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

dev.to/codeguppy/javascript-modulo...

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)