16
\$\begingroup\$

Introduction:

I have loads of different ciphers stored in a document I once compiled as a kid, I picked a few of the ones I thought were best suitable for challenges (not too trivial, and not too hard) and transformed them into challenges. Most of them are still in the sandbox, and I'm not sure yet whether I'll post all of them, or only a few. But here is the first of them to start things of.


A Computer Cipher will encipher the given text into 'random' character groups of a given length. If such a group contains a digit, it will use that digit to index into its own group for the enciphered character. If no digit is present in the group, it means the first character is used.

For example, let's say we want to encipher the text this is a computer cipher with a given length of 5. This is a potential output (note: numbers are 1-indexed in the example below):

t     h     i     s     i     s     a     c     o     m     p     u     t     e     r     c     i     p     h     e     r       (without spaces of course, but added as clarification)
qu5dt hprit k3iqb osyw2 jii2o m5uzs akiwb hwpc4 eoo3j muxer z4lpc 4lsuw 2tsmp eirkr r3rsi b5nvc vid2o dmh5p hrptj oeh2l 4ngrv   (without spaces of course, but added as clarification)

Let's take a few groups as examples to explain how to decipher the group:

  • qu5dt: This group contains a digit 5, so the (1-indexed) 5th character of this group is the character used for the deciphered text: t.
  • hprit: This group contains no digits, so the first character of this group is used implicitly for the deciphered text: h.
  • osyw2: This groups contains a digit 2, so the (1-indexed) 2nd character of this group is the character used for the deciphered text: s.

Challenge:

Given an integer length and string word_to_encipher, output a random enciphered string as described above.

You only have to encipher given the length and word_to_encipher, so no need to create a deciphering program/function as well. I might make a part 2 challenge for the deciphering in the future however.

Challenge rules:

  • You can assume the length will be in the range [3,9].
  • You can assume the word_to_encipher will only contain letters.
  • You can use either full lowercase or full uppercase (please state which one you've used in your answer).
  • Your outputs, every group, and the positions of the digits in a group (if present) should be uniformly random. So all random letters of the alphabet have the same chance of occurring; the position of the enciphered letter in each group has the same chance of occurring; and the position of the digit has the same chance of occurring (except when it's the first character and no digit is present; and it obviously cannot be on the same position as the enciphered character).
  • You are also allowed to use 0-indexed digits instead of 1-indexed. Please state which of the two you've used in your answer.
  • The digit 1 (or 0 when 0-indexed) will never be present in the output. So b1ndh is not a valid group to encipher the character 'b'. However, b4tbw is valid, where the 4 enciphers the b at the 4th (1-indexed) position, and the other characters b,t,w are random (which coincidentally also contains a b). Other possible valid groups of length 5 to encipher the character 'b' are: abcd2, ab2de, babbk, hue5b, etc.

General rules:

  • This is , so shortest answer in bytes wins.
    Don't let code-golf languages discourage you from posting answers with non-codegolfing languages. Try to come up with an as short as possible answer for 'any' programming language.
  • Standard rules apply for your answer with default I/O rules, so you are allowed to use STDIN/STDOUT, functions/method with the proper parameters and return-type, full programs. Your call.
  • Default Loopholes are forbidden.
  • If possible, please add a link with a test for your code (i.e. TIO).
  • Also, adding an explanation for your answer is highly recommended.

Test cases:

Input:
 Length:           5
 Word to encipher: thisisacomputercipher
Possible output:
 qu5dthpritk3iqbosyw2jii2om5uzsakiwbhwpc4eoo3jmuxerz4lpc4lsuw2tsmpeirkrr3rsib5nvcvid2odmh5phrptjoeh2l4ngrv

Input:
 Length:           8
 Word to encipher: test
Possible output:
 ewetng4o6smptebyo6ontsrbtxten3qk

Input:
 Length:           3
 Word to encipher: three
Possible output:
 tomv3h2rvege3le
\$\endgroup\$
11
  • 2
    \$\begingroup\$ How does "uniform" mean \$\endgroup\$
    – l4m2
    Commented Dec 3, 2018 at 9:32
  • 1
    \$\begingroup\$ @l4m2 That there is an equal chance for any output. So all random letters of the alphabet have the same chance of occurring; the position of the enciphered letter in each group has the same chance of occurring; and the position of the digit has the same chance of occurring (except when it's the first character and no digit is present, and also not on the same position as the enciphered character). \$\endgroup\$ Commented Dec 3, 2018 at 9:34
  • \$\begingroup\$ So abcd2, ab2de, babbk all same? Also is b1akk valid? \$\endgroup\$
    – l4m2
    Commented Dec 3, 2018 at 9:43
  • \$\begingroup\$ @l4m2 Yep, all three are possible outputs enciphering the character 'b'. As for b1akk I'd say no. Will edit it in the challenge description to clarify. If the first character is the enciphered one, no digit should be present. \$\endgroup\$ Commented Dec 3, 2018 at 9:48
  • 1
    \$\begingroup\$ For example, when length = 3, char = "a"; The form "a??" has 676 possible results, but "1a?", "?a1", "2?a", "?2a", has only104 results. So, if I'm trying to chose one result from all these 780 results, the distribution of "position of the enciphered letter" is 13:1:1, not 1:1:1. And I would consider this as how "uniformly random" work. \$\endgroup\$
    – tsh
    Commented Dec 4, 2018 at 9:43

14 Answers 14

5
\$\begingroup\$

Perl 6, 125 bytes

->\n{*.&{S:g{.}=(65..90)>>.chr.roll(n).join.subst(/./,$/,:th($!=roll 1..n:)).subst(/./,$!,:th($!-1??(^n+1∖$!).roll!!n+1))}}

Try it online!

Takes input and output in uppercase. Takes input curried, like f(n)(string). Uses 1 indexing.

Explanation:

->\n{*.&{ ...  }}   # Anonymous code block that takes a number n and returns a function
     S:g{.}=        # That turns each character of the given string into
                          .roll(n)      # Randomly pick n times with replacement
            (65..90)>>.chr              # From the uppercase alphabet
                                  .join # And join
            .subst(                         ) # Then replace
                   /./,  ,:th($!=roll 1..n:)  # A random index (saving the number in $!)
                       $/               # With the original character
            .subst(                )    # Replace again
                   /./,$!,:th( ... )    # The xth character with $!, where x is:
                           $!-1??          # If $! is not 1
                                 (^n+1∖$!).roll       # A random index that isn't $!
                                               !!n+1  # Else an index out of range
\$\endgroup\$
4
\$\begingroup\$

Pyth, 22 bytes

smsXWJOQXmOGQJdO-UQJJz

Try it online.

Uses lowercase and zero-indexing.

Explanation

Very straightforward algorithm.

                           Implicit: read word in z
                           Implicit: read number in Q
 m                   z     For each char d in z:
      OQ                     Choose a number 0..Q-1
     J                       and call it J.
         m  Q                Make an array of Q
          OG                 random letters.
        X     d              Place d in this string
             J               at position J.
    W                        If J is not 0,
   X                J        place J in this string
               O             at a random position from
                 UQ          0..Q-1
                -  J         except for J.
  s                          Concatenate the letters.
s                          Concatenate the results.
        
\$\endgroup\$
4
\$\begingroup\$

Python 2, 187 177 176 156 154 148 bytes

lambda l,s:''.join([chr(choice(R(65,91))),c,`n`][(j==n)-(j==i)*(n>0)]for c in s for n,i in[sample(R(l),2)]for j in R(l))
from random import*
R=range

Try it online!

Uses uppercase letters, and 0-indexed numbers.

-3 bytes, thanks to Kevin Cruijssen

\$\endgroup\$
5
  • \$\begingroup\$ @KevinCruijssen Thanks :) \$\endgroup\$
    – TFeld
    Commented Dec 3, 2018 at 10:33
  • \$\begingroup\$ What does sample(R(l),2)[::1|-(random()<.5)] mean? \$\endgroup\$
    – l4m2
    Commented Dec 3, 2018 at 10:34
  • \$\begingroup\$ @l4m2 It takes 2 numbers from range(l), and shuffles them. But apparently sample does not guarantee order, so it's not needed :) \$\endgroup\$
    – TFeld
    Commented Dec 3, 2018 at 10:40
  • \$\begingroup\$ Can't you remove the parenthesis around (j==i)*(n>0)? The multiply has operator precedence over the subtract doesn't it? \$\endgroup\$ Commented Dec 3, 2018 at 10:41
  • 1
    \$\begingroup\$ @KevinCruijssen Yeah, I forgot to remove them, when i had some problems \$\endgroup\$
    – TFeld
    Commented Dec 3, 2018 at 10:45
3
\$\begingroup\$

JavaScript (Node.js), 135 bytes

n=>f=([c,...s])=>c?(g=n=>n?g(--n)+(n-p?n-q|!p?(r(26)+10).toString(36):p:c):'')(n,r=n=>Math.random()*n|0,p=r(n),q=r(n-1),q+=q>=p)+f(s):s

Try it online!

Thank Arnauld for 1B

\$\endgroup\$
0
3
\$\begingroup\$

R, 134 132 123 bytes

function(S,n,s=sample)for(k in utf8ToInt(S)){o=k+!1:n
P=s(n,1)
o[-P]=s(c(P[i<-P>1],s(17:42,n-1-i,T)))+48
cat(intToUtf8(o))}

Try it online!

Takes uppercase letters.

Explanation of old code (mostly the same approach):

function(S,n){s=sample				# alias
K=el(strsplit(S,""))				# split to characters
o=1:n						# output array
for(k in K){					# for each character in the string
P=s(n,1)					# pick a Position for that character
o[-P]=						# assign to everywhere besides P:
      s(					# a permutation of:
	c(P[i<-P>1],				# P if it's greater than 1
		s(letters,n-1-i,T)))		# and a random sample, with replacement, of lowercase letters
o[P]=k						# set k to position P
cat(o,sep="")}}					# and print
\$\endgroup\$
2
\$\begingroup\$

Java (JDK), 193 bytes

s->n->s.flatMap(c->{int a[]=new int[n],i=n,x=0;for(;i-->0;)a[i]+=Math.random()*26+97;a[i+=Math.random()*n+1]=c;x+=Math.random()*~-n;if(i>0)a[x<i?x:x+1]=48+i;return java.util.Arrays.stream(a);})

Try it online!

  • The index are 0-based.
  • This entry uses an IntStream (gotten through String::chars) as input, as well as a number and returns another IntStream.
  • Casts from double to int are unnecessary because of the += hack.
\$\endgroup\$
2
\$\begingroup\$

Japt, 29 bytes

;£=VöJ;CöV hUÎX hUÅÎUÎ?UÎs:Cö

Try it online!

Zero-indexed.

Explanation:

;                                :Set C = [a...z]
 £                               :For each character of the input:
  =VöJ;                          : Get two different random indexes from [0,length)
       CöV                       : Get 5 random letters
           hUÎX                  : Replace one at random with the character from the input
                hUÅÎ             : Replace a different random character with:
                    UÎ?          :  If the input character was not placed at 0:
                       UÎs       :   The index of the input character
                          :      :  Otherwise:
                           Cö    :   A random letter
                                 :Implicitly join back to a string
\$\endgroup\$
2
\$\begingroup\$

C, 115 bytes

g(_,n)char*_;{int i=rand(),j=i%~-n,k=0;for(i%=n;k<n;k++)putchar(k-i?!i|i<k^k-j?rand()%26+97:48+i:*_);*++_&&g(_,n);}

Try it online!

0-indexed, lowercase.

Slightly ungolfed and expanded:

g(char*_,int n) {
    int i = rand(), j = i%(n-1), k = 0;
    for(i = i%n; k<n; k++)
        putchar(k!=i ? i!=0 || k==j + (k>i)
                          ? rand()%26 + 'A'
                          : i + '0')
                    : *_);
    if (*++_!=0) g(_,n);
}

The code should be pretty straightforward. The two randoms i,j generated in one rand() call are good as independent since gcd(n,~-n)=1 and RAND_MAX is large.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Welcome to PPCG! :) \$\endgroup\$
    – Shaggy
    Commented Dec 17, 2018 at 11:48
2
\$\begingroup\$

Wolfram Language (Mathematica), 169 bytes

StringJoin@Table[While@FreeQ[a=RandomChoice[Alphabet[],#2],c];If[(p=Position[a,c][[1,1]])!=1,a[[RandomChoice@Complement[Range@#2,{p}]]]=ToString@p];a,{c,Characters@#1}]&

Try it online!

Thank you for the interesting challenge!
It turned out a pretty big program,
but I can not do less and at the same time meet the requirements (

Ungolfed explained:

str = "thisisacomputercipher"; len = 5;
StringJoin[Table[
  While[(*Get first random choice contained character*)
   FreeQ[arr = RandomChoice[Alphabet[], len], c]
   ];
  If[(pos = Position[arr, c][[1, 1]]) != 1,
   (*If char is at first position - keep it, 
   else set random item to digit as index of char*)
   arr[[RandomChoice@Complement[Range@len, {pos}]]] = ToString@pos
   ]; arr,
  {c, Characters@str}]]
\$\endgroup\$
1
\$\begingroup\$

Clean, 256 bytes

import StdEnv
s::!Int->Int
s _=code {
ccall time "I:I"
ccall srand "I:I"
}
r::!Int->Int
r _=code {
ccall rand "I:I"
}
$n|s 0<1#k=map\e.r e rem n
=flatten o map\c.hd[map(\i|i==x=c=toChar if(i==y&&x>0)(x+48)(r i rem 26+97))[0..n-1]\\x<-k[0..]&y<-k[0..]|x<>y]

Try it online!

Chooses:

  • a random x (position of the character in the segment)
  • a random y that isn't equal to x (position of the digit in the segment)
  • a random lowercase letter for each position not equal to x and not equal to y unless x is zero
\$\endgroup\$
1
\$\begingroup\$

JavaScript, 134 bytes

l=>w=>w.replace(/./g,c=>eval("for(s=c;!s[l-1]||s[t?t-1||9:0]!=c;t=s.replace(/\\D/g,''))s=(p=Math.random()*36**l,p-p%1).toString(36)"))

Try it online!

This answer chose the encoded string from all possible encoded string uniformly. So it is more possible to make the encoded letter as the first one.

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

C# (Visual C# Interactive Compiler), 171 bytes

s=>n=>{var r=new Random();return s.SelectMany(c=>{int i=r.Next(n),j=r.Next(n-1);j+=j<i?0:1;return new int[n].Select((_,k)=>(char)(i==k?c:j==k&i>0?i+49:r.Next(26)+97));});}

Try it online!

Explanation...

// s is the input string
// n is the input length
s=>n=>{
  // we need to create an instance
  // of Random and use throughout
  var r=new Random();
  // iterate over s, each iteration
  // returns an array... flatten it
  return s.SelectMany(c=>{
    // i is the position of the letter
    // j is the position of the number
    int i=r.Next(n), j=r.Next(n-1);
    // ensure i and j are different
    j+=j<i?0:1;
    // create an iterable of size n
    return new int[n]
      // iterate over it with index k
      .Select((_,k)=>(char)(
        // return the letter
        i==k?c:
        // return the number
        j==k&i>0?i+49:
        // return a random letter
        r.Next(26)+97)
      );
  });
}
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 35 30 bytes

NθFS«≔‽θη≔∧η‽Φθ⁻κηζFθ≡κζIηηι‽β

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

Nθ

Input the length.

FS«

Input the word and loop over the characters.

≔‽θη

Choose a random position for the deciphered letter.

≔∧η‽Φθ⁻κηζ

Choose a different random position for the digit, unless the letter is at position 0, in which case put the digit at position 0 as well.

Fθ≡κ

Loop once for each output character and switch on the position.

ζIη

If this is the position of the digit then output the position of the deciphered letter.

ηι

But if this is the position of the deciphered letter then output the letter. This takes precedence over the position of the digit because Charcoal takes the last entry if multiple switch cases have the same value.

‽β

Otherwise output a random letter.

\$\endgroup\$
0
\$\begingroup\$

05AB1E, 26 bytes

ε²Ý¨Ω©A.r²£Šǝ®Āi®²Ý¨®KΩǝ]J

0-indexed.

Try it online or verify all test cases.

Explanation:

ε            # Map over the characters of the first (implicit) input-string:
 ²Ý¨         #  Create a list in the range [0, second input)
    Ω        #  Get a random item from this list
     ©       #  Store it in the register (without popping)
 A           #  Push the lowercase alphabet
  .r         #  Shuffle it
    ²£       #  Leave only the first second input amount of characters
      Š      #  Triple swap, so the stack order becomes:
             #  random index; random string; map-character
       ǝ     #  Insert the map-character at this random index into the random string
 ®Āi         #  If the random index was NOT 0:
    ®        #   Push the random index
    ²Ý¨      #   Push the list in the range [0, second input) again
       ®K    #   Remove the random index from this list
         Ω   #   Get a random item from this list
          ǝ  #   Insert the first random index at the second random index into the string
]            # Close both the if-else and map
 J           # Join all strings together (and output implicitly)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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