""" patristocrats.py c odenthal 2005/05/21 This module contains a class designed to be an aid in solving "PATRISTOCRATS" cryptograms. These differ from "ARISTOCRATS" in that spaces and other punctuation is suppressed. """ from __future__ import division import string import plottools, pstringtools, pcounts, polyalphabetic cipher2plain = dict(zip(pstringtools.UPPERCASE,"_"*26)) plain2cipher = dict(zip(pstringtools.LOWERCASE,"_"*26)) startTable = string.maketrans(pstringtools.UPPERCASE,"_"*26) class Patristocrat(str): def __init__(self,S=""): str.__init__(self,S) self.cipher2plain = cipher2plain.copy() self.plain2cipher = plain2cipher.copy() self.cipherText = pstringtools.stripPunc(self.upper()) self.decode() def __repr__(self): return " ".join(pstringtools.breakIntoWords(self.cipherText,lngth=5)) def __str__(self): return "\n"+pstringtools.formatRows( (self.plainText, self.cipherText), l = 5, ll=80, s=1) def __len__(self): return len(self.cipherText) def decode(self): self.makeTable() self.plainText = self.cipherText.translate(self.transTable) def makeTable(self): """ This makes a decipherment table that will translate the cipher text to plain text. """ cp = self.cipher2plain.items() ct = "".join([c for c,p in cp]) pt = "".join([p for c,p in cp]) self.transTable = string.maketrans(ct,pt) def _map(self,CLett,pLett): """ Decodes the cipher text 'Ctext' as plain text 'ptext'. """ C = CLett.upper() p = pLett.lower() oldC, self.plain2cipher[p] = self.plain2cipher[p], C self.cipher2plain[oldC] = "_" self.cipher2plain[C] = p self.decode() def _unmap(self,C): C = C.upper() p = self.cipher2plain[C] self.plain2cipher[p] = "_" self.cipher2plain[C] = "_" self.decode() def update(self,CT,PT=""): """ This calls '_map' or '_unmap' possibly multiple times to update the current deciphering guess. If no 'PT' is used, then the ciphertext 'CT' is unmapped. Otherwise, the plain text 'PT' is assigned to the cipher text 'CT'. """ CT = list(CT) PT = list(PT) while PT: self._map(CT.pop(0),PT.pop(0)) while CT: self._unmap(CT.pop()) print self def load(self,filename): self.cipherText = open(filename).read().upper().strip() self.decode() def reset(self): self.cipher2plain = cipher2plain.copy() self.plain2cipher = plain2cipher.copy() self.decode() def letterFreq(self): """ Gets the letter fequencies of the cipher text. """ D = {} for char in self.cipherText: D[char] = D.get(char,0)+1 return D def plotLetterFreq(self): """ Plots the letter frequencies of the cipher text. """ plottools.freqPlot(self) def plotKappaTest(self,k): """ Plots the result of running the 'kappa' test on the cipher text. """ kappaLst = [ polyalphabetic.testKappa(self,i) for i in range(1,k) ] plottools.lstPlot(kappaLst) return def plotPhiTest(self,k): """ Plots the result of running the 'phi' test on the cipher text. """ phiLst = [ polyalphabetic.testPhi(self,i) for i in range(1,k) ] plottools.lstPlot(phiLst) return def printLetterPerc(self,ref=pcounts.austenLetter,refLabel="Austen"): """ Displays the percentages of the cipher letters occurences. """ percList = pstringtools.sortByValue( pstringtools.letterPerc(self)) dataStr = pstringtools.formatColumns((percList,ref)) lhead1 = "Cipher letter" lhead2 = "percentages" rhead1 = refLabel + " letter" rhead2 = "percentages" headStr1 = "\n"+lhead1.center(30) + rhead1.center(18) + "\n" headStr2 = lhead2.center(30) + rhead2.center(18) + "\n" print headStr1+headStr2+"\n"+dataStr def printDigraphPerc(self, ref = pcounts.austenDigraph, refR = pcounts.austenDigraphReverse, refLabel="Austen"): """ Displays the percentages of the cipher digraph occurences. """ dDct = pstringtools.digraphPerc(self) dLst = pstringtools.sortByValue(dDct)[:20] dRLst = [] for (k,v) in dLst: rk = k[::-1] dRLst.append((rk,dDct.get(rk,0.0))) dataStr = pstringtools.formatColumns((dLst,dRLst,ref,refR)) lhead1 = "Cipher digraph" lhead2 = "percentages" rhead1 = refLabel + " digraph" rhead2 = "percentages" headStr1 = "\n"+lhead1.center(54) + rhead1.center(40) + "\n" headStr2 = lhead2.center(54) + rhead2.center(40) + "\n" print headStr1+headStr2+"\n"+dataStr def showLowFreqContacts(self,lowFreq=0.04,spaces=1): """ Shows the contact numbers that the characters of the cipher text have with the low frequency characters. """ freqDict = self.letterFreq() freqList = [freqDict.get(char,0) for char in pstringtools.UPPERCASE] totaLett = sum(freqList) cutOff = lowFreq*totaLett print cutOff lowFreqList = [count