Shifting Caesars Shifts

13

1

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!

racer290

Posted 2017-07-18T16:22:36.543

Reputation: 1 043

2Isn't E -3? – Leaky Nun – 2017-07-18T16:25:04.213

3What 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) – Mr. Xcoder – 2017-07-18T17:00:04.737

6Test 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? – Luis Mendo – 2017-07-18T17:08:54.303

Is it just me or is Hello, World! actually IBSLR, EGUFV!? – Magic Octopus Urn – 2017-07-18T17:16:26.590

@Mr.Xcoder I assumed it would wrap, as the ceaser cipher traditionally doesn't use numerals or symbols. – Magic Octopus Urn – 2017-07-18T17:29:52.950

Why did you vote to close? I will gladly adjust my challenge if it is a minor problem. – racer290 – 2017-07-18T17:56:52.623

@racer290 generally a challenge is VTCd if there is anything important unclear, and then once that is cleared up, it is reopened. Since answerers couldn't correctly answer until they heard a verdict on wrapping, they VTCd. Now that you've fixed it, it won't be closed. – Stephen – 2017-07-18T18:01:08.793

So to be clear, punctuation is skipped, so HELLO, WORLD and HELLOWORLD should have the same result (minus punctuation)? As Luis Mendo has asked, could we have a few more test cases? – Giuseppe – 2017-07-18T18:51:48.390

1@Giuseppe see the upvoted answers for test cases, they've been validated by OP as correct, I'd assume, or they'd have downvotes. – Magic Octopus Urn – 2017-07-18T19:15:17.043

2Did you mean for words like RELIEF and RELIES to both encipher to the same result SRSFAG? – Anders Kaseorg – 2017-07-18T19:50:59.837

Next time, you can use the sandbox to get feedback on your challenges before posting them.

– programmer5000 – 2017-07-18T21:13:12.540

@AndersKaseorg The cipher is such that plaintext isn't recoverable. In order to decypher the second character you need to know what the second character was (as the offset for it is the difference between the first character and the second). – Draco18s no longer trusts SE – 2017-07-19T14:21:36.050

@Draco18s it's actually only the modularity which makes this cipher unrecoverable. For each letter in the cipher text, there are only two ways to arrive there (either ' directly' or through modular wrapping), so there are only 2^n plain text candidates for each length n word in the cipher text, making this scheme a joke as both an attacker and the intended target need to spend the same amount of effort to decipher the code. – Sanchises – 2017-07-20T11:43:25.857

@Sanchisesabout Ah, yes, you are correct. :) – Draco18s no longer trusts SE – 2017-07-20T13:12:32.227

More examples please! You should at least add one verbose example with non-letters or provide more detailed instructions on how to handle them. Looking at some of the answers, I noticed that this seems unclear: A) They don´t get converted. B) They are removed. C) They are neither converted nor considered for the shifting. – Titus – 2018-05-01T17:15:58.030

Answers

5

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

Emigna

Posted 2017-07-18T16:22:36.543

Reputation: 50 798

We both get IBSLR, EGUFV! for Hello, World!, is that correct? Did OP just mess up that example? – Magic Octopus Urn – 2017-07-18T17:16:58.583

1@MagicOctopusUrn: His example in the beginning is just showing what a shift is. It just shifts by 1 letter so it's pretty misleading. – Emigna – 2017-07-18T17:18:05.633

4

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!

Lynn

Posted 2017-07-18T16:22:36.543

Reputation: 55 648

100 bytes – ovs – 2017-07-18T17:43:12.350

3

05AB1E, 32 30 29 bytes

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

Try it online!

Magic Octopus Urn

Posted 2017-07-18T16:22:36.543

Reputation: 19 422

1

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 :)

bytepusher

Posted 2017-07-18T16:22:36.543

Reputation: 149

This seems to have some problems dealing with offsets that wrap around the alphabet and with non-alphabetic characters. Try it online!

– Xcali – 2018-05-01T16:01:22.470

1

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

Jonas Wilms

Posted 2017-07-18T16:22:36.543

Reputation: 131

doesn´t skip non-letters, it seems? – Titus – 2018-05-01T17:18:47.500

1

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

Sanchises

Posted 2017-07-18T16:22:36.543

Reputation: 8 530

1

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!

Xcali

Posted 2017-07-18T16:22:36.543

Reputation: 7 671

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!. – Titus – 2018-05-01T17:08:27.730

You're right. Fixed it with 4 more bytes to preserve the previous letter. – Xcali – 2018-05-01T17:18:26.500

1

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.

Titus

Posted 2017-07-18T16:22:36.543

Reputation: 13 814

1

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!

DanielIndie

Posted 2017-07-18T16:22:36.543

Reputation: 1 220

1@Nit fixed it :) – DanielIndie – 2018-05-02T03:25:09.367