/* -*- Mode: c++ -*- * @(#) CryptoGram.java 1.0 6/10/97 Ralph Morelli * * Classes: CryptoGram FreqRecord FreqVector * Author: Ralph Morelli, Trinity College (ralph.morelli@trincoll.edu) * * Copyright (c) 1996 Ralph Morelli. All Rights Reserved. * * Permission to use, copy, modify, and distribute this software * and its documentation for NON-COMMERCIAL purposes and * without fee is hereby granted provided that this copyright * notice appears in all copies. */ /** * A class to store letter frequencies. */ class FreqRecord { int count; char letter; FreqRecord (int count, char letter) { this.count = count; this.letter = letter; } } // FreqRecord class FreqVector { private FreqRecord freq[]; FreqVector() { freq = new FreqRecord[26]; for (int k = 0; k < 26; k++) { freq[k] = new FreqRecord(0, (char)('a' + k)) ; } } public void reInitFreq() { for (int k = 0; k < 26; k++) { freq[k].count = 0; freq[k].letter = (char)('a' + k) ; } } /** * Counts the letters in the current cipher and * stores them in the frequency vector. * Calls a sort to sort frequency vector. * @param s the cipher string */ public void countLetters( String s ) { for (int k = 0; k < s.length(); k++) { char ch = s.charAt(k); if (ch >= 'A' && ch <= 'Z') ch = (char)(ch + 32); if (ch >= 'a' && ch <= 'z') freq[ ch - 'a'].count += 1; } sortFreq(freq,freq.length-1); } /** * Returns the top 8 most frequent letters in * the frequency vector. * @return a string of letters */ public String getTopFreq() { String temp = ""; for (int k = 0; k < 8; k++ ) { temp = temp + (char)(freq[k].letter - 32) + ", "; // System.out.println(freq[k].count); } return temp; } /** * Sorts the frequency array using a recursive * selection sort. * @param a the frequency array * @param n the length of the array */ private void sortFreq (FreqRecord a[], int n) { // recursive selection sort FreqRecord temp; if (n == 0) return; else { // recursive case int min = a[n].count; // let nth be min so far int minL = n; for (int k = n-1; k >= 0; k--) { // for entire array int next = a[k].count; // get next if (next < min) { // if greater than max min = next; // remember it minL = k; } // if next } // for each card temp = a[n]; // swap max and nth a[n] = a[minL]; a[minL] = temp; sortFreq(a, n-1); // sort the rest of the arr } // recursive case } // sort } // FreqVector class /** * A class to support making and breaking of simple * substitution cryptograms. * @version 1.0 6/10/97 * @author Ralph Morelli */ class CryptoGram { public static char NULL_CHAR = '*'; private int NHINTS = 8; private char theKey[] = new char[26]; // key for 'a' to 'z' private FreqVector freq = new FreqVector(); // letter frequency vector private String cipherText; // remains unchanged private String plainText; String hint[]; // an array of hints int hintPtr; // pointer to next hint CryptoGram ( ) { // Default Constructor cipherText = new String("") ; plainText = new String("") ; initKey(); freq.reInitFreq(); initHints(); } /** * Construct the initial cryptogram from a String. * @param s the String */ CryptoGram ( String s) { setCipherText( s ) ; plainText = new String("") ; initKey(); freq.reInitFreq(); initHints(); } /** * Resets the cryptogram. */ public void reset() { cipherText = ""; plainText = ""; initKey(); freq.reInitFreq(); } /** * Initializes the key */ public void initKey() { for (int k = 0; k < 26; k++) theKey[k] = NULL_CHAR; } /** * Initializes an array of canned hints. */ private void initHints() { hintPtr = 0; hint = new String[NHINTS]; hint[0] = "Frequent English letters: E, T, A, O, N, R, I, S, H" ; hint[1] = "Frequent double letters: LL,EE,SS,OO,TT,FF,RR,NN,PP,CC,MM,GG" ; hint[2] = "Frequent 2 letter words: OF,TO,IN,IS,IT,BY,HE,AS,ON,AT,AN,OR" ; hint[3] = "Frequent bigrams: TH,HE,AN,RE,ER,IN,ON,AT,ND,ST,ES,EN " ; hint[4] = "Frequent 3 letter words: THE,AND,FOR,ARE,WAS,BUT,ONE,HIS,HAS,HAD" ; hint[5] = "Frequent trigrams: ING,ION,ENT,ERE,TIO,THA,ATE,AND,EVE,HAT,VER,HER " ; hint[6] = "Frequent English initial letters: T,A,O,S,W,I,H,C,B,F,P,M" ; hint[7] = "Frequent English terminal letters: E,S,D,T,N,Y,F,R,O,H,G,A" ; } /** * Returns the next canned hint where hintPtr defines next. * @return the next hint in the hint array. */ public String getHint() { String temp = hint[hintPtr]; if (hintPtr == NHINTS-1) { hintPtr = 0; return "Most frequent letters in this cryptogram: " + freq.getTopFreq() ; } else hintPtr++; return temp; } /** * Determines if the cryptogram key is consistent -- i.e., * maintains a 1-1 mapping from {'A'..'Z'} to {'A'.. 'Z'} * @param key an integer array storing the key * @return true if the key is consistent and false otherwise */ public boolean isValidKey( int key ) { // works just for 'a' to 'z' if (key >= 'a' && key <= 'z') { // if key is a letter int k; for (k = 0; k < 26 && theKey[k] != (char)key; k++) ; // bodyless loop if ( k < 26 ) return false; else return true; } else if ( key == '*' ) // if key is NULL_CART return true; return false; } // isValidKey /** * Creates a mapping between a cipher and plain character * and then applies the entire key to the cryptogram. * @param cipherCh is the cipher character * @param plainCh is the plaintext character */ public void setKeyChar( int cipherCh, int plainCh) { theKey[ cipherCh ] = (char) plainCh ; translate(); } /** * Generates an entire key from a keyword where the keyword * has been installed in the first k locations of theKey[]. * This assumes that the first NULL_CHAR marks the end of * the keyword. * @return the keyword */ public String generateKey() { String keyWd = ""; // initialize the KEYWORD for (int k = 0; k < 26; k++) // find the end of the KEYWORD if (theKey[k] != NULL_CHAR) keyWd = keyWd + theKey[k]; else break; int k = keyWd.length(); // fill in the rest of the KEY for (int ch = 'a'; ch <= 'z'; ch++ ) { if (keyWd.indexOf(ch) == -1) theKey[k++] = (char) ch; } return keyWd; } /** * Returns the mapping for a given character in theKey. * @param ch the character to be mapped * @return the character's mapping. */ public char getPlainCh( int ch ) { return theKey[ch]; } /** * Sets the ciphertext from a given string. * @param s the String */ public void setCipherText( String s ) { cipherText = s; freq.countLetters( s ); } /** * Gets the current plaintext for this cryptogram. * The key is first applied to generate an up-to-date * plaintext. * @return the updated plaintext. */ public String getPlainText( ) { translate(); return plainText; } /** * Gets the current ciphertext for this cryptogram. * @return the current cipherText. */ public String getCipherText( ) { return cipherText; } /** * Translates ciphertext to plaintext using theKey[] */ private void translate() { plainText = ""; for (int k = 0; k < cipherText.length(); k++) { plainText = plainText + enCode( cipherText.charAt(k) ) ; } } // translate() /*----------------------------------------------*/ // If the character's key is not NULL_CHAR, return its key, // otherwise return the original character. /** * Encodes a single character if it is in {a..z}. * Case insensitive. The mapping is performed by * lookup in theKey. * NULL_CHAR's are not encoded * @param inChar the character to be encoded. * @return the encoded character. */ private char enCode(char inChar){ char outChar = inChar; if (inChar == NULL_CHAR) return inChar; // just return if NULL_CHAR if (inChar >= 'A' && inChar <= 'Z') { // If UPPERCASE character inChar = (char)((int)inChar + 32); // convert to lowercase outChar = theKey[(int)inChar - (int)'a']; // and then encode if (outChar != NULL_CHAR) // if not NULL outChar = (char) ((int)'A' + (int)outChar - (int)'a'); // convert back to UPPERCASE } else if (inChar >= 'a' && inChar <= 'z') { // If LOWERCASE outChar = theKey[(int)inChar - (int)'a']; // just encode } return outChar; } // Encode } // class CryptoGram