Description

The Caesar cipher is a cipher, where every letter in the alphabet will be rotated by a secret number.
If the rotation is $$\7\$$, then a -> h, g -> n, v -> c and z -> g.

Today, we're playing Caesar's Cipher with ASCII chars, (0-127 inclusive). So, with rotation $$\7\$$, b -> i, Z -> a, x -> DEL (127),

But, even if the encoder shifted the chars around the ASCII table, you know, that the original string consisted of only the lowercase, uppercase alphabet, and space.

You're given a string of ASCII code points, your task is to print all of the possible original strings if the encoder only used the lowercase and the upper case alphabet (plus space) in the original string.

Rules

• The program must take the ASCII char codes of the encoded string as the input
• The encoded string is not constricted on lowercase, uppercase, and space, it can (because it's ASCII shifted) include ASCII chars
• The output must be all possible original strings (as a list, separated by newlines, etc.)
• Default loopholes apply
• Lowercase, uppercase, and space: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
• This is , so the shortest answer wins!

Examples

[In]: [91, 111, 112, 122, 39, 112, 122, 39, 104, 39, 123, 108, 122, 123, 39, 116, 108, 122, 122, 104, 110, 108]
[Out]: This is a test message

[In]: [43, 35, 49, 49, 31, 37, 35]
MESSAGE
NFTTBHF
OGUUCIG
PHVVDJH
QIWWEKI
RJXXFLJ
SKYYGMK
TLZZHNL
message
nfttbhf
oguucig
phvvdjh
qiwweki
rjxxflj
skyygmk
tlzzhnl

[In]: [103, 123, 120, 51, 97, 124, 1, 120, 7, 120, 120, 1, 7, 123, 51, 85, 12, 7, 120]
[Out]: The Nineteenth Byte
[In]: [78, 82, 78, 78]
[Out]:
NRNN
OSOO
PTPP
QUQQ
RVRR
SWSS
TXTT
UYUU
VZVV
aeaa
bfbb
cgcc
dhdd
eiee
fjff
gkgg
hlhh
imii
jnjj
kokk
lpll
mqmm
nrnn
osoo
ptpp
quqq
rvrr
swss
txtt
uyuu
vzvv
AEAA
BFBB
CGCC
DHDD
EIEE
FJFF
GKGG
HLHH
IMII
JNJJ
KOKK
LPLL
MQMM

• Suggests adding testcases with NULL bytes in it.
– tsh
Apr 1 at 9:09

Jelly, 16 bytes

+ⱮØ⁷%Ø⁷ỌfƑƇØẠ;⁶¤


A monadic Link that accepts a list of code-points and yields a list of lists of characters.

Try it online!

How?

+ⱮØ⁷%Ø⁷ỌfƑƇØẠ;⁶¤ - Link: list of code-points C
Ø⁷             - 128
Ɱ               - map (across v in [1..128]) with:
+                -   C add v (vectorises)
%Ø⁷          - modulo 128
Ọ         - convert to characters (list of potentials, P)
ØẠ    -   alphabetic characters
⁶  -   space character
;   -   concatenate (list of characters "A..Za..z ", A)
Ƈ      - filter keep those p in P for which:
Ƒ       -   is invariant under:
f        -     p filter keep only those in A


R, 82 bytes

\(s)for(i in 0:127)if(all((x=(s+i)%%128)%in%c(32,97:122,65:90)))show(intToUtf8(x))

Attempt This Online!

With the use of grepl instead of filtering char codes:

R, 84 bytes

\(s)for(i in 0:127)if(!grepl("[^ a-z]",v<-intToUtf8(x<-(s+i)%%128),T)&all(x))show(v)

Attempt This Online!

intToUtf8 has some weird behaviour with the input of 0 (it omits that char altogether) - the workaround here is using all to filter that out.

Perl 5-na, 45 bytes

y//\1-\x7f\0/c,/[^\pL ]/||say for(pack'C*',@F)x128


Try it online!

The program works as is, but replace \0, \1 and \x7f with their literal equivalents for the claimed score.

Very simple approach. 128 times, translate \0-\x7f to \1-\x7f\0, thus shifting each char by one, and check whether it meets the criteria.

Based on ophact's answer. (Sadly I don't have the reputation to comment this instead of duplicating it like this)

Python 3.8+, 97 bytes

lambda l:[b for i in range(128)if(b:=bytes(i+c&127for c in l))*all(c==32or 64<c&95<91for c in b)]


Try it online!

Since both A-Z and a-z are permitted, we can ignore the "case bit" 0x20 (c | 0x20 or c & 0x5f) to get a single range for the letters. There's no need to worry about the out of bounds 0x80.
As we can write 0x5b (b'Z'[0]+1) as 91, while 0x7b (b'z'[0]+1) could only be as short as 123 (and 0x20==32 is as expensive as 0x5f==95), we'll choose the uppercase letters' range.
bytes([1,2,3]) is, for all purposes I can think of "just read-only [1,2,3]" while already being something string-like we can return.

Python 3.8+, 105 bytes

If it has to output str instead for some reason, we need to add a .decode():

lambda l:[w.decode()for i in range(128)if(w:=bytes(c+i&127for c in l))*all(64<c&95<91or c==32for c in w)]


Try it online!

Is it required to be a lambda? Just the business part would be 86 bytes.

b for i in range(128)if(b:=bytes(i+c&127for c in l))*all(c==32or 64<c&95<91for c in b)


You would get the same output by giving the input in the variable l and writing something like

sys.stdout.buffer.write(b'\n'.join(
-paste here-
))

• Welcome to the site. The last two solutions there are what we call "snippets" and they are generally disallowed. You can leave them in though since you do have a valid answer. Apr 1 at 21:46
• @WheatWizard How could returning str be ok while returning bytes wouldn't? They are the same thing for ASCII in most encodings. Python 3 writes str to stdout by converting it to bytes first. Apr 3 at 17:24
• It probably is valid. I would guess, but I don't know. You can ask on meta if it is really a concern. Apr 3 at 20:13

JavaScript (Node.js), 85 bytes

a=>[...Array(128)].flatMap(_=>/[^a-z ]/i.test(s=Buffer(a=a.map(c=>c+1&127))+'')?[]:s)


Try it online!

• Why do you need global regex?
– l4m2
Mar 31 at 16:50
• @l4m2 Actually, I don't need to match the whole string either. :-p Mar 31 at 16:54

PHP 4 (123 chars)

The function f is accepting an encoded string.

function f($s){for($i=128;$i-=print chop($s,'A..Za..z ')?'':"$s\n";)for($j=strlen($s);$j--;)$s[$j]=chr(1+ord($s[$j])%128);}


05AB1E, 18 16 bytes

₅ÝIδ+žy%êçʒðKDáQ


Outputs as a list of list of characters.

Explanation:

₅Ý        # Push a list in the range [0,255]
# (255 is the lowest single-byte value above 127)
δ      # Map over this list of integers:
I +     #  Add the integer to each value in the input-list
žy%  # Modulo-128
ê # Sorted uniquify this list of lists
ç         # Convert each inner-most integer to a character with this codepoint
ʒ        # Filter this list of lists of characters by:
ðK      #  Remove all spaces
D     #  Duplicate it
á    #  Only keep all ASCII letters
Q   #  Check if both lists are the same
# (after which the filtered list is output implicitly)


Vyxal, 17 15 bytes

₇ƛ⁰+₇%C;U'kBṄ↔⁼


Try it Online!

-2 thanks to @lyxal

Explanation is for my old code:

₇ƛ⁰+₇%Cṅ;U'ðkB+↔= # This comment line needs some love
₇                 # 128, implicitly cast to 0..128
ƛ      ;         # Map to...
⁰+              # The input plus the offset
₇%            # Modulo 128
Cṅ          # Converted into a string
U        # Remove duplicates
'       # Filter by...
= # Is the original string equal to...
↔  # The string with the following chars removed
ðkB+   # " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"


• Try it Online! for 16 bytes. Apr 3 at 9:37
• Try it Online! for 15, outputting as a list of lists. Apr 3 at 11:12

Python 3.8 (pre-release), 117 bytes

lambda l:[''.join(map(chr,w))for i in range(128)if(w:=[c+i&127for c in l])*all(64<c<91or 96<c<123or c==32for c in w)]


Try it online!

Yep... that long.

Nothing much to say here; simply loops through the numbers from 0-127 and keeps the word found by shifting by that many units if all characters in such a word are part of the required alphabet.

• -7 Mar 31 at 23:54
• @loopywalt not sure I really understand that one, python bytes stuff is beyond my knowledge... but I could add it in soon when I'm at a computer Apr 1 at 5:06

BQN, 39 bytesSBCS

Returns a matrix with one possible original per row.

128⊸|{@+𝔽𝕩+⌜˜(∊/⊣)˝𝔽𝕩-˜⌜32∾⥊65‿97+⌜↕26}


Run online!

This computes the possible shifts for each character, takes the intersection, and shifts the input by all remaining numbers.
Generating all shifts and then only keeping alphabetic ones is 2 bytes longer:

@+·(∧´˘∊⟜(32∾⥊65‿97+⌜↕26))⊸/128|(↕128)+⌜⊢


Run online!

JavaScript (Node.js), 78 bytes

x=>Buffer(128).map(_=>/[^ a-z]/i.test(x=x.map(y=>y+1&127))||console.log(''+x))


Try it online!

JavaScript (Node.js), 83 bytes

a=>[...Array(128)].flatMap(_=>/[^a-z ]/i.test(a=Buffer(a.map(c=>c+1&127)))?[]:a+'')


Try it online!

Handleing part already near Arnauld enough, so I place it here

• You can't assume that the console function is input Mar 31 at 17:23
• @ophact I remember so but it turns out that it's not well agreed
– l4m2
Mar 31 at 19:00

Charcoal, 24 bytes

ΦＥ¹²⁸⭆θ℅﹪⁺ιλ¹²⁸⬤ι№⁺⁺αβ λ


Try it online! Link is to verbose version of code. Explanation:

  ¹²⁸                       Literal integer 128
Ｅ                          Map over implicit range
θ                     Input array
⭆                      Map over elements and join
ι                 Outer value
⁺                  Plus
λ                Inner value
﹪                   Modulo
¹²⁸             Literal integer 128
℅                    Convert from ASCII code
Φ                           Filtered where
ι           Current string
⬤            All characters satisfy
№          (Non-zero) Count of
λ    Current character in
α       Uppercase letters
⁺        Concatenated with
β      Lowercase letters
⁺         Concatenated with space
Implicitly print each solution on its own line


Perl 5, 63 bytes

sub{grep!/[^a-z ]/i,map{//;join'',map chr($_+$'&127),@_}0..127}


Try it online!

Factor, 84 bytes

[ 128 iota [ '[ _ + 128 mod ] ""map-as ] with [ R/ [a-z ]+/i matches? ] map-filter ]


Try it online!

APL+WIN, 74 bytes

Prompts for code points

⎕av[((⍴n)=+/m∊(i,53)⍴32,∊(⊂65 97)+¨⍳26)⌿m←i|((i,⍴n)⍴n)+⍉((⍴n←⎕),i)⍴⍳i←128]


Unfortunately Dyalog APL Classic's atomic vector does not line up with ASCII code points whereas APL+WIN does so I cannot demonstrate via TIO as I would do normally.

Behaviour, 78 bytes

u=&(127*&(f=a;s=c*&(f+a)%127~(s<~&[a==32(a>64a<91)(a>96a<123)])@s*ascii>&a+b))


test with:

@u:c={43 35 49 49 31 37 35}
@u:c={91 111 112 122 39 112 122 39 104 39 123 108 122 123 39 116 108 122 122 104 110 108}


ungolfed and commented:

uncypher = &(
127*&(  // do the following sequence 127 times
off = a  // get offset from iterator
s = c*&(off+a)%128  // rotate each character from /c by the offset
~(s<~&[  // stop sequence if the previous result has any invalid character
(a==32)
(a>64 a<91)
(a>96 a<123)
])
@(s*ascii)>&a+b // convert ascii num to char, reduce to string and print
)
)


C (gcc), 159 136 bytes

• -23 bytes thanks to ceilingcat

As NUL is a valid character, also takes the counted string length. The function first iterates through each shifted string to verify that all the decoded characters are valid (and bails early if not), then prints the shifted string if it is still valid.

#define v;for(r=0;a&&r<l;r++
a,h,r,c;f(s,l)char*s;{for(h=128;a=h--;a&&puts("")){v)a=isalpha(c=s[r]+h&127)|c==32 v)putchar(s[r]+h&127);}}


Try it online!