# Implement a Caesar Cipher with a Digit Shift

I assume everyone here is familiar with the Caesar Cipher that shifts letters to a certain index. https://en.wikipedia.org/wiki/Caesar_cipher for those who still needs introduction.

But, we can all agree that plain Caesar is unfun and way too easy. So here's a version that also shifts characters around. This version first applies a cyclic position shift of a given amount to the string, for each character in the string separated with punctuations (space counts as character in this context, positive index means to the right).

For example, Hello world with shift +3 becomes rldHello wo.
Hello! World becomes lloHe!rld Wo with the same shift however because the ! separated the string.

After that, your program applies the Caesar cipher to the shifted text, and outputs the result. (easy enough)
The first example becomes uogKhoor zr after the Caesar cipher of +3.
The second example becomes iilEb!oia Tl after a Caesar shift of -3 (or +23 in alphabets)

As seen above, the index of the two shifts may or may not be equal.

Write a encode and decode program or function (or whatever your language of choice calls them), they may or may not be the same one. It must implement the aforementioned rules of double shifting, and the decode function must successfully reverse it.

Conditions

Your program must at the very least function on ASCII characters, ASCII 0-9 (48-57, inclusive), A to Z (65-90), a to z (97-122) and Space (32) counts as characters in this context, with every other ASCII character counting as punctuation and breaking up the string.

Bonus points to those whose program functions on your language of choice's codebase. Since non-ASCII language contain special characters the "punctuation" rule is omitted in this use case. You only need to implement the cyclic shift and then the Caesar shift on your language's codebase.
¡¢£¤ in Jelly becomes ©¤¥¦ with a position shift of +1 and Caesar shift of -253.

You may assume:

The index input are integers. It may be negative or zero.
If only one number is provided, encode both shifts with that number.
If no number is provided, perform whatever default shifts your heart desire (except 0 and 0, that is boring).
Edit: Due to overwhelming demand, you may assume that there will be at least one number provided. Code that has defaults would still be preferred but no extra scoring.
The input string is in ASCII. Or in your language's codebase if you decide to input that to your program.

Scoring

Of course programs that successfully include their codebase will be prioritized. Include the shifted version of your program in your answer to reflect this.

The other criteria is lowest byte wins. This counts both the encoder and decoder. List the sum and respective length separately please.

Test cases

+3 -5 "Hello,world" becomes "ggjCz,mgyrj"
+10 "Implement a Caesar Cipher with a Digit Shift" becomes "sqsd Crspd Swzvowoxd k Mkockb mszrob gsdr k N"
-2 -11 "Caesar cipher, also known as Caesar's cipher, the shift cipher, Caesar's code or Caesar shift."
becomes "thpg rxewtgRp,ahd zcdlc ph Rpthpg p'rxewtgh ,wt hwxui rxewtg i,pthpg'h rdst dg Rpthpg hwxui R."


PS. Of course I am aware that the two shifts can be carried out in any order and produce the same result, it is just my way of visualizing things. Plus, the first input means position shift index.

PPS. If the test cases are wrong I apologize, I am not a good coder.

• The punctuation also breaks up for the cycles? Or should I cycle the WHOLE string and then only encode the characters you mentioned? – RGS Feb 7 at 14:24
• @RGS Yes the punctuation always break the ASCII string, this for obvious reasons does not apply to your language's codebase. – Jack Asimov Feb 7 at 14:25
• "If no number is provided, perform whatever default shifts your heart desire (except 0 and 0, that is boring)." Is this mandatory? I have a solution which I was about to post, but adding this requirement would make both the encoder and decoder almost twice as large.. My current solution does work for both 1 and/or 2 shift-integers, though. PS: I would advice to let answers assume we always get three inputs (1 string and 2 integers). – Kevin Cruijssen Feb 7 at 14:30
• What should happen to values which push above Z in ASCII, should they map down to A or map up in the ASCII range? Also, why do spaces get special treatment in the rotation but not in the transformation? Is it ok to transform both? – DeathIncarnate Feb 7 at 17:10
• "If only one number is provided, encode both shifts with that number." - Oh this invalidates my answer, and the other Jelly answer. This is a very unusual request and seems to be unrelated to the core of the challenge. – Jonathan Allan Feb 7 at 23:49

# 05AB1E, 23 (12+11) bytes

Uses the 05AB1E encoding for the Caesar-shifting.

Encoder:

žĆDII(._kI+è


Input order as string, shift, (optional Caesar-shift). I/O of the strings as character-list.

Decoder:

žĆDIkI-èI._


Input order as string, (optional Caesar-shift), shift. I/O of the strings as character-list.

Explanation:

              # ENCODER:
žĆ            # Push the 05AB1E codepage
D           # Duplicate it
I          # Push the first character-list input
I         # Push the second shift integer
(        # Negate it
._      # Shift the character-list that many times towards the left
k     # Get the index of each character in the duplicated codepage
I    # Push the second shift input again or the optional third Caesar-shift input
+   # Add it to each index
è  # And index it into the 05AB1E codepage
# (after which the resulting character-list is output implicitly as result)

# DECODER:
žĆ            # Push the 05AB1E codepage
D           # Duplicate it
I          # Push the first character-list input
k         # Get the index of each in the duplicated codepage
I        # Push the optional Caesar-shift or shift integer
-       # Subtract it from each index
è      # Index it into the 05AB1E codepage
I     # Push the second/third shift integer
._   # And rotate the character-list that many times towards the left
# (after which the resulting character-list is output implicitly as result)


# Jelly, (8 + 11 = 19?) 11 + 14 = 25 bytes

Uses the code-page, no punctuation option

19 + 6 = 25 bytes handling the only 1 number option by taking a list of numbers as one of the two arguments.

Encoder +: (Shift(s), plaintext)

0ịØJ,ṙ¥yṙ⁸Ḣ


Decoder +: (Shift(s), cyphertext)

0ịNØJ,ṙ¥yṙ⁸N¤Ḣ


These Links build on the below programs:

Encoder: (Caesar shift, plaintext, Cyclic shift)

ØJ,ṙ¥yṙ⁵


Decoder: (Caesar shift, cyphertext, Cyclic shift)

NØJ,ṙ¥yṙ⁵N¤


# How?

### Encoder

ØJ,ṙ¥yṙ⁵ - Main Link: Caesar shift, plaintext
ØJ       - code-page characters
¥    - last two links as a dyad:
ṙ     -   rotate (code-page) left by (Caesar shift)
,      -   (code-page) pair with (that)
y   - (plaintext) translated using (that mapping)
⁵ - get program's 3rd argument (Cyclic shift)
ṙ  - rotate (translated) left by (Cyclic shift)
- implicit print


### Decoder

NØJ,ṙ¥yṙ⁵N¤ - Main Link: Caesar shift, cyphertext
N           - (Caesar shift) negated
ØJ         - code-page characters
¥      - last two links as a dyad:
ṙ       -   rotate (code-page) left by (negated Caesar shift)
,        -   pair
y     - (cyphertext) translated using (that mapping)
⁵   -   get program's 3rd argument (Cyclic shift)
N  -   negated
ṙ    - rotate (translated) left by (negated Cyclic shift)
- implicit print

• Really nice use of the y link here. More for for my brain! +1 – RGS Feb 7 at 23:42
• Remember, it is not the spoon that bends, it is yourself :p – Jonathan Allan Feb 7 at 23:44
• sorry for asking, but are you being serious or just joking? Sometimes sarcasm flies over my head xD – RGS Feb 8 at 19:59
• @RGS just joking around. Reference to The Matrix. – Jonathan Allan Feb 8 at 20:07
• ah I see! Also, well played on handling the cases where no third argument is given. – RGS Feb 8 at 20:09

# Jelly, 25 23 bytes (10 + 13)

ØJiⱮṙ+⁵ịØJ
ØJiⱮṙN}¥_⁵ịØJ


Encoder:

   Ɱ          For each character on the input string
i           get its index on
ØJ            the Jelly code page.
ṙ         Then rotate the list we get by as much as the cyclic shift input
+⁵       and add to each number as much as the Caesar shift input.
ị      Finally use those numbers to index
ØJ    on Jelly's code page again.


Decoder:

   Ɱ             For each character on the input string
i              get its index on
ØJ               the Jelly code page.
ṙ            Then rotate the list of indices
N}¥         by the symmetric of the cyclic shift input.
_⁵       Then subtract from each number as much as the Caesar shift input.
ị      Finally use those numbers to index
ØJ    on Jelly's code page again.


@Jonathan saved me 2 bytes! Kudos to him :D

Inputs are the string to encode, the shift corresponding to the "rotation" of the string and the shift corresponding to the Caesar encoding. I am encoding the whole Jelly codepage.

You can try the encoder or the decoder. You can also check the encoder and decoder are the inverse of one another (swap the order of the encoder/decoder to verify they are the inverses on both sides).

• I am trying to write a footer to compose both links to show that their composition is the Identity function, but I am failing to write such a footer. Can someone give me a hint in the right direction? I think I am struggling with the fact that the two links I wrote have 3 arguments and the quicks I find are for links up to dyads... – RGS Feb 7 at 15:24
• Maybe place the third argument in the register like this? – Jonathan Allan Feb 7 at 20:36
• Encoder can be simplified to ØJiⱮṙ+⁵ịØJ – Jonathan Allan Feb 7 at 20:43
• @JonathanAllan thanks! Can we chat for a couple of minutes? I don't fully understand the footer you used. – RGS Feb 7 at 20:45
• Our answers fail to handle the odd rule "If only one number is provided, encode both shifts with that number." :( – Jonathan Allan Feb 7 at 23:53

# PHP, 398 bytes

function e($z,$x,$y=null){$y=is_null($y)?$x:$y;for($i=0,$o=preg_split('/([^A-Za-z0-9 ])/',$z,0,2);$i<count($o);$o[$i]=$s,$i+=2)for($s=substr(($s=$o[$i]).$s.$s,($l=strlen($s))-$x,$l),$j=0;$j<strlen($s);$j++)if(($c=$s[$j])!=' ')$s[$j]=(chr(ctype_digit($c)?(ord($c)+$y-38)%10+48:(ord($c)+$y-($p=ctype_lower($c)?97:65)+26)%26+$p));return join($o);}function d($z,$x,$y=null){return e($z,-$x,-($y?:$x));}  Try it online! Submitted as two functions: • Encode: e($string, $index_shift, [$caesar_shift ] ) : string
• Decode: d( $string,$index_shift, [ $caesar_shift ] ) : string Yes, this is not very golfed yet, but I thought it would be helpful to post to verify my assumptions about this challenge, while waiting for answers to my questions to the OP above. Still unclear to me is: 1. Case of inputs +1 "Z" == "A", +1 "z" == "a", +1 "9" == "0"? 2. Only UTF-8 / ASCII languages are required to implement the "punctuation delimiter" rule? 3. Verify output with posted test cases, which as I've pointed out, I believe the second two are incorrect. • Only downsides I see to your code is that it's not precised in the question which index number can be missing, but in your case the first one cannot be missing. It's also precised for those numbers "It may be negative or zero", so zero should be a valid input, a different case than missing (but I actually made the same mistake too) – Kaddath Feb 11 at 9:35 • I've updated the solution to account for this, as I'm honestly not concerned about byte counts until some of open questions are answered. – 640KB Feb 11 at 14:10 • I see what you are saying -- in the example e( 'input', 1, 0 ) would imply e( 'input, 1, 1 ) the way it is now. That said, as others have pointed out, having "optional" or "default" parameters is discouraged in challenges , and I'd suggest to the OP that this be more clearly changed to confirm to Default I/O methods for challenges – 640KB Feb 11 at 14:16 • updated my code, maybe you can save some bytes with default values as empty strings, it should work either if argument is empty or missing (calling with null will use default value) – Kaddath Feb 11 at 17:21 First version, a bit verbose yet, I'll try to optimize: • "fully cyclic caesar" (9+1 becomes 0, z+1 becomes a, Z+1 becomes A) • breaks on anything not a space or char (a-z A-Z 0-1) (I think your last example is wrong, it breaks on the last ', which is intended) • default values if missing: 1 / 1 (same value for both, could be up to 9 without extra byte) • originally planned to use PHP's char increment, convenient way to loop over alpha values ($c='Z';$c++; makes 'AA'), but decrement is not implemented (so no negative values or decoding)! • should be prioritized IMO because it supports its own codebase, UTF-8 ;) ENCODER + DECODER = TOTAL # PHP, 430 + 71 = 501 bytes function a($a,$b='',$c=''){$b!==''?:$b=$c;$c!==''?:$c=$b;$t="/([^a-zA-Z0-9 ]+)/";preg_match_all($t,$a,$s);$r=preg_split($t,$a);for($i=0;null!==$d=$r[$i];$i++){if($j=strlen($d)){$n=-$b%$j;$d=substr($d,$n).substr($d,0,$n);for(;$j--;){if(' '!=$e=$d[$j]){$n=($t=ctype_digit($e))?10:26;$f=$c%$n+$e=ord($e);$d[$j]=chr($t&$f>57|$f>122|$e<91&$f>90?$f-$n:($t&$f<48|!$t&$f<65|$e>96&$f<97?$f+$n:$f));}}}$r[$i]=$d.$s[1][$i];}return join($r);}function b($a,$b='',$c=''){return a($a,$b!==''?-$b:'',$c!==''?-$c:'');}


Try it online!

BONUS - Encoder with line breaks and comments: (not updated)

$a=$argv;                     //arguments short alias
$b=$a[2]?$a[2]:$a[3];         //argument 1 defaults to 2 if missing
$c=$a[3]?$a[3]:($b?$b:$b=1);  //argument 2 defaults to 1 or both default to "1"
$t="/([^a-zA-Z0-9 ]+)/"; //breaking on anything not alpha or digit or space preg_match_all($t,$a[1],$s);  //saving breaking chars for later
$r=preg_split($t,$a[1]); //splitting at breaks for($i=0;null!==$d=$r[$i];){ //for any non null split if($j=strlen($d)){ //if it has a length$n=-$b%$j;                         //modulo for shifting
$d=substr($d,$n).substr($d,0,$n); //the shifting for(;$j--;){                       //for every char
if(' '!=$e=$d[$j]){ //which is not a space$n=($t=ctype_digit($e))?10:26; //char code range depending if digit or alpha
$f=$c%$n+$e=ord($e); //new char code and save current one$d[$j]=chr($t&$f>57|$f>122|$e<91&$f>90?$f-$n:($t&$f<48|!$t&$f<65|$e>96&$f<97?$f+$n:$f)); //applying char code$f or using range $n to calculate if out } } } echo$d.$s[1][$i++];       //display current split with breaking chars
}


BONUS 2 - shifted version of the program with -2 / -11:

crixdc puj($p,$q='',$r=''){$q!==''?:$q=$r;$r!==''?:$r=$q;$i="/([^p-oP-O9-8 ]+)/";tveg_irwbp_apa($i,$p,$h);$g=tveg_axihe($i,$p);gud($x=9;aacj!==$s=$g[$x];$x++){xu($y=gatchi($s)){$c=-$q%$y;$s=qhighj($s,$c).qhighj($s,9,$c);gud(;$y--;){xu(' '!=$t=$s[$y]){$c=($i=netri_vxisx($t))?09:15;$u=$r%$c+$t=sdg($t);$s[$y]=grw($i&$u>46|$u>101|$t<80&$u>89?$u-$c:($i&$u<37|!$i&$u<54|$t>85&$u<86?$u+$c:$u));}}}$g[$x]=$s.$h[0][$x];}ijgc ydxcgt($g);}crixdc quj($p,$q='',$r=''){ijgc pgt($p,$q!==''?-$q:'',$r!==''?-$r:'');}  EDIT: saved 10 bytes by refactoring to remove brackets + replaced ''!== by ''!= + replaced logical operators ||/&& by bitwise |/& EDIT2: saved 1 byte by using $l=strlen( in for declaration instead of ''!= and reusing it after, but +4 bytes correcting a bug for $f<65 picking numbers, changed to !$t&$f<65 EDIT3: fixed bug of prematurely breaking first loop when string begins by a punctuation, forces new if to prevent division by zero, but uses split length $j to shorten second loop declaration

EDIT4: improved with a function and advices from 640KB but didn't want to go too far while questions not resolved and didn't want to end with same code - gave up default values when both arguments miss, supports now zero shifting

• Is your total score 830 then? (the sum of both encode and decode) – 640KB Feb 10 at 22:58
• hint: isn't the decode function just the encode function with the opposite signs of the two shift values? (ex: function d($a,$b,$c) { return e(-$a,-$b,$c); } (if that saves you any bytes :) ) – 640KB Feb 10 at 22:59
• Nice job you did using parameters for preg_split! Many valid points, but it's gonna be hard not end up with your code if I use all this ;) I also implemented default values although it doesn't add to the score.. maybe a bit dumb – Kaddath Feb 11 at 9:18