File I/O and Associative Array

  1. An associative array is a good tool to use to map one value to another.

    For instance you could map an IP address to a site name with code like this:

    
    set dns(127.0.1.1) localhost
    set dns(64.233.169.99) www.google.com
    set dns(199.111.20.4) www.emu.edu
    

    You could then display a name instead of a number with code like:

    
     foreach address {127.0.0.1 64.233.169.99 199.111.20.4} {
       puts "ADDRESS: $address IS $dns($address)"
     }
    

    We can simulate a coin toss with the expr command and its rand() function like this:

    
    set result [expr {int(rand()*2)}]
    

    The value in result will be either a 1 or a 0.

    To report the results to a user, we don't want to say 1 or 0, we want to say "heads" or "tails".

    This code will display heads for a single coin toss:

    
    set text(0) "tails"
    set text(1) "heads"
    set result [expr {int(rand()*2)}]
    puts "$text($result)"
    

    Add a loop to this code to display the results of 10 coin tosses.

    solution

  2. We can also use an associative array for grouping related sets of data.

    Once we've got a coin tossing application, we can make a simple histogram like this:

    
    set counts(0) 0
    set counts(1) 0
    for {set i 0} {$i < 10} {incr i} {
      set result [expr {int(rand()*2)}]
      incr counts($result)
    }
    parray counts
    

    Batman once captured TwoFace by getting him to agree to surrender if the coin-toss came up on edge.

    Assume that there's a 1 in 100 chance of a coin toss landing on edge.

    Rework the code above to generate a histogram that shows about 1 in 100 tosses landing on edge.

    The results for 1000 tosses should look something like this:

    
    counts(edge)  = 8
    counts(heads) = 500
    counts(tails) = 492
    
    solution

  3. Using an associative array as a lookup-table is an easy way to implement simple ciphers.

    For example, this code implements part of a simple caesar cipher:

    
    set cipher(a) b
    set cipher(b) c
    set cipher(c) d
    set cipher(d) e
    set cipher(e) f
    set cipher(f) g
    
    set str ""
    foreach ch [split "beef" {}] {
      append str $cipher($ch)
    }
    puts $str
    

    All those set cipher... lines are too long to type.

    Use the trick in lesson exercise 4 of the encyrption lab to create a caesar cipher in a loop.

    In order to test this, we'll also need an entry in the cipher associative array for the space.

    This is tricky, since a space is a separator for Tcl. This code won't work:

    
      set cipher( ) " " 
    

    We need to escape the space to force Tcl to use it as the index like this:

    
      set cipher(\ ) " " 
    

    solution

  4. We can use a foreach loop to build a transpositional cipher.

    This problem reduces to "taking N things 1 at a time".

    In real world terms, you can think of it as putting a single alphabet worth of Scrabble letters into hat, and pulling them one at a time. The first letter you pull will be the cipher for "A", the second letter will be for "B", etc.

    In programming terms, we can do this by creating a list, selecting on element from the list at random, and then removing that element from the list.

    Given a list of letters in alphabetic order (alphabet and a list of unused letters (unused, the code to fill the associative array looks like this:

    
      foreach letter $alphabet {
        set pos [expr {int([llength $unused] * rand())}]
        set cipher($letter) [lindex $unused $pos]
        set unused [lreplace $unused $pos $pos]
      }
    

    Extend this snippet into a small application that will display the enciphered version of the string "This is a Test".

    solution

  5. If you run the previous application a few times, you'll notice that the output string changes.

    That's because the Tcl random number generator doesn't always deliver the same random numbers.

    Numeric pseudo-random number generators always generate the same sequence of numbers based on a given starting location. To avoid having programs always use the same random numbers, most applications (Tcl included) will seed the random number generator with a different starting value.

    Most applications use the time of day in seconds as the seed.

    Sometimes you need to use the same set of random numbers. For instance, if you encrypt a message using a transposition cipher that was created on your system, you will want me to be able to generate the same cipher at my end to decipher the message.

    Tcl provides a function you can call to seed the random generator with a known value. The srand function does this. It's an arithmetic function invoked in the expr command.

    The srand() function sets the random number generator start point. The Tcl interpreter calls this with the current time-in-seconds when it starts. This ensures that you always get a different pattern of random numbers when you run an application.

    A line like this will force the random number generator to start from 0.

    
    expr srand(0)
    

    Modify the previous code to read arguments from the command line. The two arguments will be the seed to define the transposition cipher and the string to encrypt.

    Using the same plaintext, but different seeds should look like this:

    
    tclsh sol5.txt 1 "This is a test"
    yJXh Xh g wlhw
    tclsh sol5.txt 2 "This is a test"
    quTl Tl n SxlS
    

    solution

  6. A useful application will both encrypt and decrypt messages.

    Embrace and Extend the previous solution to include three procedures to create a cipher, encrypt or decrypt a message.

    The new procedures will look like this:

    
    proc makeCipher {cipherArrayName decipherArrayName key} {...}
    proc encrypt {cipherArrayName plainText} {...}
    proc dencrypt {decipherArrayName cipherText} {...}
    

    Note the fact that the procedures are using call by name rather than call by value. When passing an associative array directly to a procedure, you must use Call by Name.

    The application should accept a "-d" or "-e" flag on the command line to choose whether the message should be encrypted or decrypted, the key to use, and a message. Invoking the application will look like this:

    
    tclsh sol6.tcl -e 1 test
       returns "wlhw"
    tclsh sol6.tcl -d 1 wlhw
      returns "test"
    

  7. A more useful application would let you define a file to read on the command line, and would print out the encrypted text.

    Change the previous application to open and read a file, rather than just print text from the command line.

    No solution for this one. You've got all the commands you need to write this application on your own.

    Copyright Clif Flynt 2009