13
\$\begingroup\$

Description

A Caesar Shift is a very simple monoalphabetic cipher where each letter gets replaced by the one after it in the alphabet. Example:

Hello world! -> IFMMP XPSME!

(IBSLR, EGUFV! is the output for the actual challenge, this was an example of shifting by 1.)

As you can see, spacing and punctuation remain unattuned. However, to prevent guessing the message, all letters are capitalized. By shifting the letters back, the message was deciphered, convenient, but also really easy to decipher by other persons who are supposed not to know what the message means.

So, we'll be helping Caesar a bit by using an advanced form of his cipher: the Self-shifting Caesar Shift!

Challenge

Your task is to write a program or function, that, given a string to encipher, outputs the encrypted string corresponding to the input. The advanced Caesar Shift works like this:

1. Compute letter differences of all adjacent letters: 
    1.1. Letter difference is computed like this:

         Position of 2nd letter in the alphabet
        -Position of 1st letter in the alphabet
        =======================================
                              Letter difference

    1.2. Example input: Hello
         H - e|e -  l|l  -  l|l  -  o
         7 - 5|5 - 12|12 - 12|12 - 15 Letter differences: 3; -7; 0; -3
            =3|   =-7|     =0|    =-3

2. Assign the letters continously a letter difference from the list,
   starting at the second letter and inverting the differences:
    2.1. 2nd letter: first difference, 3rd letter: second difference, etc.

    2.2. The first letter is assigned a 1.

    2.3. Example input: Hello with differences 3; -7; 0; -3

         Letter || Value
         =======||======
            H   ||   1
            E   ||  -3
            L   ||   7
            L   ||   0
            O   ||   3

3. Shift the letters by the value x they have been assigned:
    3.1. In case of a positive x, the letter is shifted x letters to the right.
    3.2. In case of a negative x, the letter is shifted |x| letters to the left.
    3.3. In case of x = 0, the letter is not shifted.

    3.4. If the shift would surpass the limits of the alphabet, it gets wrapped around
         Example: Y + Shift of 2 --> A

    3.5. Example input: See the table under 2.3.

                ||       || Shifted
         Letter || Value || Letter
         =======||=======||=========
            H   ||   1   ||    I
            E   ||  -3   ||    B     Program output:
            L   ||   7   ||    S     IBSLR
            L   ||   0   ||    L
            O   ||   3   ||    R

Spaces and other special symbols, such as punctuation are skipped in this process. It is guaranteed that your program will be given a string containing only printable ASCII characters. The output of your function/program must only be in upper case.

This is , so standard loopholes apply, and may the shortest answer in bytes win!

\$\endgroup\$
  • 2
    \$\begingroup\$ Isn't E -3? \$\endgroup\$ – Leaky Nun Jul 18 '17 at 16:25
  • 3
    \$\begingroup\$ What if the letter difference brings the letter out of the alphabet? Like ZEN, for example. Z shifted by 1 is... A? (as a side note, the 05AB1E answer turns Z into A) \$\endgroup\$ – Mr. Xcoder Jul 18 '17 at 17:00
  • 6
    \$\begingroup\$ Test cases please. Also, which characters are skipped exactly? And what does it mean for them to be skipped? Are they removed altogether, or must they remain in the output? \$\endgroup\$ – Luis Mendo Jul 18 '17 at 17:08
  • 1
    \$\begingroup\$ @Giuseppe see the upvoted answers for test cases, they've been validated by OP as correct, I'd assume, or they'd have downvotes. \$\endgroup\$ – Magic Octopus Urn Jul 18 '17 at 19:15
  • 2
    \$\begingroup\$ Did you mean for words like RELIEF and RELIES to both encipher to the same result SRSFAG? \$\endgroup\$ – Anders Kaseorg Jul 18 '17 at 19:50
5
\$\begingroup\$

05AB1E, 28 27 24 bytes

láÇ¥R`XIlvyaiAyk+Aèëy}u?

Try it online!

Explanation

l                          # convert input to lower case
 á                         # keep only letters
  ǥ                       # compute deltas of character codes
    R`                     # reverse and push separated to stack
      X                    # push 1
       Ilv                 # for each char y in lower case input
          yai              # if y is a letter
             Ayk           # get the index of y in the alphabet
                +          # add the next delta
                 Aè        # index into the alphabet with this
            ëy             # else push y
              }            # end if
            u?             # print as upper case
\$\endgroup\$
  • \$\begingroup\$ We both get IBSLR, EGUFV! for Hello, World!, is that correct? Did OP just mess up that example? \$\endgroup\$ – Magic Octopus Urn Jul 18 '17 at 17:16
  • 1
    \$\begingroup\$ @MagicOctopusUrn: His example in the beginning is just showing what a shift is. It just shifts by 1 letter so it's pretty misleading. \$\endgroup\$ – Emigna Jul 18 '17 at 17:18
4
\$\begingroup\$

Python 3, 100 bytes

b=0
for c in map(ord,input().upper()):
 if 64<c<91:b,c=c,(c+c-(b or~-c)-65)%26+65
 print(end=chr(c))

Try it online!

b keeps track of the last letter’s ASCII code, or is initially zero; the formula c+c-(b or~-x) means a letter with ASCII code c gets shifted by c-b if b is non-zero, and c-(c-1) == +1 if b is zero (for the very first letter).

b will never become zero again, as the string is guaranteed to consist of printable ASCII characters.

Finally, 64<c<91 checks if c is an uppercase ASCII letter, and (…-65)%26+65 wraps everything back into the A-Z range.

ovs saved a byte. Thanks!

\$\endgroup\$
  • \$\begingroup\$ 100 bytes \$\endgroup\$ – ovs Jul 18 '17 at 17:43
3
\$\begingroup\$

05AB1E, 32 30 29 bytes

lDáÇ¥1¸ìUvyaiAX¾èAyk+sè¼ëy}Ju

Try it online!

\$\endgroup\$
1
\$\begingroup\$

ES6 ( Javascript ) , 138 bytes :

s=>((s,a,f)=>((r=i=>s[i]&&(a[i]=String.fromCharCode((2*s[f](i)-(s[f](i-1)||71)-38)%26+64),r(i+1)))(0),a))(s.toUpperCase(),[],"charCodeAt")

http://jsbin.com/manurenasa/edit?console

\$\endgroup\$
  • \$\begingroup\$ doesn´t skip non-letters, it seems? \$\endgroup\$ – Titus May 1 '18 at 17:18
1
\$\begingroup\$

MATL, 27 bytes

tXkt1Y2mXH)tlwdh+64-lY2w)H(

Try it online!

I think this is the shortest I can get, but there are lots of different varieties since there is a lot of re-use of 'variables' (there are 3 t (duplication), and 2 w (swap) operations, clipboard H is used, and even then there is still a duplicate 1Y2...). Sadly, I couldn't save bytes with the automatic M clipboard.

Over half of the program is dedicated to making it uppercase and ignoring non-alphabetic characters - just the cipher is no more than 13 bytes (Try it online!)

\$\endgroup\$
1
\$\begingroup\$

Perl, 9089

Though non-codegolf languages are rarely competitive, we can go below 100 ;)

@a=split//,<>;say uc(++$a[0]).join'',map{uc chr(2*ord($a[$_+1])-ord($a[$_])+!$_)}0..$#a-1

I've decided to ungolf this:

@a = split//,<>; Takes input from STDIN, stores character list (with newline!) in @a.

say uc(++$a[0]) output uppercase first letter shifted by 1. Turns out you can increment a letter in perl if you use a prefix ++. This is a mutator ofc.

2*ord($a[$_+1])-ord($a[$_])+!$_ We are asked to take character at x, and add the difference + (x-(x-1)). Well that's 2x - (x-1). However: I changed the first letter! So I've got to correct that error, hence +!$_, which will correct for having subtracted one too many at position 0 (only case !$_ is not undef ). We then uc chr to get an uppercase letter from the calculated ASCII value.

map{ ... } $#a-2 - $#a is the position to access the last array element. Since I'm adding one I want $#a-1, but because the newline from input needs ignoring, this is $#a-2.

This is concatenated with the first letter, and we are done :)

\$\endgroup\$
  • \$\begingroup\$ This seems to have some problems dealing with offsets that wrap around the alphabet and with non-alphabetic characters. Try it online! \$\endgroup\$ – Xcali May 1 '18 at 16:01
1
\$\begingroup\$

Perl 5 -F, 73 77 74 bytes

/\w/&&($_=chr 65+(2*($n=ord uc)-65-($!||-1+ord uc))%26)&($!=$n)for@F;say@F

Try it online!

\$\endgroup\$
  • \$\begingroup\$ This doesn´t totally skip non-letters; it just doesn´t convert them. I think Hello, World! should result in IBSLR, EGUFV!, not IBSLR, XGUFV!. \$\endgroup\$ – Titus May 1 '18 at 17:08
  • \$\begingroup\$ You're right. Fixed it with 4 more bytes to preserve the previous letter. \$\endgroup\$ – Xcali May 1 '18 at 17:18
1
\$\begingroup\$

PHP, 106 98 bytes

pretty nasty that one ... if just base_convert wasn´t that long (or ctype_alpha) ...
but I got it under 100. satisfied.

for(;$a=ord($c=$argn[$i++]);print ctype_alpha($c)?chr(65+($p?(25-$p+2*$p=$a)%26:$p=$a)):$c)$a&=31;

Run as pipe with -nR or try it online.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (Node.js), 86 83 87 81 bytes

s=>s.replace(/[a-z]/gi,(x,i)=>Buffer(x).map(x=>((i?x%32-l:1)+26+(l=x%32))%26+64))

Try it online!

\$\endgroup\$
  • 1
    \$\begingroup\$ @Nit fixed it :) \$\endgroup\$ – DanielIndie May 2 '18 at 3:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.