# 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.

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.

• "we don't like J" Do you harbour similar sentiments about APL? Mar 7 '14 at 2:50
• Gobbledygook! (Although the lack of a J in its name is creditworthy, I suppose.) Mar 7 '14 at 9:19
• 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. Mar 7 '14 at 20:37

## J I*, 536431417380263218203197186 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?

• We don't like J, but I is beautiful! Mar 7 '14 at 9:26
• Wow, this is extraordinary. Mar 10 '14 at 15:18
• While the first version looked a bit like witchcraft to me, this latest one is pure voodoo. Very impressive reduction. Mar 12 '14 at 1:29
• 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. Mar 12 '14 at 7:39
• I like J now :-) Mar 14 '14 at 9:42

## Ruby, 461411366359352346 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  • 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). Jan 2 '16 at 1:29 • Using & also eliminates the need for uniq later. And the chars to array thing also applies to line 6. Jan 2 '16 at 1:38 • @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! – Doorknob Jan 2 '16 at 1:41 • 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 :) Jan 2 '16 at 1:46 • @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). – Doorknob Jan 2 '16 at 1:48 # C: 495401355 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. • Well this is making my effort look very bad!! Looking forward to see how far you can go with this :-) Mar 9 '14 at 10:08 # 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  # 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"  # 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  # 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


# Python 3, 709705685 664

Accepts input from stdin.

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


Example:

mfukar@oxygen[/tmp]<>$python playfair.py Stack Overflow The cat crept into the crypt, crapped, and crept out again. SIRACARDFMVUICVSMORDZNAKECMZMFBCYNRDFMSVTVKBTMMY  • Also works perfectly in Python 2.5 :-) Mar 9 '14 at 10:15 ## 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  • 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 . May 31 '16 at 20:51 ## 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  # 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  • Good point about the regex issue — I'll ad a note to the question Mar 7 '14 at 14:47 • 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. Mar 7 '14 at 15:57 • 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. Mar 9 '14 at 4:28 # 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);  outputs $ php playfair.php "Stack Overflow" "The cat crept into the crypt, crapper, and crept out again."
SIRACARDFMVUICVSMORDZNAKECMZMFECYNRDFMSVTVKBTMMY
($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) }  # 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]