23
\$\begingroup\$

Write a program that takes two lines of input and uses the first as a key phrase to encrypt the second according to the Playfair encryption technique.

Wikipedia describes Playfair encryption in some detail, but to avoid any ambiguity, here's a brief summary:

1. Generate a key table:

Replace all occurrences of J in the key phrase with I, then strip all non-alphabet characters and repeated characters. Insert into a 5×5 encryption table, filling the remaining cells with the rest of the alphabet (except J; we don't like J).

Example:

                                        S T A C K
                                        O V E R F
Stack Overflow  -->  STACKOVERFLW  -->  L W B D G
                                        H I M N P
                                        Q U X Y Z

2. Prepare the message to be encrypted

Replace every J with an I, strip all non-alphabet characters and split into pairs, using an X to break any pairs that contain the same letter twice. If you end up with an odd number of letters, add X at the end. (Note: Numerals have to be spelt out in full — ONE, TWO, THREE, etc. — but you can assume this has already been done for you.)

Example:

In:
The cat crept into the crypt, crapped, and crept out again.

Out:
TH EC AT CR EP TI NT OT HE CR YP TC RA PX PE DA ND CR EP TO UT AG AI NX

3. Encryption

Encrypt each pair of letters in turn. If they are in different rows and columns of the key table, replace each with the letter from the same row in the column where the other letter is found (e.g., VMEI, LZGQ). If they are in the same row (or column), choose the two characters immediately to the right (or below), wrapping around if necessary (e.g., OEVR, ZGKP).

Example:

In:
TH EC AT CR EP TI NT OT HE CR YP TC RA PX PE DA ND CR EP TO UT AG AI NX

Out:
SI RA CA RD FM VU IC VS MO RD ZN AK EC MZ MF BC YN RD FM SV TV KB TM MY

The string produced by this process is the encrypted message, which your program should output.

Rules:

  • The input text and key may be obtained from stdin, command line arguments or other such sources. Hard-coded input is not allowed.
  • Your program must accept both upper and lower case text for the pass phrase and message.
  • The encrypted output may be upper or lower case.
  • Your program should accept key phrases of at least 64 characters in length, and message texts of at least 16 KB.
  • You are not required to handle non-ASCII input.
  • You may ignore the possibility of the letter pair XX occurring during encryption.
  • There is no need to add whitespace to the output of the program.
  • Your answer should include an example of a message, key phrase and encrypted output produced by your program.
  • This is a code golf challenge, so the answer with the shortest code (in bytes) will win.

NOTE: Please remember that you only need to break consecutive letters if they appear in the same pair. So for example MASSACHUSETTS should be encrypted as MA SX SA CH US ET TS — the double S has to be split, but the double T doesn't.

\$\endgroup\$
3
  • 10
    \$\begingroup\$ "we don't like J" Do you harbour similar sentiments about APL? \$\endgroup\$ Commented Mar 7, 2014 at 2:50
  • \$\begingroup\$ Gobbledygook! (Although the lack of a J in its name is creditworthy, I suppose.) \$\endgroup\$
    – r3mainer
    Commented Mar 7, 2014 at 9:19
  • \$\begingroup\$ Regarding the input requirement, are function arguments allowed? (not sure if this constitutes "hard-coding") If not, can we assume that the key contains no newlines (preferrably, the plaintext too)? If not, command line args might be more viable compared to stdin. \$\endgroup\$
    – AlliedEnvy
    Commented Mar 7, 2014 at 20:37

17 Answers 17

12
\$\begingroup\$

J I*, 536 431 417 380 263 218 203 197 186 167

p=:4 :0
a=.u:65+9-.~i.26
,_2(5|,:~@|.@(=/)+$$,A.~5*1-0{=/)&.(5 5#:(~.n x,a)&i.)\(,'X'#~2|#)(({.,'X',}.)~1+2*1{&I._2{.\2=/\]) ::]^:_(n=:a(e.~#])'JI'charsub toupper)y
)

(with extensive suggestions from @algorithmshark)

example use:

   'Stack Overflow' p 'The cat crept into the crypt, crapped, and crept out again.'
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

splits input correctly:

   d=:(({.,'X',}.)~1+2*1{&I._2{.\2=/\]) ::]
   d^:_ 'MASSACHUSETTS'
MASXSACHUSETTS

*replace every J with an I, right?

\$\endgroup\$
5
  • 2
    \$\begingroup\$ We don't like J, but I is beautiful! \$\endgroup\$
    – Vereos
    Commented Mar 7, 2014 at 9:26
  • \$\begingroup\$ Wow, this is extraordinary. \$\endgroup\$
    – r3mainer
    Commented Mar 10, 2014 at 15:18
  • \$\begingroup\$ While the first version looked a bit like witchcraft to me, this latest one is pure voodoo. Very impressive reduction. \$\endgroup\$
    – Geobits
    Commented Mar 12, 2014 at 1:29
  • \$\begingroup\$ Pop pop pop, watching keystrokes drop! If anyone wants an explanation of how this voodoo magic works, here is a link; it's too long to fit in the answer without severe cramping. \$\endgroup\$ Commented Mar 12, 2014 at 7:39
  • 1
    \$\begingroup\$ I like J now :-) \$\endgroup\$
    – r3mainer
    Commented Mar 14, 2014 at 9:42
7
\$\begingroup\$

Ruby, 461 411 366 359 352 346 330 characters

k,m=$<.map{|x|x.tr(?j,?i).upcase.tr('^A-Z','').chars}
t=[*((k&k)|[*?A..?Z]-[?J]).each_slice(5)]
m=(m*'').gsub(/(.)\1/,'\1X\1').chars
c=->n{[t.index{|r|k=r.index n},k]}
$><<(m.size%2<1?m:m+[?X]).each_slice(2).map{|p,q|a,b,d,e=*c[p],*c[q]
a==d ?[t[a][(b+1)%5],t[d][(e+1)%5]]:b==e ?[t[(a+1)%5][b],t[(d+1)%5][e]]:[t[a][e],t[d][b]]}*''

Thanks to @daniero for saving... err, a lot of bytes. \o/

Here's the ungolfed code:

key = gets.chomp
msg = gets.chomp
transform = ->str{
    str.gsub! 'j', 'i'
    str.upcase!
    str.gsub! /[^A-Z]/, ''
    str.split('')
}

# 1. Generate a key table
key = transform[key]
chars = key.uniq + ([*?A..?Z] - key - ['J'])
tbl = Array.new(5) {
    Array.new(5) {
        chars.shift
    }
}

# 2. Prepare the message
msg = transform[msg]
msg = msg.join('').gsub(/(.)\1/){ "#{$1}X#{$1}" }.split('')
msg = (msg.length % 2 == 0 ? msg : msg + ['X']).each_slice(2).to_a

# 3. Encryption
coords = ->chr{
    i = -1
    [tbl.index{|row| i = row.index chr}, i]
}
msg.map! do |c1, c2|
    c1, c2 = coords[c1], coords[c2]
    if c1[0] == c2[0]
        # same row
        [tbl[c1[0]][(c1[1] + 1) % 5], tbl[c2[0]][(c2[1] + 1) % 5]]
    elsif c1[1] == c2[1]
        # same column
        [tbl[(c1[0] + 1) % 5][c1[1]], tbl[(c2[0] + 1) % 5][c2[1]]]
    else
        # neither
        [tbl[c1[0]][c2[1]], tbl[c2[0]][c1[1]]]
    end
end

# Output!
puts msg.join

Here's some sample outputs:

llama@llama:...code/ruby/ppcg23276playfair$ printf 'Stack Overflow\nThe cat crept into the crypt, crapped, and crept out again.\n' | ./playfair.rb; printf 'This is a password!\nProgramming Puzzles and Code Golf is a Stack Exchange site.\n' | ./playfair.rb
SIRAVXRDFMVUUYVSBLRDZNYVECMZMFBCYNRDFMSVTVKBVBMY
WDDEDSXIXOQFBTUYVQFISQWGRPFBWMESATAHHGMBVEITQFFISHMI
\$\endgroup\$
10
  • \$\begingroup\$ Looks nice, but there's room for improvement: On the first line there's no need to "cast" chars into an array, assuming you're using Ruby 2. Also you can use & as a set operator instead of tr: t=->s{s.gsub(?j,?i).upcase.chars&[*?A..?Z]} (7 bytes saved). The two next lines can be joined with something like k,m=[1,2].map{t[gets.chop]} (note chop rather than chomp). \$\endgroup\$
    – daniero
    Commented Jan 2, 2016 at 1:29
  • \$\begingroup\$ Using & also eliminates the need for uniq later. And the chars to array thing also applies to line 6. \$\endgroup\$
    – daniero
    Commented Jan 2, 2016 at 1:38
  • \$\begingroup\$ @daniero Right, this was done a looong time ago, so there's probably lots more improvements I could make. Thanks for the tips; time to revisit this! \$\endgroup\$
    – Doorknob
    Commented Jan 2, 2016 at 1:41
  • \$\begingroup\$ Yeah, I see that :) I stumbled upon the challenge and I immediately wanted to take a stab at it in Ruby, until I saw your answer.. The complexity of the code kind of scared me out of it, but I had to take a look at it :) \$\endgroup\$
    – daniero
    Commented Jan 2, 2016 at 1:46
  • \$\begingroup\$ @daniero Unfortunately, tr to & on line 1 doesn't work because m can't be uniqified. However, k.uniq can be shortened to (k&k) (1 byte off). \$\endgroup\$
    – Doorknob
    Commented Jan 2, 2016 at 1:48
4
\$\begingroup\$

C: 495 401 355 341 characters

It's just a rough sketch as of now. I should be able to shave off at least a hundred characters.

Goal accomplished: more than a hundred characters (154 as of now) have mysteriously vanished from the code.

p[25],l[96],a=1,b,c;o(x,y){putchar(p[x%5^y%5?x/5*5+(x/5^y/5?y:x+1)%5:(x+5)%25]);}main(){for(;a&&((a=(b=getchar())>31)||(b=65))||b++<90;c=0)for(b&=-33;b/65-b/91&&p[c]^b-(b==74);p[c++]||(p[--c]=b-(b==74),l[b]=c));for(;b=getchar(),b=b>31?b&-33:(c=88),b=b/65-b/91?a?a^b?(c*=c==88,b):(c=b,88):(a=b,0):0,a&b&&(o(a=l[a],b=l[b]),o(b,a),a=c),c^88;);}

With some pleasant whitespace:

p[25],l[96],a=1,b,c;
o(x,y){
    putchar(p[
        x%5^y%5
            ?x/5*5+(x/5^y/5?y:x+1)%5
            :(x+5)%25
    ]);
}
main(){
    for(;
        a&&(
            (a=(b=getchar())>31)||
            (b=65)
        )||b++<90;
        c=0
    )for(
        b&=-33;
        b/65-b/91&&
        p[c]^b-(b==74);
        p[c++]||(
            p[--c]=b-(b==74),
            l[b]=c
        )
    );
    for(;
        b=getchar(),
        b=b>31
            ?b&-33
            :(c=88),
        b=b/65-b/91
            ?a
                ?a^b
                    ?(c*=c==88,b)
                    :(c=b,88)
                :(a=b,0)
            :0,
        a&b&&(
            o(a=l[a],b=l[b]),
            o(b,a),
            a=c
        ),
        c^88;
    );
}

I wrote the first iteration of the program on the verge of falling asleep, so it had a lot of superfluous meaningless statements and such. Most of that is rectified, but there are quite a few areas where improvement is most definitely possible.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Well this is making my effort look very bad!! Looking forward to see how far you can go with this :-) \$\endgroup\$
    – r3mainer
    Commented Mar 9, 2014 at 10:08
2
\$\begingroup\$

Matlab - 458 chars

function p=pf(k,p)
k=[upper(k),65:90];k(k==74)=73;k(k<65|k>90)='';[~,i]=unique(k,'first');k=reshape(k(sort(i)),5,5);e=[k,k(:,1);k(1,:)];p=upper(p);p(p==74)=73;p(p<65|p>90)='';n=length(p);for i=1:2:n
if i<n&&p(i)==p(i+1)p=[p(1:i),88,p(i+1:end)];end
n=length(p);end
if mod(n,2)p=[p,88];n=n+1;end
for i=1:2:n [x,y]=find(k==p(i));[w,z]=find(k==p(i+1));p(i:i+1)=[k(w,y),k(x,z)];if x==w p(i:i+1)=[e(w,y+1),e(x,z+1)];end
if y==z p(i:i+1)=[e(x+1,z),e(w+1,y)];end
end

Some examples:

octave:180> pf('Stack Overflow', 'The cat crept into the crypt, crapped, and crept out again.')
ans = SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

octave:181> pf('This is a password!','Programming Puzzles and Code Golf is a Stack Exchange site.')
ans = WDDEDSXIXOQFBTUYVQFISQWGRPFBWMESATAHHGMBVEITQFFISHMI

octave:182> pf('Matlab needs lambdas', 'Who thought elseif is good syntax?')
ans = XGQMFQPKQDSACDKGRIFPQNILDMTW
\$\endgroup\$
2
\$\begingroup\$

Haskell - 711

Demo:

[timwolla@/data/workspace/haskell/PCG]ghc pcg-23276.hs
[1 of 1] Compiling Main             ( pcg-23276.hs, pcg-23276.o )
Linking pcg-23276 ...
[timwolla@/data/workspace/haskell/PCG]./pcg-23276 "Stack Overflow" "The cat crept into the crypt, crapped, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

Code:

import Data.List
import Data.Char
import Data.Maybe
import System.Environment
main=do a<-getArgs
    putStrLn$concat$map(x (a!!0))$map(\x->if (length x)==1 then x++"X"else x)$s 2$concat$map(\x->if (length x)==1then x else intersperse 'X' x)$group$p (a!!1)
p=map(\x->if x=='J' then 'I' else x).filter(isUpper).map toUpper
k x=y++(delete 'J'$['A'..'Z']\\y)where y=nub$p x
u l m=(div i 5,mod i 5)where i=fromJust$elemIndex l$k m
x y z
    |a/=c&&b/=d=(e!!(a*5+d)):(e!!(c*5+b)):[]
    |a==c=(e!!(a*5+(mod(b+1)5))):(e!!(c*5+(mod(d+1)5))):[]
    |True=(e!!((5*(mod(a+1)5))+b)):(e!!((5*(mod(c+1)5))+d)):[]
    where
        o=u(z!!0)y
        t=u(z!!1)y
        a=fst o
        b=snd o
        c=fst t
        d=snd t
        e=k y
s _ []=[]
s n l=(take n l):(s n(drop n l))

Large version:

import Data.List
import Data.Char
import Data.Maybe

encryptAll key text = map (encrypt key) (transformValue text)

clean x = map (\x -> if x == 'J' then 'I' else x) $ filter (isUpper) $ map (toUpper) x
transformKey x = y ++ (delete 'J' $ ['A'..'Z'] \\ y)
    where y = nub (clean x)

transformValue x = map (\x -> if (length x) == 1 then x ++ "X" else x) $ split 2 $ concat $ map (\x -> if (length x) == 1 then x else intersperse 'X' x) $ group $ clean x

search letter key = (div index 5, mod index 5)
    where index = fromJust $ elemIndex letter $ transformKey key

encrypt key chars
    | rowA /= rowB && colA /= colB = (key' !! (rowA * 5 + colB)) : (key' !! (rowB * 5 + colA)) : []
    | rowA == rowB = (key' !! (rowA * 5 + ((colA + 1) `mod` 5))) : (key' !! (rowB * 5 + ((colB + 1) `mod` 5))) : []
    | otherwise = (key' !! ((5 * ((rowA + 1) `mod` 5)) + colA)) : (key' !! ((5 * ((rowB + 1) `mod` 5)) + colB)) : []
    where
        rowA = fst $ search (head chars) key
        colA = snd $ search (head chars) key
        rowB = fst $ search (last chars) key
        colB = snd $ search (last chars) key
        key' = transformKey key

-- http://stackoverflow.com/a/12876438/782822
split :: Int -> [a] -> [[a]]
split _ [] = []
split n l
  | n > 0 = (take n l) : (split n (drop n l))
  | otherwise = error "Negative n"
\$\endgroup\$
2
\$\begingroup\$

Pyth - 111

Too late for competing, I just wanted to share. Here's the encoder and decoder

L@G:rb0\j\iJ{y+wGKywWhZ=Zh*2xlR{RcK2 1IhZ=KXZK\x;M@J+G?!eH5?!hH?q4%G5_4 1eHVcK2A,xJhNxJeN=Z-V.DH5.DG5pgGZpgH_RZ

Explanation:

L    b                              L defines common method y(b); 2 calls helps us saving two bytes
    r 0                             lowercase r(b,0)
   :   \j\i                         : replaces all occurrences of "j" with "i"
 @G                                 strips all non-alphabetic characters; G = pyth built-in alphabet

    w                               first input argument
   + G                              appends the alphabet (G)
  y                                 calls y(b)
 {                                  { makes set (removes duplicated characters)
J                                   assigns result to 'J' (KEY VARIABLE)

Kyw                                 assigns output from y(second input argument) to 'K' (TEXT VARIABLE)

WhZ                         ;       While (Z+1 != 0) <-> While (Z != -1) <-> While mismatched items found
             cK2                    list of K pairs.                    e.g. 'ABCCDDE' -> [AB, CC, DD, E]
         lR{R                       l length of { unique characters.    e.g. [2, 1, 1, 1]
        x       1                   1-length first index.               e.g. 1
     h*2                            *2+1 (Index in K)                   e.g. 3 'ABC CDDE'
   =Z                               Assigns to 'Z'
                  IhZ               if (Z != -1) <-> if (mismatched found)
                     =KXZK\x        X Inserts at Z index in K an 'x' and reassigns to 'K'  e.g. 'ABCXC...'

M                                   M defines function g(G, H) where G index H vector (INDEX CONVERSION)
     ?!eH                           if (same col)
         5                              then +5
         ?!hH                           else { if (same row)
             ?q4%G5                             then if (last col)
                   _4                               then -4
                      1                             else +1
                       eH                       else col
   +G                               index += increment
 @J                                 J[index]

VcK2                                V loops over cK2 list of K pairs
     ,xJhNxJeN                      x returns pair members index in J
    A                               A assigns G = xJhN, H = xJeN
                  .DH5              .D returns [row, col] = [i/5,i%5] of 5xn matrix from index of H
                      .DG5          idem. of G
                -V                  Subtracts vectors (RELATIVE POSITION)
              =Z                    Assigns to 'Z'
                          pgGZ          p prints g(G, Z) return value
                              pgH_RZ    p prints g(H, _RZ) return value, and _R changes signs of Z vector

Sample Key/Message/Output:

Stack Overflow
Gottfried Leibniz is famous for his slogan Calculemus, which means Let us calculate. He envisioned a formal language to reduce reasoning to calculation.
lfaukvvnrbbomwpmupkoexvqkovfimaqohflcmkcdsqwbxqtlintinbehcbovttksbtybsavmormwuthrhrbkevfxebqbspdxtbfsvfrwyarfrctrhmpwkrssbtybsvurh
\$\endgroup\$
2
\$\begingroup\$

Python 3, 709 705 685 664 662 615 569

With @ceilingcat's improvements. Accepts input from stdin.

import itertools as I,re,string
a=string.ascii_uppercase
d=lambda x:I.product(range(5),repeat=x)
t=lambda x:(input()+x).upper().replace('J','I')
s=''
for _ in t(a):
 if _ not in s and _ in a:s+=_
m=[s[i:i+5]for i in range(0,len(s),5)]
e={r[i]+r[j]:r[-~i%5]+r[-~j%5]for r in m for i,j in d(2)if i-j}
e.update({c[i]+c[j]:c[-~i%5]+c[-~j%5]for c in zip(*m)for i,j in d(2)if i-j})
e.update({m[q][r]+m[u][v]:m[q][v]+m[u][r]for q,r,u,v in d(4)if(q-u)*(r-v)})
print(''.join(e[a+(b if b else'X')]for a,b in re.findall(r'(.)(?:(?!\1)(.))?',''.join([_ for _ in t('')if _ in a]))))

Example:

mfukar@oxygen[/tmp]<>$ python playfair.py
Stack Overflow
The cat crept into the crypt, crapped, and crept out again.
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY
\$\endgroup\$
3
  • \$\begingroup\$ Also works perfectly in Python 2.5 :-) \$\endgroup\$
    – r3mainer
    Commented Mar 9, 2014 at 10:15
  • 1
    \$\begingroup\$ 569 bytes \$\endgroup\$
    – ceilingcat
    Commented May 13 at 17:06
  • \$\begingroup\$ Also, suggest adding a "Try it online" link. \$\endgroup\$
    – ceilingcat
    Commented May 14 at 15:52
2
\$\begingroup\$

Rust, 583 578 bytes 557 bytes

use std::io::*;fn f(mut k:Vec<u8>,t:Vec<u8>){let u=|a:Vec<u8>|a.into_iter().flat_map(|mut a|{a|=32;(a>96&&a<123).then(||a-(a==106)as u8)});k.extend(97..123);let mut o=vec![];for a in u(k){if!o.contains(&a){o.push(a)}}let mut p=vec![];for c in u(t){if p.len()%2>0&&p[p.len()-1]==c{p.push(120)}p.push(c)}if p.len()%2>0{p.push(120)}for k in 0..p.len()/2{let[d,e]=[0,1].map(|z|o.iter().position(|&a|a==p[k*2+z]).unwrap());stdout().write(&if d%5==e%5{[(d+5)%25,(e+5)%25]}else if d/5==e/5{[d/5*5+(d+1)%5,e/5*5+(e+1)%5]}else{[d/5*5+e%5,e/5*5+d%5]}.map(|z|o[z]));}}

Attempt This Online!

Formatted

use std::io::*;
fn f(mut k: Vec<u8>, t: Vec<u8>) {
    let u = |a: Vec<u8>| {
        a.into_iter().flat_map(|mut a| {
            a |= 32;
            (a > 96 && a < 123).then(|| a - (a == 106) as u8)
        })
    };
    k.extend(97..123);
    let mut o = vec![];
    for a in u(k) {
        if !o.contains(&a) {
            o.push(a)
        }
    }
    let mut p = vec![];
    for c in u(t) {
        if p.len() % 2 > 0 && p[p.len() - 1] == c {
            p.push(120)
        }
        p.push(c)
    }
    if p.len() % 2 > 0 {
        p.push(120)
    }
    for k in 0..p.len() / 2 {
        let [d, e] = [0, 1].map(|z| o.iter().position(|&a| a == p[k * 2 + z]).unwrap());
        stdout().write(
            &if d % 5 == e % 5 {
                [(d + 5) % 25, (e + 5) % 25]
            } else if d / 5 == e / 5 {
                [d / 5 * 5 + (d + 1) % 5, e / 5 * 5 + (e + 1) % 5]
            } else {
                [d / 5 * 5 + e % 5, e / 5 * 5 + d % 5]
            }
            .map(|z| o[z]),
        );
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Suggest let(mut o,mut p)=(vec![],vec![]); \$\endgroup\$
    – ceilingcat
    Commented May 15 at 20:37
1
\$\begingroup\$

C, 516

Linefeeds added for improved legibility presentation. (Legibility went out the window, I'm afraid.)

#define Z(u,v) putchar(o[u]),putchar(o[v])
#define X while((Y=getchar())>31){Y&=223;if(Y==74)Y--;if(Y<65||Y>90
P,L,A,Y,f,a,i,r,c=512,o[25],d[2],*e=o;Q(){for(i=0;o[i]!=d[0];i++);i-=(f=i%5);
for(r=0;o[r]!=d[1];r++);r-=(a=r%5);if(f==a)Z(f+(i+5)%25,a+(r+5)%25);
else if(i==r)Z((f+1)%5+i,(a+1)%5+r);else Z(a+i,f+r);}main(){X||c&(A=1<<Y-65))continue;
c|=A;*e++=Y;}A=1;Y=65;for(P=0;P<25;P++){if(!(c&A))*e++=Y;
if(++Y==74)Y++,A+=A;A+=A;}L=0;X)continue;if(L&&Y==*d)d[1]=88,Q(),*d=Y;
else d[L]=Y,L=1-L;if(!L)Q();}if(L)d[1]=88,Q();}

Example:

$ ./pf
Playfair                                    
The quick brown fox jumps over the lazy dog
QMHNPEKSCBQVTPSVEPEFTQUGDOKGAYXFRTKV
\$\endgroup\$
1
1
\$\begingroup\$

Python: 591 bytes

import sys
l=list
n=len
a=[sys.stdin.readline().upper().replace('J','I') for i in (1,2)]
b=l('ABCDEFGHIKLMNOPQRSTUVWXYZ')
def z(x):
    a=0
    if x in b:
        b.remove(x)
        a=1
    return a
c=l(filter(z,a[0]))+b
d=[x for x in a[1] if x in c]
e=1
while e<n(d):
    if d[e-1]==d[e]:
        d.insert(e,'X')
    e+=2
if n(d)%2>0:
    d+='X'
def y(i):
    z=c.index(d[i])
    return z/5,z%5
x=lambda i,j:c[(i%5)*5+(j%5)]
def w(i):
    e,f=y(i)
    g,h=y(i+1)
    if e==g:
        z=x(e,f+1)+x(g,h+1)
    elif f==h:
        z=x(e+1,f)+x(g+1,h)
    else:
        z=x(e,h)+x(g,f)
    print z,
e=0
while e<n(d):
    w(e)
    e+=2
print

This uses stdin to get the key and the message in that order. I hope it's not cheating to use a flat list to store the encryption matrix, because that made working with the matrix pretty simple. Here are some example runs:

>python playfair.py
Stack Overflow
The cat crept into the crypt, crapped, and crept out again.
SI RA CA RD FM VU IC VS MO RD ZN AK EC MZ MF BC YN RD FM SV TV KB TM MY

>python playfair.py
Stack Overflow
The quick red fox jumps over the lazy brown dog.
SI OX TU KS FR GR EQ UT NH OL ER VC MO BS QZ DE VL YN FL
\$\endgroup\$
2
  • \$\begingroup\$ I believe you can shorten z to lambda x:0if b not in x else b.remove(x)or 1. There's also quite a lot of whitespace you can get rid of. That would also let you move it directly into the filter call instead of defining it outside . \$\endgroup\$ Commented May 31, 2016 at 20:51
  • \$\begingroup\$ 508 bytes \$\endgroup\$
    – ceilingcat
    Commented May 10 at 18:35
1
\$\begingroup\$

Java - 791

My first golf, so any criticism is welcome. Using Java because I shouldn't. It doesn't seem so bad; less than double the size of the current leader. I was expecting it to be bigger since it's, well, Java :)

public class P{static String c(String s){return s.toUpperCase().replace('J','I').replaceAll("[^A-Z]","");}static int f(char[]a, char n){for(int i=0;i<a.length;i++)if(a[i]==n)return i;return -1;}public static void main(String[]a){int i=0,k,l;char j=0;String g=c(a[0]);char[]e,b,h=c(a[1]).toCharArray();b=new char[25];for(;j<g.length();j++)if(j==g.indexOf(g.charAt(j)))b[i++]=g.charAt(j);for(j=65;i<25;j++)if(f(b,j)<0&&j!=74)b[i++]=j;e=new char[h.length*2];for(i=0,j=0;j<h.length;){if(i%2>0&&h[j]==h[j-1])e[i++]=88;e[i++]=h[j++];}if(i%2>0)e[i++]=88;for(j=0;j<i;j+=2){k=f(b,e[j]);l=f(b,e[j+1]);if(k/5==l/5){e[j]=b[(k/5*5)+((k+1)%5)];e[j+1]=b[(l/5*5)+((l+1)%5)];}else if(k%5==l%5){e[j]=b[(k+5)%25];e[j+1]=b[(l+5)%25];}else{e[j]=b[(k/5*5)+(l%5)];e[j+1]=b[(l/5*5)+(k%5)];}}System.out.println(e);}}

With auto-format:

public class P {
    static String c(String s) {
        return s.toUpperCase().replace('J', 'I').replaceAll("[^A-Z]", "");
    }

    static int f(char[] a, char n) {
        for (int i = 0; i < a.length; i++)
            if (a[i] == n)
                return i;
        return -1;
    }

    public static void main(String[] a) {
        int i = 0, k, l;
        char j = 0;
        String g = c(a[0]);
        char[] e, b, h = c(a[1]).toCharArray();
        b = new char[25];
        for (; j < g.length(); j++)
            if (j == g.indexOf(g.charAt(j)))
                b[i++] = g.charAt(j);
        for (j = 65; i < 25; j++)
            if (f(b, j) < 0 && j != 74)
                b[i++] = j;
        e = new char[h.length * 2];
        for (i = 0, j = 0; j < h.length;) {
            if (i % 2 > 0 && h[j] == h[j - 1])
                e[i++] = 88;
            e[i++] = h[j++];
        }
        if (i % 2 > 0)
            e[i++] = 88;
        for (j = 0; j < i; j += 2) {
            k = f(b, e[j]);
            l = f(b, e[j + 1]);
            if (k / 5 == l / 5) {
                e[j] = b[(k / 5 * 5) + ((k + 1) % 5)];
                e[j + 1] = b[(l / 5 * 5) + ((l + 1) % 5)];
            } else if (k % 5 == l % 5) {
                e[j] = b[(k + 5) % 25];
                e[j + 1] = b[(l + 5) % 25];
            } else {
                e[j] = b[(k / 5 * 5) + (l % 5)];
                e[j + 1] = b[(l / 5 * 5) + (k % 5)];
            }
        }
        System.out.println(e);
    }
}

Sample output:

>java P "Stack Overflow" "The cat crept into the crypt, crapped, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

>java P "Write a PlayFair encryption program" "Write a program that takes two lines of input and uses the first as a key phrase to encrypt the second according to the Playfair encryption technique."
RITEWFCPGMWPGEBLYTWYQTXWINOLMWVNLECAXRNBURZWXWQILEWUWYWNQTFLDINWWEMICOTPYRIKWZRMGCBPGUOGPUWOKYGIQILYPFAPTIWMDPFLETGCEWODOWDZTZ
\$\endgroup\$
1
1
\$\begingroup\$

JS (node) - 528 466

k=n(2)+'ABCDEFGHIKLMNOPQRSTUVWXYZ',p=n(3),t=o=''
for(i=0;i<k.length;i++)if(!~t.indexOf(k[i]))t+=k[i]
for(i=0;i<p.length;){a=f(c=p[i++]),b=f(!(d=p[i])||c==d?'X':(i++,d))
if(a.x==b.x)a.y=(a.y+1)%5,b.y=(b.y+1)%5
else if(a.y==b.y)a.x=(a.x+1)%5,b.x=(b.x+1)%5
else a.x=b.x+(b.x=a.x,0)
o+=t[a.x+a.y*5]+t[b.x+b.y*5]}console.log(o)
function f(c){x=t.indexOf(c);return{x:x%5,y:x/5|0}}
function n(a){return process.argv[a].toUpperCase().replace(/[^A-Z]/g,'').replace(/J/g,'I')}

Sample output:

$ node playfair "Stack Overflow" "The cat crept into the crypt, crapped, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY
$ node playfair "Lorem ipsum" "dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
CRORSDAHGAMQKPXDOFQMQAMSBSSPUPBPTDMOAHURNRCRLUAULRGNMLCPLSKDSBSBSQQAHMIGRERYMQCROREMAGDTSZIMUHAIAQRQALSGLAHSLZRQPIETAPDXRPNMSFRYMEBPZGHARKIEMIOGROIGREPUHSUPAQIMUHAPUOYRPGRLLRCRKPXDUYAINZ
\$\endgroup\$
3
  • \$\begingroup\$ Good point about the regex issue — I'll ad a note to the question \$\endgroup\$
    – r3mainer
    Commented Mar 7, 2014 at 14:47
  • \$\begingroup\$ There's a problem with your second example — the pair of letters at characters 75 and 76 is encoded as UU. Looks like there was a repeated E that you should have split. \$\endgroup\$
    – r3mainer
    Commented Mar 7, 2014 at 15:57
  • \$\begingroup\$ You're right, I shot myself in the foot. The problem is with only looking at pairs but ending up off by one after an insertion. \$\endgroup\$
    – zobier
    Commented Mar 9, 2014 at 4:28
1
\$\begingroup\$

PHP 582

<? list($i,$k,$v)=array_map(function($v){return str_split(preg_replace('#[^A-Z]#','',strtr(strtoupper($v),'J','I')));},$argv);@$i=array_flip;$k=$i($k)+$i(range('A','Z'));unset($k['J']);$k=array_keys($k);$q=$i($k);for($i=1;$i<count($v);$i+=2){if ($v[$i-1]==$v[$i]){array_splice($v,$i,0,'X');}}if(count($v)%2)$v[]='X';for($i=1;$i<count($v);$i+=2){$c=(int)($q[$v[$i-1]]/5);$d=$q[$v[$i-1]]%5;$e=(int)($q[$v[$i]]/5);$f=$q[$v[$i]]%5;if($c==$e){$d=($d+1)%5;$f=($f+1)%5;}elseif($d==$f){$c=($c+1)%5;$e=($e+1)%5;}else{$t=$f;$f=$d;$d=$t;}$v[$i-1]=$k[$c*5+$d];$v[$i]=$k[$e*5+$f];}echo join($v);

Ungolfed
Decoder

outputs

$ php playfair.php "Stack Overflow" "The cat crept into the crypt, crapper, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFECYNRDFMSVTVKBTMMY
$ php playfair.php "This was codegolf?" "The full J answers is shorter than my preparation code :("
HIOKVGFHCMWTKZWSIYWIEPWAMWTCPNXQZKMOMEHSCPODEA
\$\endgroup\$
1
\$\begingroup\$

Perl, 265

Very straightforward.

chomp(($k,$_)=map{uc=~y/A-Z//cdr=~y/J/I/r}<>."@{[A..Z]}",~~<>);1while$k=~s/((.).*)\2/$1/;while(/(.)((?=\1|$)|(.))/g){($a,$b,$c,$d)=map{$e=index$k,$_;5*int$e/5,$e%5}$1,$3||X;print substr$k,$_%25,1 for$a-$c?$b-$d?($a+$d,$c+$b):($a+5+$b,$c+5+$d):(++$b%5+$a,++$d%5+$c)}

Indented:

chomp(($k,$_)=map{uc=~y/A-Z//cdr=~y/J/I/r}<>."@{[A..Z]}",~~<>);
1while$k=~s/((.).*)\2/$1/;
while(/(.)((?=\1|$)|(.))/g){
    ($a,$b,$c,$d)=map{$e=index$k,$_;5*int$e/5,$e%5}$1,$3||X;
    print substr$k,$_%25,1 for
        $a-$c
            ?$b-$d
                ?($a+$d,$c+$b)
                :($a+5+$b,$c+5+$d)
            :(++$b%5+$a,++$d%5+$c)
}
\$\endgroup\$
0
\$\begingroup\$

CoffeeScript - 610

Demo:

[timwolla@/data/workspace/js/PCG]coffee pcg-23276.coffee "Stack Overflow" "The cat crept into the crypt, crapped, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY

Code:

String::r=String::replace
_=(t,l)->
    for r in[0..4]
        for c in[0..4]
            return [r,c]if l is t[r][c]
K = {}
K[c]=c for c in (process.argv[2].toUpperCase().r(/J/g, 'I').r x=/([^A-Z])/g, '')
for i in[1..26]when i!=10
    c=String.fromCharCode 64+i
    K[c]=c
K=(c for c of K)
t=(K[s..s+4]for s in[0..24]by 5)
v=process.argv[3].toUpperCase().r(/J/g,'I').r(x,'').r(/(.)\1/g,'$1X$1').r /(..)/g, '$1 '
o=""
for p in v.trim().r(/\s([A-Z])$/, ' $1X').split /\s/
    [a,b]=p.split '';[d,f]=_ t,a;[e,g]=_ t,b
    o+=if d!=e&&f!=g
        t[d][g]+t[e][f]
    else if d==e
        t[d][++f%5]+t[e][++g%5]
    else
        t[++d%5][f]+t[++e%5][g]
console.log o

Ungolfed version:

search = (table, letter) ->
    for row in [0..4]
        for column in [0..4]
            return [ row, column ] if letter is table[row][column]

encrypt = (key, value) ->
    key = key.toUpperCase().replace(/J/g, 'I').replace /([^A-Z])/g, ''
    keyChars = {}
    keyChars[char] = char for char in key
    for i in [1..26] when i != 10
        char=String.fromCharCode 64 + i
        keyChars[char] = char
    keyChars = (char for char of keyChars)

    keyTable = (keyChars[start..start+4] for start in [0..24] by 5)

    value = value.toUpperCase().replace(/J/g, 'I').replace(/([^A-Z])/g, '').replace(/(.)\1/g, '$1X$1').replace /(..)/g, '$1 '
    pairs = value.trim().replace(/\s([A-Z])$/, ' $1X').split /\s/

    out = ""
    for pair in pairs
        [a,b] = pair.split ''
        [rowA, colA] = search keyTable, a
        [rowB, colB] = search keyTable, b
        if rowA!=rowB&&colA!=colB
            out += keyTable[rowA][colB]+keyTable[rowB][colA]
        else if rowA==rowB
            out += keyTable[rowA][++colA%5]+keyTable[rowB][++colB%5]
        else
            out += keyTable[++rowA%5][colA]+keyTable[++rowB%5][colB]
    out.replace /(..)/g, '$1 '

console.log encrypt process.argv[2], process.argv[3]
\$\endgroup\$
0
\$\begingroup\$

Python, 1338 525 bytes

A port of @Doorknob's Ruby answer in Python.

Saved 813 bytes thanks to @ceilingcat


Golfed version. Try It Online!

L=len
t=lambda:[c for c in input().strip().replace('j','i').upper()if c.isalpha()]
u=list(dict.fromkeys(t()))
m=t()
j=0
p=e=''
while j<L(m):
 p+=m[j];j+=1
 if j<L(m)and m[j-1]==m[j]:p+='X'
if L(p)%2:p+='X'
l=[(u+[h for h in"ABCDEFGHIKLMNOPQRSTUVWXYZ"if h not in u])[j:j+5]for j in range(0,25,5)]
def f(h):
 for i,o in enumerate(l):
  if h in o:return i,o.index(h)
for c,C in[p[j:j+2]for j in range(0,L(p),2)]:r,i=f(c);R,I=f(C);e+=l[r][-~i%5]+l[R][-~I%5]if r==R else l[r][I]+l[R][i]if i-I else l[-~r%5][i]+l[-~R%5][I]
print(e)

Ungolfed version. Attempt This Online!

# Function to transform the input string
def transform(text):
    # Replace 'j' with 'i', convert to uppercase, remove non-alphabet characters, and split into characters
    text = text.replace('j', 'i').upper()
    return [char for char in text if char.isalpha()]

# Function to generate the Playfair cipher key table
def generate_key_table(key):
    key = transform(key)
    # Create a list of unique characters in the key followed by the rest of the alphabet (excluding 'J')
    unique_key_chars = list(dict.fromkeys(key))
    remaining_chars = [char for char in "ABCDEFGHIKLMNOPQRSTUVWXYZ" if char not in unique_key_chars]
    combined_chars = unique_key_chars + remaining_chars
    # Create the 5x5 key table
    return [combined_chars[i:i+5] for i in range(0, 25, 5)]

# Function to prepare the message for encryption
def prepare_message(msg):
    msg = transform(msg)
    # Add 'X' between repeating characters
    prepared_msg = []
    i = 0
    while i < len(msg):
        prepared_msg.append(msg[i])
        if i + 1 < len(msg) and msg[i] == msg[i + 1]:
            prepared_msg.append('X')
        i += 1
    # Ensure even length by adding 'X' if necessary
    if len(prepared_msg) % 2 != 0:
        prepared_msg.append('X')
    # Split into pairs
    return [prepared_msg[i:i+2] for i in range(0, len(prepared_msg), 2)]

# Function to find coordinates of a character in the key table
def find_coords(tbl, char):
    for row_idx, row in enumerate(tbl):
        if char in row:
            return row_idx, row.index(char)
    return None

# Function to encrypt a message using the Playfair cipher
def encrypt_message(tbl, msg):
    encrypted_msg = []
    for pair in msg:
        c1, c2 = pair
        c1_row, c1_col = find_coords(tbl, c1)
        c2_row, c2_col = find_coords(tbl, c2)
        if c1_row == c2_row:
            # Same row
            encrypted_msg.append(tbl[c1_row][(c1_col + 1) % 5])
            encrypted_msg.append(tbl[c2_row][(c2_col + 1) % 5])
        elif c1_col == c2_col:
            # Same column
            encrypted_msg.append(tbl[(c1_row + 1) % 5][c1_col])
            encrypted_msg.append(tbl[(c2_row + 1) % 5][c2_col])
        else:
            # Rectangle swap
            encrypted_msg.append(tbl[c1_row][c2_col])
            encrypted_msg.append(tbl[c2_row][c1_col])
    return ''.join(encrypted_msg)

# Get user input for the key and message
key = input().strip()
msg = input().strip()

# Generate the key table and prepare the message
key_table = generate_key_table(key)
prepared_msg = prepare_message(msg)

# Encrypt the message
encrypted_msg = encrypt_message(key_table, prepared_msg)

# Output the encrypted message
print(encrypted_msg)
\$\endgroup\$
0
0
\$\begingroup\$

Scala 3, 1492 766 747 bytes

A port of @Doorknob's Ruby answer in Scala.

Saved 745 bytes thanks to @ceilingcat


Golfed version. Attempt This Online!

object m{def t()=scala.io.StdIn.readLine().trim.replace('j','i').toUpperCase.filter(_.isLetter).toList
def f(t:Array[Array[Char]],c:Char):(Int,Int)={t.zipWithIndex.collectFirst{case(r,i)if r.contains(c)=>(i,r.indexOf(c))}.getOrElse((0,0))}
def main(A:Array[String])={val k=t().distinct
var F=(k++("ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(k.contains))).toArray
val T=Array.tabulate(5,5)((r,c)=>F(r*5+c))
val s=t().foldLeft(List[Char]()){(a,c)=>if(a.nonEmpty&&a.last==c)a:+'X':+c else a:+c}
println(s"${(if(s.size%2<1)s else s:+'X').sliding(2,2).toList.flatMap{p=>val(g,h)=(p(0),p.last)
val(r,c)=f(T,g)
val(q,d)=f(T,h)
if(r==q){List(T(r)((c+1)%5),T(q)((d+1)%5))}else if(c==d){List(T((r+1)%5)(c),T((q+1)%5)(d))}else{List(T(r)(d),T(q)(c))}}.mkString}")}}

Ungolfed version. Attempt This Online!

object Main {
  // Function to transform the input string
  def transform(text: String): List[Char] = {
    // Replace 'j' with 'i', convert to uppercase, remove non-alphabet characters, and split into characters
    text.replace('j', 'i').toUpperCase.filter(_.isLetter).toList
  }

  // Function to generate the Playfair cipher key table
  def generateKeyTable(key: String): Array[Array[Char]] = {
    val keyChars = transform(key).distinct
    val remainingChars = "ABCDEFGHIKLMNOPQRSTUVWXYZ".filterNot(keyChars.contains)
    val combinedChars = (keyChars ++ remainingChars).toArray
    // Create the 5x5 key table
    Array.tabulate(5, 5)((row, col) => combinedChars(row * 5 + col))
  }

  // Function to prepare the message for encryption
  def prepareMessage(msg: String): List[List[Char]] = {
    val msgChars = transform(msg)
    val preparedMsg = msgChars.foldLeft(List[Char]()) { (acc, char) =>
      if (acc.nonEmpty && acc.last == char) acc :+ 'X' :+ char else acc :+ char
    }
    val evenPreparedMsg = if (preparedMsg.size % 2 == 0) preparedMsg else preparedMsg :+ 'X'
    evenPreparedMsg.sliding(2, 2).toList
  }

  // Function to find coordinates of a character in the key table
  def findCoords(tbl: Array[Array[Char]], char: Char): (Int, Int) = {
    tbl.zipWithIndex.collectFirst {
      case (row, rowIndex) if row.contains(char) => (rowIndex, row.indexOf(char))
    }.getOrElse(throw new NoSuchElementException(s"Character $char not found in the table"))
  }

  // Function to encrypt a message using the Playfair cipher
  def encryptMessage(tbl: Array[Array[Char]], msg: List[List[Char]]): String = {
    msg.flatMap { pair =>
      val (c1, c2) = (pair.head, pair.last)
      val (c1Row, c1Col) = findCoords(tbl, c1)
      val (c2Row, c2Col) = findCoords(tbl, c2)
      if (c1Row == c2Row) {
        // Same row
        List(tbl(c1Row)((c1Col + 1) % 5), tbl(c2Row)((c2Col + 1) % 5))
      } else if (c1Col == c2Col) {
        // Same column
        List(tbl((c1Row + 1) % 5)(c1Col), tbl((c2Row + 1) % 5)(c2Col))
      } else {
        // Rectangle swap
        List(tbl(c1Row)(c2Col), tbl(c2Row)(c1Col))
      }
    }.mkString
  }

  def main(args: Array[String]): Unit = {
    val key = scala.io.StdIn.readLine().trim
    val msg = scala.io.StdIn.readLine().trim

    // Generate the key table and prepare the message
    val keyTable = generateKeyTable(key)
    val preparedMsg = prepareMessage(msg)

    // Encrypt the message
    val encryptedMsg = encryptMessage(keyTable, preparedMsg)

    // Output the encrypted message
    println(s"Encrypted Message: $encryptedMsg")
  }
}
\$\endgroup\$
0

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.