Analyzing text to decrypt simple substitution cyphers

So here's my project. It's broken into three main sections: Analysis, Encryption, and Decryption. 

The analysis part is the most complex and it takes the longest (by far) to run. That's where the program will create a dictionary of all the words in a given text and then analyze the dictionary to find letter frequencies and doubles frequencies (any reference to a double from here on refers to a combination of 2 letters -- aa, ab, ac, ect.) The bigger the text the better, and the text should be somehow related to the encrypted text that you need to decode. The point is that different types of text have (slightly) different letter frequencies and you want your two texts to match as closely as possible.

The encryption part is very simple. All it does is takes a piece of text, and changes into encryted text. This means that it takes out all spaces and punctuation and changes every letter to another letter based on the given key (if the key were 1, 'a' would become 'b', 'b' would become 'c', and so on all the way up to 'z', which would become 'a'.

The decryption part analyzes our encrypted text and compares it to the letter frequencies from the analysis part (our normal english text). Basically, it tries decrypting it in all 26 possible ways and sees which would make for the best match. It then tells us the key that we need to use to turn our text into normal english.

To run the program, first you need to evaluate all defintions. Type "init[file]" to declare all the necessary global variables. "file" should be a text file in the same directory as this notebook. Leave off the ".txt" part, as the program already assumes that we're using a text file. 

To analyze the text, type "analyze[d,e]" where d is the number of sections you want to break the text into (do this to save memory) and e is the number of sections you want to analyze. If you want to analyze the whole text, d and e should be the same number. When the computer has finished, type "exportResults" to save this information for later, so you don't have to analyze the whole text every time you want to decrypt something.

For encryption, just type "text=encrypt[anyText,key]". Make sure you use the variable "text", because that is what the program will look for when it's doing analysis. The key can be any number from 1 to 26 (actually, it can be any positive integer), but if you set it to 26, it won't change any of the letters (26 is the same as 0). For testing the program, you can use the function "grabTextSection[d,e]", which will assign a certain chunk of the fullText to the variable "text". Here d is the number of sections to break the text into and e is the section you want to grab.

To decrypt the text, type "analyzeEncodedText". This should return the key that you need to use to decrypt your text. Use the encrypt function to apply the key. If the analyzeEncodedText function finds two possible keys, it will return both. Once the text is in normal English, you can try findWordBreaks (beta, not final version) to find words in the text and insert the appropriate spaces. For all of this decryption stuff to work, you will need to have the results from your analysis. Use "importResults[file]" to get the results from a previous analysis.

For help with how to use these functions, type "?functionName". A short explanation of the function will be given (this only works for functions that the user is suppose to be able to use, other functions are only meant to be called by the program itself).

Definitions:

Globals

Off[General::spell1];

init::usage = "init[file] imports file.txt from the current \
directory and declares all the variables wnecessary for analysis of the text.\
";

\(\(\( (*\ 
      Declare\ variables\ and\ all\ \(\(that\)\(.\)\)\ *) \)\(
\)\(init[name_] := 
        Module[{}, 
SetDirectory[
            DirectoryName[
              ToFileName["FileName" /. 
                  NotebookInformation[
                    SelectedNotebook[]]]]]; 
fileName\  = \ 
            name; 
extension\  = \ ".txt"; 
fullText = 
            Import[fileName <> 
                extension]; 

 (*\ 
            create\ a\ list\ of\ 26\ zeros . \ 
                each\ index\ will\ represent\ a\ letter\ in\ the\ alphabet\ *) 
letters\  = \ 
            Table[0, {26}]; \ 

 (*\ 
            create\ a\ table\ of\ every\ possible\ combination\ of\ 2\ letters\ *) 
doubles\  = \ {}; 
Do[
Do[
\(AppendTo[
                  doubles, {0, 
                    FromCharacterCode[i\  + \ 96] <> 
                      FromCharacterCode[
                        j\  + \ 96]}];\)
, {j, 1, 
                26}
]
, {i, 1, 
              26}
]; 

wordList\  = \ {}; \  (*\ 
            here' s\ where\ we' 
              ll\ store\ our\ dictionary\ *) 
];\)\)\)

Analysis

analyze::usage = "analyze[d, e] breaks the selected text into d \
sections and analyzes the first e sections to find word and letter \
frequencies.";

    
\(\(analyze[numberOfSections_, depth_] := 
        Module[{sec, begin, end, startTime, 
            elapsed}, 
startTime\  = \ 
            SessionTime[]; \  (*\ 
            just\ to\ keep\ track\ of\ how\ long\ this\ takes\ *) 

end\  = \ 
            0; 
Do[
 (*\ 
              We' ll\ break\ the\ text\ into\ numberOfSections\ sections\ and\ analyze\ them\ one\ at\ a\ time\ to\ save\ \(\(memory\)\(.\)\)\ *) 
begin\  = \ end\  + \ 1; 
end\  = \ 
              Round[section\ StringLength[fullText]/
                    numberOfSections]; 

 (*\ to\ get\ the\ best\ results, \ 
              we\ want\ to\ break\ the\ text\ on\ word\ \(\(boundaries\)\(.\)\)\ *) 
While[
              True, 
cur = 
                StringTake[fullText, {end, end}]; 
 (*\ 
                keep\ looking\ at\ the\ next\ character\ until\ we\ find\ a\ space\ or\ we\ reach\ the\ end\ of\ the\ text\ *) 
If[
                cur\  == \ " " || 
                  end\  == \ StringLength[fullText], 
                Break[], \(end++\)];
]; 
text\  = \ 
              StringTake[fullText, {begin, end}]; 
len = 
              StringLength[text]; 

 (*\ 
              count\ words\ in\ this\ section\ *) 
countWords; 

elapsed\  = \ 
              SessionTime[] - 
                startTime; 
Print["Section = ", 
              section, \ ", Words = ", \ 
              Length[wordList], ", Time = ", 
              elapsed];

, {section, 1, 
              depth}
]; 

 (*\ now\ that\ we\ have\ our\ dictionary, \ 
            
            we\ analyze\ it\ to\ find\ letter\ frequency\ and\ doubles\ *) 
analyzeWordList; 
elapsed\  = \ 
            SessionTime[] - 
              startTime; 
Print["Finished analyzing wordList. Time = ", elapsed]; 

 (*\ 
            wordCounts\ is\ wordList, \ 
            only\ switched\ so\ that\ it\ will\ be\ easier\ to\ sort\ by\ word\ frequency\ *) 
wordCounts\  = \ {\ 
                wordList[\([All, 2]\)], wordList[\([All, 1]\)]\ } // 
              Transpose;
];\)\)

\(\(countWords := 
        Module[{newWord, index}, 
i = 1; \  (*\ 
            This\ is\ going\ to\ keep\ track\ of\ our\ position\ in\ the\ \(\(text\)\(.\)\)\ *) 

 (*\ 
            It' s\ gonna\ yell\ at\ us\ if\ we\ try\ to\ do\ this\ with\ an\ empty\ list, \ 
            so\ we\ need\ to\ grab\ the\ first\ word\ before\ we\ can\ enter\ the\ loop\ *) 
If[Length[wordList] == 0, 
            firstWord\  = getWord[len]; \ 
            wordList\  = \ {{firstWord, 
                  1}}]; 

While[
            i < \ len, 
 (*grab\ the\ next\ word*) 
newWord\  = \ 
              getWord[len]; 

 (*\ 
              Make\ sure\ we\ really\ got\ a\ word\ *) 
If[
              newWord\  == \ "", 
              Break[]\ ]; 

 (*\ 
              See\ if\ we' 
                ve\ already\ got\ that\ word\ *) 
index\  = \ findIndex[newWord]; 

 (*\ 
              If\ we\ don' t, \ 
              add\ it\ to\ the\ list . 
This\ seems\ like\ it\ would\ be\ a\ slow\ way\ to\ do\ things, \ 
              but\ I\ tried\ rewriting\ the\ find\ function\ to\ search\ for\ the\ index\ and\ place\ new\ words\ in\ the\ proper\ place, \ 
              but\ it\ didn' 
                t\ make\ things\ better . \ 
                  It\ started\ out\ going\ faster, \ 
              but\ the\ bigger\ the\ word\ list\ got, \ 
              the\ slower\ it\ went\ so\ that\ it\ ended\ up\ taking\ longer\ than\ what\ I\ have\ written\ \(\(below\)\(:\)\)\ *) 
If[\ 
              index\  == \ False, 
              wordList\  = \ 
                Sort[\ Append[\ 
                    wordList, {newWord, 
                      1}\ ]\ ]\ ]; 

 (*\ 
              If\ we\ do, \ increment\ it\ *) 
If[
              index\  > \ 
                0, \(\(wordList[\([index, 
                      2]\)]++\);\)];
];
];\)\)

\(\(\( (*grabs\ the\ next\ word*) \)\(
\)\(getWord[
          end_] := Module[{cur, 
            word}, 
word = ""; 
While[
            i ≤ end, 

 (*\ 
              Keep\ looking\ at\ the\ next\ character\ until\ we\ find\ a\ letter\ *) 
cur = 
              StringTake[text, {i, i}]; 
If[\ 
              LetterQ[cur]\  == \ True, 
              Break[]]; 
\(i++\);

]; 

While[
            i ≤ end, 

 (*\ 
              Keep\ grabbing\ letters\ until\ we\ hit\ a\ character\ that\ isn' t\ a\ letter\ *) 
cur = 
              ToLowerCase[StringTake[text, {i, i}]]; 
If[\ 
              LetterQ[cur]\  == \ True, word\  = \ word\  <> \ cur, 
              Break[]]; 
\(i++\);

]; 
Return[
            word]
];\)\)\)

(*Modified Jim' 
        s binary search to make it work with my list of words*) 
findIndex[word_] := 
        Module[{iLow, iHigh, i}, 
iLow = 
            1; 
iHigh = 
            Length[wordList]; 
While[
            True, 
If[iHigh  ==  0, 
              Return[False]]; 
i = 
              Floor[(iLow + iHigh)/2]; 
If[
              wordList[[i, 1]] == word, 
              Return[i]]; 
If[iLow ≥ iHigh, 
              Return[False]]; 
If[ 
              Order[word, wordList[[i, 1]]]  ==  1, 
              iHigh = i - 1]; 
If[ 
              Order[word, wordList[[i, 1]]]  == -1, 
              iLow = i + 
                  1];
];
];

(* 
      counts letters and doubles in the dictionary *) 
analyzeWordList := 
        Module[{stop}, 
 (* 
            loop through each word in the dictionary and count the letters and doubles *) 
Do[
countLettersInWord[
              i]; 
countDoublesInWord[
              i];
, {i, 1, 
              Length[wordList]}
]; 

 (* 
            turn the letter counts into a percentage *) 
letterTotal  =  
            Total[letters]; 
Do[
letters[[i]] = letters[[i]]/N[letterTotal]
, {i, 1, 
              26}
]; 
 (* 
            create a table with letter frequency *) 
sortLetters; 

 (* 
            turn doubles in percentage*) 
doubleTotal  =  Total[doubles[[All, 
                  1]]]; 
Do[
doubles[[i, 1]] = 
              doubles[[i, 1]]/N[doubleTotal]
, {i, 1, 
              Length[doubles]}
];
];

(* 
      counts letters in a word and multiplies each by the number of times the word occurs *) 
countLettersInWord[wordIndex_] := 
        Module[{currentWord, index, 
            end}, 
currentWord  =  
            wordList[[wordIndex, 1]]; 
end  =  
            StringLength[currentWord]; 
index  =  
            1; 

While[
            index ≤  
              end, 

 (* 
              grab the next character *) 
cur = 
              ToLowerCase[
                StringTake[
                  currentWord, {index, 
                    index}]]; 

 (* 
              count it *) 
letters[[ ToCharacterCode[ cur ][[1]] - 96]] +=  
              wordList[[wordIndex, 
                  2]]; 
index++;
];
];

(* same as above,  
      but for doubles *) 
countDoublesInWord[
          wordIndex_] := 
        Module[{currentWord, index}, 
currentWord  =  
            wordList[[wordIndex, 1]]; 
wordCount = 
            wordList[[wordIndex, 
                2]]; 

index  =  
            2; 

While[
            index ≤  
              StringLength[
                currentWord], 

 (* 
              grab the next characters *) 
cur = 
              ToLowerCase[
                StringTake[
                  currentWord, {index  -  1, 
                    index}]]; 

 (* 
              count this combination *) 
countDouble[
              cur, wordCount]; 
index++;
];
];

countDouble[word_, count_] := 
        Module[{iLow, iHigh, i}, 
iLow = 
            1; 
iHigh = 
            Length[doubles]; 

  
While[
            True, 

  
i = 
              Floor[(iLow + iHigh)/2]; 
  
If[ 
              word  ==  doubles[[i, 2]],  
              Break[ doubles[[i, 1]] += 
                  count ] ]; 

  
If[ 
              Order[word, doubles[[i, 2]]]  ==  1, iHigh = i - 1, 
              iLow = i + 
                  1];
];
];

(* 
      turns our list ({1, 4, 
            2,  ... }) into a table ({{1, a}, {4, 
              b}, {2, c} ... }) *) 
sortLetters := 
        Module[{}, 
letterFrequency  =  {}; 
Do[
AppendTo[
                letterFrequency, {letters[[i]],  
                  FromCharacterCode[i  +  96]}];
, {i, 
              1, 26}
]; 
letterFrequency  =  letterFrequency;
];

exportResults::usage = "exportResults exports letterFrequency, \
doubles, and wordCounts into separate .csv files in the current \
directory.";
exportResults := 
        Module[{}, 
Export[
            fileName <> "-letterFrequency.csv", 
            letterFrequency]; 
Export[
            fileName <> "-doubles.csv", 
            doubles]; 
Export[
            fileName <> "-wordCounts.csv", 
            wordCounts];
];

Encryption

grabTextSection::usage = "grabTextSection[d,e] breaks the text into \
d sections and grabs section number e";
\(\(grabTextSection[numberOfSections_, section_] := 
        Module[{begin, end, i, j}, 
 (*\ 
            find\ the\ start\ of\ our\ text\ *) 
i\  = \ 
            Round[\((section - 1)\) 
                  StringLength[fullText]/numberOfSections]\  + \ 
              1; 
 (*\ 
            keep\ looking\ at\ the\ next\ character\ until\ we\ find\ a\ space\ \((word\ break)\)\ *) 
While[
            True, 
cur = 
              StringTake[fullText, {i, i}]; 
If[
              cur\  == \ " ", 
              Break[], \(i++\)];
]; 

 (*\ 
            now\ do\ the\ same\ thing\ to\ find\ the\ end\ of\ our\ text\ *) 
begin\  = \ i; 
j\  = \ 
            Round[section\ StringLength[fullText]/
                  numberOfSections]; 
While[
            True, 
cur = 
              StringTake[fullText, {j, j}]; 
If[
              cur\  == \ " " || 
                j\  == \ StringLength[fullText], 
              Break[], \(j++\)];
]; 
end\  = \ j; 
text\  = \ 
            StringTake[
              fullText, {begin, end}];
];\)\)

encrypt::usage = "encrypt[d] returns encrypted text with each \
letter changed by d places";
encrypt[t_, key_] := 
        Module[{text}, 
text = 
            t; 
len  =  
            StringLength[text]; 
  
Do[
cur = 
              ToLowerCase[StringTake[text, {i, i}]]; 
If[ 
              LetterQ[cur]  ==  True, 
   text  =  
                StringReplacePart[text, 
                  convert[cur, key], {i, i}], 
   text  =  
                  StringDrop[
                    text, {i}];
];
  
, {i, len, 
              1, -1}
]; 
  
text  =  
            StringInsert[text, " ", 
              Table[5  i + 1, {i, 1,  
                  Floor[StringLength[text]/5]}] ]; 
Return[
            text];
];

convert[letter_, key_] := 
        Module[{}, 
Return[
            FromCharacterCode[
              Mod[ToCharacterCode[letter] - 97 + key, 26] + 
                97]]
];

Decryption

importResults::usage = "importResults[file] imports results from \
the analysis of file.txt";
\(\(importResults[fileName_] := 
        Module[{}, 
letterFrequency\  = \ 
            Import[fileName <> "-letterFrequency.csv"]; 
doubles\  = \ 
            Import[fileName <> "-doubles.csv"]; 
wordCounts\  = \ 
            Import[fileName <> "-wordCounts.csv"];
];\)\)

analyzeEncodedText::usage = "analyzeEncodedText analyses contents \
of the variable 'text' and returns the proper decryption key. If the analysis \
finds two different keys, it returns both.";
analyzeEncodedText := 
        Module[{key}, 
countLettersInEncodedText; 
countDoublesInEncodedText; 
key = 
            findDecryptionKey; 
Return[
            key];
];

(* 
      same as the countLetters function in the analysis section,  
      only this one counts letters in the whole text instead of by word*) 
countLettersInEncodedText := 
        Module[{}, 
encodedLetters  =  
            Table[0, {26}]; 
Do[
 (* 
              grab the next character *) 
cur = 
              ToLowerCase[
                StringTake[
                  text, {i, i}]]; 

 (* 
              if it' s a letter,  count it *) 
If[ 
              LetterQ[cur]  ==  
                True, encodedLetters[[ ToCharacterCode[ 
                          cur ][[1]] - 
                      96]]++];

, {i, 
              1, StringLength[
                text]}
]; 
sortEncodedLetters;
];

(* again,  
      same as the function in the analysis section,  
      only this one is switched so that we can easily sort alphabetically ({{a, 4}, {b, 2}, {c, 
              6},  ... }) *) 
sortEncodedLetters := Module[{}, 
encodedLetterFrequency  =  {}; 
Do[
AppendTo[
                encodedLetterFrequency, {FromCharacterCode[i  +  96], 
                  encodedLetters[[i]]}];
, {i, 1, 
              26}
];
];

(* 
      here I had to change the countDoubles routine to account for the fact that we' 
        ve got spaces in our text *) 
countDoublesInEncodedText := 
        Module[{index, end}, 
 (* 
            create our encodedDoubles table *) 
encodedDoubles  =  {}; 
Do[
Do[
AppendTo[
                  encodedDoubles, {FromCharacterCode[i  +  96] <> 
                      FromCharacterCode[j  +  96], 
                    0}];
, {j, 1, 
                26}
]
, {i, 1, 
              26}
]; 
index  =  
            2; 

While[
            index ≤  
              StringLength[text], 

 (* 
              grab the next character *) 
secondLetter = ToLowerCase[StringTake[
                  text, {index , 
                    index}]]; 

 (* 
              if we found a letter,  
              we need to find the letter that came before it*) 
If[
              LetterQ[secondLetter] == True, 
n = 
                1; 

 (* 
                keep checking previous characters until we find a letter *) 
While[
                True, 
If[index == 0, 
                  Break[firstLetter == 
                      False]]; 
firstLetter  =  
                  ToLowerCase[
                    StringTake[
                      text, {index - n , index - n}]]; 
If[
                  LetterQ[firstLetter] == True, 
                  Break[]]; 
n++;
]; 

 (* if we found two letters,  
                count them *) 
If[
                firstLetter == False, 
                Continue[]]; 
cur  =  
                firstLetter <> 
                  secondLetter; 
countEncodedDouble[
                cur];
]; 

index++;
];
];

(* 
      same as in analysis *) 
countEncodedDouble[word_] := 
        Module[{iLow, iHigh, i}, 
iLow = 
            1; 
iHigh = 
            Length[encodedDoubles]; 

While[
            True, 

 (* 
              check to see if we' 
                ve found the word in our list *) 
i = Floor[(iLow + iHigh)/2]; 
 (* if we have,  
              increment it *) 
If[ 
              word  ==  encodedDoubles[[i, 1]],  
              Break[ encodedDoubles[[i, 
                      2]]++ ] ]; 

 (* If not,  we keep looking *) 
If[ 
              Order[word, encodedDoubles[[i, 1]]]  ==  1, 
              iHigh = i - 1, 
              iLow = i + 
                  1];
];
];

\(\(\( (*\ 
      compares\ the\ encoded\ letter\ and\ double\ frequencies\ to\ the\ normal\ frequencies\ to\ find\ the\ best\ match\ *) \)\(
\)\(findDecryptionKey := 
        Module[{bestScore, bestKey, trialLetterFrequency, trialDoubles, 
            letterKey, doublesKey}, 
 (*\ 
            try\ all\ 26\ possible\ conversions\ of\ the\ text\ to\ see\ which\ one\ has\ the\ best\ match\ for\ letter\ frequencies\ *) 
bestScore = 0; 
bestKey = 
            0; 
Do[
trialLetterFrequency = encodedLetterFrequency; 
Do[
 (*\ 
                create\ a\ trial\ converted\ alphabet\ *) 
\(trialLetterFrequency[\([i, 1]\)] = 
                  convert[\ encodedLetterFrequency[\([i, 1]\)], 
                    j];\)
, {i, 1, 
                Length[encodedLetterFrequency]}
]; 
 (*\ 
              sort\ it\ so\ that\ it\ matches\ the\ normal\ letter\ frequency\ *) 
trialLetterFrequency\  = \ 
              Sort[trialLetterFrequency]; 
 (*\ 
              multiply\ the\ trialLetterFrequency\ and\ the\ normal\ letterFrequency\ to\ find\ an\ arbitrary\ score\ *) 
score = Total[letterFrequency[\([All, 1]\)]*
                  trialLetterFrequency[\([All, 
                      2]\)]]; 
 (*\ 
              if\ this\ is\ the\ best\ score, \ 
              save\ it\ *) 
If[
              score > bestScore, 
bestScore = 
                score; 
bestKey = 
                j
];
, {j, 1, 
              26}
]; 
letterKey = 
            bestKey; 

 (*\ 
            now\ do\ the\ same\ for\ doubles\ *) 
bestScore = 0; 
bestKey = 
            0; 
Do[
trialDoubles = 
              encodedDoubles; 
Do[
\(trialDoubles[\([i, 1]\)] = 
                  encrypt[\ encodedDoubles[\([i, 1]\)], 
                    j];\)
, {i, 1, 
                Length[encodedDoubles]}
]; 
trialDoubles\  = \ 
              Sort[trialDoubles]; 
score = 
              Total[doubles[\([All, 1]\)]*
                  trialDoubles[\([All, 2]\)]]; 
If[
              score > bestScore, 
bestScore = 
                score; 
bestKey = 
                j
];
, {j, 1, 
              26}
]; 
doublesKey = 
            bestKey; 

 (*\ 
            if\ both\ test\ \((letter\ and\ double)\)\ agree, \ 
            return\ that\ value; \ if\ not, \ 
            return\ a\ list\ of\ both\ values\ so\ that\ the\ user\ can\ try\ both\ *) 
If[
            doublesKey == letterKey, 
	
            Return[letterKey], 
	\(Return[{letterKey, 
                  doublesKey}];\)
];
];\)\)\)

findWord[word_] := 
        Module[{iLow, iHigh, i}, 
iLow = 
            1; 
iHigh = 
            Length[wordCounts]; 
While[
            True, 
If[iHigh  ==  0, 
              Return[False]]; 
i = 
              Floor[(iLow + iHigh)/2]; 
If[
              wordCounts[[i, 2]] == word, 
              Return[True]]; 
If[
              iLow ≥ iHigh, 
              Return[False]]; 
If[ 
              Order[word, wordCounts[[i, 2]]]  ==  1, 
              iHigh = i - 1]; 
If[ 
              Order[word, wordCounts[[i, 2]]]  == -1, 
              iLow = i + 
                  1];
];
];

findWordBreaks::usage = "findWordBreaks applies dictionary to \
decrypted text and finds possible word breaks.";
\(\(findWordBreaks := 
        Module[{max, start, end, 
            cur}, 
removeSpaces; 

 (*\ 
            put\ spaces\ where\ they\ belong\ *) 
max = 
            findLongestWordLength[wordCounts]; 
start = 
            1; 

While[
            start < StringLength[text], 
end = 
              start + max; 
If[end > StringLength[text], 
              end = StringLength[text]]; 
While[
              start ≤ end, 
cur = 
                StringTake[text, {start, end}]; 
If[\ 
                findWord[cur]\  == \ 
                  True, 
text\  = \ 
                  StringInsert[text, " ", 
                    end + 1]; 
Break[];
]; 
\(end--\);
]; 
start = 
              end + 2;
];
];\)\)

(* 
      removes all spaces (and punctuation) from the text*) 
removeSpaces := 
        Module[{cur}, 
Do[
cur = 
                ToLowerCase[
                  StringTake[text, {i, i}]]; 
If[ 
                LetterQ[cur]  ==  False, 
 (* 
                  then *)  text  =  
                    StringDrop[
                      text, {i}];
];
, {i, StringLength[text], 
                1, -1}
];
];

(* 
      finds the length of the longest word in the dictionary *) 
findLongestWordLength[wordCounts_] := 
        Module[{longestWordLength, 
            len}, 
longestWordLength = 
            0; 
Do[
len  =  
              StringLength[ wordCounts[[i, 2]] ]; 
If[
              len > longestWordLength, 
longestWordLength = len;
];
, {i, 1, 
              Length[wordCounts]}
]; 
Return[
            longestWordLength];
];

\(\(findWordBreaksBACK := 
        Module[{max, start, end, 
            cur}, 
removeSpaces; 

 (*\ 
            put\ spaces\ where\ they\ belong\ *) 
max = 
            findLongestWordLength[wordCounts]; 
end = 
            StringLength[text]; 

While[
            end > 1, 
start = 
              end - max; 
If[
              start < 1, \(start = 1;\)]; 
While[
              start ≤ end, 
If[
                start == end, 
                Break[text\  = \ 
                    StringInsert[text, " ", 
                      start]\ ]]; 
cur = 
                StringTake[text, {start, end}]; 
If[\ 
                findWord[cur]\  == \ 
                  True, 
\(Break[
                    text\  = \ 
                      StringInsert[text, " ", 
                        start]\ ];\)
]; 
\(start++\);
]; 
end = 
              start - 1;
];
];\)\)

Running the program:

Analysis:

init["moby"];
analyze[100, 100]

exportResults

Results:

Total words:

Total[wordCounts[[All, 1]]]

Total unique words:

Length[wordCounts]

Top 100 words in Moby Dick:

Reverse[Sort[wordCounts]][[Range[1, 100], All]] // 
      TableForm

Letter Frequency:

letterFrequency // Sort // Reverse // TableForm

Top 100 doubles:

Reverse[Sort[doubles]][[Range[1, 100], All]] // 
      TableForm

Encyption/Decryption:

init["moby"];
importResults["moby"];

grabTextSection[1000, 345];
text

text = encrypt[text, 23];
text

analyzeEncodedText

{encodedLetterFrequency[[All, 2]], 
              encodedLetterFrequency[[All, 1]]} // Transpose // Sort // 
        Reverse // TableForm

text = encrypt[text, 3];
text

countLettersInEncodedText;
{encodedLetterFrequency[[All, 2]], 
              encodedLetterFrequency[[All, 1]]} // Transpose // Sort // 
        Reverse // TableForm

letterFrequency // Sort // Reverse // TableForm

findWordBreaks;
text

findWordBreaksBACK;
text

text = "i saw a panda at the zoo";
analyzeEncodedText

text = encrypt[text, 4];
text

text = "first in last out";
analyzeEncodedText