# This script attempts to solve a cryptoquote via a brute force dictionary # check. It sorts the cryptoquote words by their uniqueness and searches for # words which match by letter pattern. When a match is found, it modifies # the key so that successive searches return fewer matches. When a match # is not found, it unrolls the search to the last multi-find result and # starts searching again. # # Things to do. # 1. Split the dictionary file into smaller, multiple files based on length. # For instance, dictlen3.txt contains all words which are 3 letters or less. # And, dictlen4.txt contains all words which have exactly 4 letters. # This would speed up searches for words, since in effect, there would be # fewer words to hunt through. # 2. Reorg the dictionary(ies) so that more common words appear first. ie., # make "you" appear before "yod". # # # How to use it. Enter your cryptoquote and select your dictionary. For test purposes, # some sample cryptoquotes are included. # An inital guess for which letter corresponds to "E" is determined by the letter frequency # count. However, this guess may lead to a bad result. If so, redo just the letter frequency part # (remove the comment for the return on line 884) and decide foryourself which letter should # correspond to the letter "E". Line 847 provides an override statement. You can also # set additional hints via lines 855-880. Enjoy. # Why did I write this? I was learning tcl and I wanted to hone my skills with # a significant project. What did I learn? Basic TCL of course and that arrays are # trickier than I thought in TCL. # last modified 3/23/99 John F. Davis johnfdavis@earthlink.net ######################################################## proc openDictFile {fileName fileIdName} { upvar $fileIdName id set id [open $fileName RDONLY] } proc closeDictFile { fileId } { close $fileId; } proc readaWord { fileId } { gets $fileId theLine # puts $theLine return [string tolower $theLine]; } # Determines the number of times a letter appears in the cryptoquote. proc ltrFreq {} { global cryptoQuote; global theLetters; global ltrMatch set len [string length $cryptoQuote]; set ltrCount 0 foreach val $theLetters { for {set i 0} {$i < $len} {incr i} { if {[string match $val [string tolower [string index $cryptoQuote $i]]] } { incr ltrMatch($ltrCount) } } incr ltrCount } } # Inits the counts of letter appearances to 0. proc initTheLetterCounts {} { global theLetters global ltrMatch set i 0 foreach val $theLetters { set ltrMatch($i) 0 incr i } } # Displays the number of times a letter appears in the CryptoQuote proc dumpTheLetterCounts {} { global theLetters global ltrMatch set i 0 foreach val $theLetters { puts "$val : $ltrMatch($i) " incr i } } # Find which letter shows up the most in the frequency Distribution. proc findMostCommonLetter {} { global theLetters global ltrMatch set i 0 set mostCommonLetter "-" set hiCount 0 foreach val $theLetters { if {$ltrMatch($i) > $hiCount} { set hiCount $ltrMatch($i) set mostCommonLetter $val } incr i } puts "$mostCommonLetter shows up the most occuring $hiCount times." return $mostCommonLetter; } # A routine used by the findWords proc when it # sorts the words by uniqueness proc sortByUnique {a b} { set aLen [string length $a] set bLen [string length $b] findLettersInWord $a $aLen locArrayA findLettersInWord $b $bLen locArrayB set aDiff [expr {$aLen +($aLen - [array size locArrayA])}] set bDiff [expr {$bLen +($bLen - [array size locArrayB])}] # if the word contains double's increment the word count twice. if {[containsDoubles $a $aLen]} { incr aDiff 2 } if {[containsDoubles $b $bLen]} { incr bDiff 2 } # words which contain a dash or an apostrophee are very unique. if {[llength [array names locArrayA "'"]] > 0 || [llength [array names locArrayA "-"]] > 0 } { incr aDiff 10 } if {[llength [array names locArrayB "'"]] > 0 || [llength [array names locArrayB "-"]] > 0} { incr bDiff 10 } # And single letter words such as "a" or "I" are the most unique # of all. if {$aLen == 1} { incr aDiff 100 } if {$bLen == 1} { incr bDiff 100 } if {$aDiff == $bDiff} { return 0; } else { return [expr {$aDiff - $bDiff}]; } } # A routine used by the findWords proc when it # sorts the words by length. proc sortByLen {a b} { set aLen [string length $a] set bLen [string length $b] if {$aLen == $bLen} { return 0; } else { return [expr {$aLen - $bLen}]; } } # Generates a list of words in the cryptoQuote string # sorted such that the longer words appear first. proc findWords {} { global cryptoQuote; global theCryptWords; set len [string length $cryptoQuote]; set curBegin 0 for {set i 0} {$i < $len} {incr i} { if {[string match " " [string index $cryptoQuote $i]]} { lappend theCryptWords [string range $cryptoQuote $curBegin [expr {$i - 1}]] set curBegin [expr {$i + 1}] } } lappend theCryptWords [string range $cryptoQuote $curBegin $len] set theCryptWords [lsort -decreasing -command sortByUnique $theCryptWords] return ""; # I don't want theWords printed out, so I added this fake return. } # Determines the length of the word. proc lengthOfWord { theWord } { string length $theWord } # Determine if the word contains doubles (letters repeated twice). For speed, # give it the number of letters in the word, since its already been found once. # Returns 1 if the word contains doubles. proc containsDoubles { theWord wordLength} { # If its a one letter word, it can't possibly contain a double. if {$wordLength < 2} { return 0; } # break as soon as you find a double. for {set i 0} {$i < [expr {$wordLength - 1}]} {incr i} { if {[string match [string index $theWord $i] [string index $theWord [expr {$i + 1}]]]} { return 1; } } return 0; } # Determines a map to the letters in the word. # The locs letter associative array will contain the letter locations. # ex1. foobar would generate: # locs(a) = { 4 } # locs(b) = { 3 } # locs(f) = { 0 } # locs(o) = { 1 2 } # locs(r) = { 5 } # ex2. poopoo would generate: # locs(p) = { 0 3 } # locs(o) = { 1 2 4 5 } proc findLettersInWord { theWord wordLength locArray} { # input input output upvar $locArray locs global theLetters # delete the locArray so we start clean each time. if {[info exists locs]} { unset locs } foreach val $theLetters { for {set i 0} {$i < $wordLength} {incr i} { if {[string match $val [string index $theWord $i]] } { lappend locs($val) $i } } } } # Dumps an array of lists. proc dumpTheLocArray { locArray } { upvar $locArray locs global theLetters # This code just dumps the locArray. Its for debug foreach val $theLetters { if {[info exists locs($val)]} { foreach val2 locs($val) { puts "$val : $locs($val) " } } } } # The first locArray corresponds to a possible match. # The second locArray corresponds to a cryptoquote word. proc compareLetterMaps {locArray1 locArray2 } { upvar $locArray1 locs1 upvar $locArray2 locs2 # puts "The letter map for the first word." # dumpTheLocArray locs1 # puts "The letter map for the second word." # dumpTheLocArray locs2 # If every letter which has multiple locations has a mathing sequence # in the other word its a match. set searchId1 [array startsearch locs1] while {[array anymore locs1 $searchId1]} { set element1 [array nextelement locs1 $searchId1] set list1ItemCount [llength $locs1($element1)] if { $list1ItemCount <= 1} { # no need to look in the other list for this letter # it only appears once. ie., we can't eliminate # the word based on a letter that only appears once. continue; } # okay, so we found a letter which shows up more than once. # now we need to see if we find a letter which shows up the # same number of times. If we do and it shows up in the # same positions, then its a match. If it doesn't show up # in the same places, then we know its not a match. # Set the we found a match flag to 0. set weFoundAMatch 0 set searchId2 [array startsearch locs2] while {[array anymore locs2 $searchId2]} { set element2 [array nextelement locs2 $searchId2] set list2ItemCount [llength $locs2($element2)] if { $list1ItemCount == $list2ItemCount} { if {$locs1($element1) == $locs2($element2)} { set weFoundAMatch 1 break; } } } array donesearch locs2 $searchId2 if {$weFoundAMatch == 0} { return 0; } } array donesearch locs1 $searchId1 return 1; } # Determines if the word has repeated letters. A repeated letter # is not necessarily a double. ie., I counsider babar to have repeated # "b" but not a double "b". # Returns a 1 if the word contains repeated letters, otherwise it returns 0. proc containsDupes { locArray } { upvar $locArray locs global theLetters foreach val $theLetters { if {[info exists locs($val)]} { if { [llength $locs($val)] > 1 } { return 1; } } } return 0; } # dumps the cryptoqote words and their stats. proc dumpCryptWordStats {} { global theCryptWordStatArray global theCryptWords set i 0 foreach val $theCryptWords { global locArray$i puts "The word is $val" puts "The length of the word is [lindex $theCryptWordStatArray($val) 0]" puts "It takes [lindex $theCryptWordStatArray($val) 2] letters to make this word." puts "The letter map for the word is:" dumpTheLocArray [lindex $theCryptWordStatArray($val) 1] if {[lindex $theCryptWordStatArray($val) 3]} { puts "it contains dupes" } if {[lindex $theCryptWordStatArray($val) 4]} { puts "it contains doubles" } puts "------------------------" incr i } } # finds similar word to a given word in the cyptoquote. proc findSimilarWords {curCryptWord key matchWordsParm} { global theCryptWordStatArray global dictFileName global dictFileId global theCryptWords upvar $matchWordsParm matchWords set matchWords [list ] # make those damn locArray's visible in here. for { set i 0} {$i <= [llength $theCryptWords]} {incr i} { global locArray$i } puts "I'm looking for similar words to $curCryptWord" puts "The Master Key is:" dumpKey $key set curCryptWordStatList $theCryptWordStatArray($curCryptWord) # open the dict file openDictFile $dictFileName dictFileId set theWord [readaWord $dictFileId] while { [string length $theWord] != 0 } { # Test the wordlength. set theWordLength [lengthOfWord $theWord] if { $theWordLength != [lindex $curCryptWordStatList 0]} { set theWord [readaWord $dictFileId] continue; } # Find letter map for the word. findLettersInWord $theWord $theWordLength locArray # Test number of letters count. if {[array size locArray] != [lindex $curCryptWordStatList 2]} { set theWord [readaWord $dictFileId] continue; } # Test the dupe flag. if {[containsDupes locArray] != [lindex $curCryptWordStatList 3]} { set theWord [readaWord $dictFileId] continue; } # Test the double flag. if {[containsDoubles $theWord $theWordLength] != [lindex $curCryptWordStatList 4]} { set theWord [readaWord $dictFileId] continue; } # Test to see if they have similar letter maps. if { [compareLetterMaps locArray [lindex $curCryptWordStatList 1]] != 1} { # puts "the letter maps differ. Skip this one." set theWord [readaWord $dictFileId] continue; } # Test to see if key generated by this word agrees with the master key set decodeKey [buildAKey $curCryptWord $theWord] if {[compareKeys $decodeKey $key] != 1} { #puts "The key generated by this word doesn't match the master key." set theWord [readaWord $dictFileId] continue; } puts " *************** possible match is: $theWord ********" dumpKey $decodeKey lappend matchWords $theWord set theWord [readaWord $dictFileId] } # close the dict file. closeDictFile $dictFileId } # Returns 1 if the keys don't disagree proc compareKeys {key1 key2} { set i 0 foreach val $key1 { if {[lindex $key1 $i] != " "} { if {[lindex $key2 $i] != " "} { if {[lindex $key1 $i]!=[lindex $key2 $i]} { return 0; } } } incr i } # okay, if the two keys are combined, do they generate duplicate letters. set i 0 foreach val $key2 { # puts "$i $val" if {$val != " "} { # if its not a space set loc [lsearch $key1 $val] if {[expr "$loc >= 0"]} { # puts "$val is in key1" if { [expr {$loc != $i} ]} { # puts "but its not in the same location." return 0; } } } incr i } return 1; } # Dumps the key to the screen. proc dumpKey {key} { global theLetters set i 0 foreach val2 $key { puts -nonewline "[lindex $theLetters $i] " incr i } puts "" set i 0 foreach val2 $key { puts -nonewline "$val2 " incr i } puts "" } # Build a key proc buildAKey {cryptWord matchWord} { global theLetters #puts "Possible match word ($matchWord) gives the following key:" # init key foreach ltr $theLetters { lappend key " " } # now build the key. set i 0 foreach ltr [split $cryptWord {}] { set pos [lsearch $theLetters $ltr] set key [lreplace $key $pos $pos [string index $matchWord $i] ] incr i } # dump the key #dumpKey $key return $key; } # Fills in the missing values in the second key with values in # the first key. proc modifyKey {key1 key2Parm} { upvar $key2Parm key2 set i 0 foreach val $key1 { if {[lindex $key1 $i] != " "} { if {[lindex $key2 $i] == " "} { set key2 [lreplace $key2 $i $i [lindex $key1 $i]] } } incr i } } # saves state about last guess on stack. proc saveState { matchWords cryptWordsIndex key } { global theStackList puts "" puts "" # puts "save state vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" # puts "matchWords = $matchWords" # puts "cryptWordsIndex = $cryptWordsIndex" # puts "key = $key" # puts "save state ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" # gets stdin foo # save the current matchwords, cryptWordsIndex and key. lappend theStackList $matchWords lappend theStackList $cryptWordsIndex lappend theStackList $key } # unrolls state changes. proc unrollState { matchWordsParm cryptWordsIndexParm keyParm } { puts "" puts "" global theStackList upvar $keyParm key upvar $cryptWordsIndexParm cryptWordsIndex upvar $matchWordsParm matchWords set oldMatchWords [list ] while { [llength $oldMatchWords] == 0 } { # puts "unroll state vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" # puts "len of match words list = [llength $oldMatchWords]" # For an error check, if the stack list is empty just stop. There's nothing # else to do. if {[llength $theStackList] < 3} { puts "The stack list is empty. Not enough to pop. Let's exit." puts "This usually occurs when the word you are looking for isn't in the dictionary." puts "Or, the initial guess for e is wrong." puts "Remember, this algorithim is only as good as the dictionary." return 0; } # "pop" the stack to get the old values. set oldKey [lindex $theStackList end] set theStackList [lreplace $theStackList end end] set oldCryptWordsIndex [lindex $theStackList end] set theStackList [lreplace $theStackList end end] set oldMatchWords [lindex $theStackList end] set theStackList [lreplace $theStackList end end] # remove the dead end matchWord set oldMatchWords [lreplace $oldMatchWords 0 0] # puts "Words in match list: $oldMatchWords" # puts "cryptWordsIndex = $oldCryptWordsIndex" # puts "key = $oldKey" # puts "len of stack = [llength $theStackList]" # puts "len of match words list = [llength $oldMatchWords]" # puts "unroll state ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" # gets stdin foo } set matchWords $oldMatchWords set cryptWordsIndex $oldCryptWordsIndex set key $oldKey saveState $matchWords $cryptWordsIndex $key # sometimes we get a word which isn't in there. #if { [llength $matchWords] == 0} { # puts "we got bad one." # unrollState matchWords cryptWordsIndex key # #return 0; #} return 1; } proc doit {matchWordsParm cryptWordsIndexParm keyParm } { global theCryptWords upvar $keyParm key upvar $cryptWordsIndexParm cryptWordsIndex upvar $matchWordsParm matchWords puts "" puts "" # if there are no more words to solve, just return. if {$cryptWordsIndex >= [llength $theCryptWords] } { puts "no more words to solve." return 1; } # for this key, find the list of possible match words. findSimilarWords [lindex $theCryptWords $cryptWordsIndex] $key matchWords puts "" puts "number of matches for [lindex $theCryptWords $cryptWordsIndex] is [llength $matchWords]" if {[llength $matchWords] == 0} { # if we don't find any words, then unroll this search to the last match word. puts "no match words were found for this word. Unroll and try again." # unroll the stack and try again. Note, the unroll proc # will backup to where it should retry next from. if {[unrollState matchWords cryptWordsIndex key] == 0} { # we had an error. puts "We had an error in unroll state. We should exit ******************" return 0; } puts "****** matchWords = $matchWords" puts "****** cryptWordsIndex = $cryptWordsIndex" # puts "****** key = $key" } else { saveState $matchWords $cryptWordsIndex $key } puts "use [lindex $matchWords 0] to modify the key." modifyKey [buildAKey [lindex $theCryptWords $cryptWordsIndex] [lindex $matchWords 0]] key incr cryptWordsIndex # gets stdin foo if {[doit matchWords cryptWordsIndex key]} { return 1; } return 0; } ############################################################################# ####################### Main ################################################ ############################################################################# # Here's how this thing solves a cryptoquote. # Generate a Letter frequency count for the entire cryptoquote. # This will be stored in the global associate array variable: __ltrMatch__ # --- This will be used to find which letters could be mapped to "E". # --- I'm assuming that the letter E will be mapped to the letter # --- which shows up in the top three frequncy count. # --- "T" shows up the next frequent. # Generate a list of the words in the entire cryptoquote. # This will be stored in the global list variable: __theWords__ # # Then it generates stats for each word in the cryptoqote. # These stats will be kept in an associative array by cryptoquote word. # The global variable will be called theWordStats. # Example: theWordStats(foobar) # Then for each item of the array, there will be a list. # List item 0 will contain: the length of the word. # Example: theWordStats(foobar) {0} = 6 # List item 1 will contain: the letters and locations. # This will be an associative array. # Example: theWordStats(foobar) {1} # (a) = {4} # (b) = {3} # (f) = {0} # (o) = {1 2} # (r) = {5} # List item 2 will contain: the number of letters used to make the word. # Example: theWordStats(foobar) {2} = 5 - a,b,f,o and r. # List item 3 will contain: a flag which indicates if the word contains dupes. # Example: theWordStats(foobar) {3} = TRUE. it contains two o's. # List item 4 will contain: a flag which indicates the word contains doubles. # Example: theWordStats(foobar) {4} = TRUE. it contains a double o. # Set up any globals. #set cryptoQuote "ja'w xcvsa ajqk av wrjgg vsa x ckaako rkatvoz xrxpmeko xudkoajwkqkra"; # it's about time to sniff out a better network advertisement # this one works with the most frequent letter hint. #set cryptoQuote "icgvkh si mjwkjc ik ilsosuymg mi uk iuosl ashicgx cjusocgw kx icgx-gktc"; # seldom is anyone so spiritual as to strip himself entirely of self-love. # this one screws up if the most frequent letter hint is set. c turns out to map to e - not i. #set cryptoQuote "baaz hiemhisme gegwuun mcuu nag xtwm ma za uaib qcvadc nagd tcwz twe vhbgdcz hm agm"; # good instincts usually tell you what to do long before your head was figured it out. # this one screws up if the most frequent letter hint is set. c turns out to map to e - not a. #set cryptoQuote "wf wn r tvarf ewnidvfoxa fd ga di ona fd xdgdlk nzrvzabk bann fd ga di ona fd ajavkgdlk"; # it is a great misfortune to be of use to nobody scarcely less to be of use to everybody # this one works with the most frequent letter hint. #set cryptoQuote "ub wbcwhwcgua qgoj tk okae ykawubj ubc wb u okbok okae ogeewlwkbj py kaok vk xpko cpmb"; # An individual must be self reliant and in a sense self sufficient or else he goes down. set cryptoQuote "ys dng voyhnl uhuldisrct dng ylu isu hycrooy nv jnwruid"; # ah, you flavor everything you are the vanilla of society # this one screws up if the most frequent letter hint is set. u turns out to map to e - not n. #set cryptoQuote "h'q rtugaho pax wutseq mts rdkyxq te hn tekm mts khlxq he d fxppxu"; # I'd worship the ground you walked on if only you lived in a better neighborhood. set theLetters [list a b c d e f g h i j k l m n o p q r s t u v w x y z - ']; #set dictFileName "words.txt" ;# simple #set dictFileName "mydict.txt" ;# big set dictFileName "mydict2.txt" ;# the biggest one. set theCryptWords [list ]; # This is a sorted list of the words in the cryptoquote. set matchWords [list ]; set masterKey [list ]; # This is the master key used to decode the words. set theStackList [list ] # Generate the Letter Frequency Part. initTheLetterCounts ltrFreq # This shows the frequency of the letters in the quote. dumpTheLetterCounts #return #### Generate the master Key. # init master key foreach ltr $theLetters { lappend masterKey " " } # Set the dash and apostrophe set masterKey [lreplace $masterKey 26 26 "-" ] set masterKey [lreplace $masterKey 27 27 "'" ] #### set which letter corresponds to e. # Determine which letter shows up the most. set mostCommonLetter [findMostCommonLetter] # Use this to override the most common letter. set mostCommonLetter "u" # set the most common letter in the masterKey set pos [lsearch $theLetters $mostCommonLetter] set masterKey [lreplace $masterKey $pos $pos "e" ] # use these to set additional hints. #set masterKey [lreplace $masterKey 0 0 "v" ] ; #letter for a. #set masterKey [lreplace $masterKey 1 1 "-" ] ; #letter for b. #set masterKey [lreplace $masterKey 2 2 "v" ] ; #letter for c. #set masterKey [lreplace $masterKey 3 3 "v" ] ; #letter for d. #set masterKey [lreplace $masterKey 4 4 "v" ] ; #letter for e. #set masterKey [lreplace $masterKey 5 5 "v" ] ; #letter for f. #set masterKey [lreplace $masterKey 6 6 "v" ] ; #letter for g. #set masterKey [lreplace $masterKey 7 7 "v" ] ; #letter for h. #set masterKey [lreplace $masterKey 8 8 "t" ] ; #letter for i. #set masterKey [lreplace $masterKey 9 9 "v" ] ; #letter for j. #set masterKey [lreplace $masterKey 10 10 "e" ] ; #letter for k. #set masterKey [lreplace $masterKey 11 11 "v" ] ; #letter for l. #set masterKey [lreplace $masterKey 12 12 "v" ] ; #letter for m. #set masterKey [lreplace $masterKey 13 13 "-" ] ; #letter for n. #set masterKey [lreplace $masterKey 14 14 "v" ] ; #letter for o. #set masterKey [lreplace $masterKey 15 15 "v" ] ; #letter for p. #set masterKey [lreplace $masterKey 16 16 "m" ] ; #letter for q. #set masterKey [lreplace $masterKey 17 17 "v" ] ; #letter for r. #set masterKey [lreplace $masterKey 18 18 "v" ] ; #letter for s. #set masterKey [lreplace $masterKey 19 19 "v" ] ; #letter for t. #set masterKey [lreplace $masterKey 20 20 "v" ] ; #letter for u. #set masterKey [lreplace $masterKey 21 21 "h" ] ; #letter for v. #set masterKey [lreplace $masterKey 22 22 "v" ] ; #letter for w. #set masterKey [lreplace $masterKey 23 23 "v" ] ; #letter for x. #set masterKey [lreplace $masterKey 24 24 "v" ] ; #letter for y. #set masterKey [lreplace $masterKey 25 25 "v" ] ; #letter for z. dumpKey $masterKey #return; # Generate the list of words in the cryptoquote. findWords; # theWords list is now filled out and sorted largest word first. # sort the words based on the easiest to decode. # Collect stats for each word. set i 0 foreach val $theCryptWords { # Determine the length of the word. INDEX 0 = Length lappend theCryptWordStatArray($val) [lengthOfWord $val] # Determine the letters and their locations. INDEX 1 = Letter Map findLettersInWord $val [lindex $theCryptWordStatArray($val) 0] locArray$i lappend theCryptWordStatArray($val) locArray$i # Determine the number of letters in the word. INDEX 2 = Number of Letters lappend theCryptWordStatArray($val) [array size [lindex $theCryptWordStatArray($val) 1]] # Determine if the word contains dupes. INDEX 3 = Dupe Flag lappend theCryptWordStatArray($val) [containsDupes [lindex $theCryptWordStatArray($val) 1]] # Determine if the word contains doubles. INDEX 4 = Double Flag lappend theCryptWordStatArray($val) [containsDoubles $val [lindex $theCryptWordStatArray($val) 0]] incr i } # Dump each word of the cryptoquote followed by its stats. dumpCryptWordStats #return; # Determine timing info. set t1 [clock clicks] after 5000 ;# delay 5 seconds. set t2 [clock clicks] set sysClicksPerSecond [expr ($t2-$t1)/5] ;# diff of timers divided by 5 seconds. # # Now start looking through the dictionary finding words which are a possible match. # Start finding match words using the intial key and the first cryptoword. # set t1 [clock clicks] set cryptWordsIndex 0 if {[doit matchWords cryptWordsIndex masterKey] == 1} { puts "" puts "The final decoded key is:" dumpKey $masterKey puts "" puts "And the cryptoquote decodes to:" foreach val [split $cryptoQuote {}] { if {[string match $val " "] } { puts -nonewline " " } else { foreach val2 [split $val {}] { #puts "$val2 [lsearch $theLetters $val2]" puts -nonewline [lindex $masterKey [lsearch $theLetters $val2]] } } } } set t2 [clock clicks] puts "" puts "processing time is: [expr ($t2-$t1)/$sysClicksPerSecond] seconds."