Create a new Tcl file named keyedListLib.tcl
and then
add the appendKeyedPair
procedure from the lecture.
Create another Tcl file named keyedListTest.tcl
and
write code to test that the procedure works.
A good test would allow the user add elements and see the changes. You
can use the -textvariable
option to the label
command to view the contents of the keyed list with code like this:
set keyList {}
set w [label .l_keyList -textvariable keyList -width 50]
grid $w -row 1 -column 1
A button command to add a pair of elements to the list might look like this:
set key 1
set w [button .b_addPair -text "Add Pair" \
-command "set keyList [appendKeyedPair $keyList [incr key] $key]"]
grid $w -row 2 -column 1
This command adds a pair of elements where the key and value are identical, and they increase each time you click the button.
Put these snippets together into a test application and confirm
that it works and that the appendKeyedPair
application works.
The button code is not correct. Think about when the substitution is happening and where when it should be happening. Fix the bug and test it again.
When it works, it should look like this:
-command
option to the button.
Hardcode the name of the keyed list to modify.
Notice that you don't need to worry about when substitutions happen with the procedure.
getValue
procedure to keyListLib.tcl
and
add a test for this to the keyedListTest.tcl
application.
Each time you click the button to test the getValue
procedure
it should display the key and the value returned, and step to the next key.
After a few button clicks, it should look like this:
The getValue
procedure in the lecture has a bug.
Here is the code. What happens when the first occurance of the searched for key is a value, not a key?
For instance, consider a keyed list like this:
set keyed {A 1 B 2 C D D 4}
getValue $keyed D
proc getValue {list key} {
set pos [lsearch $list $key]
if {($pos >= 0) && (($pos & 1) == 0)} {
return [lindex $list $pos+1]
}
return ""
}
The lsearch
command will find the first occurance
of a D, which is the value associated with the key "C".
the getValue
then returns an empty string, rather
than searching to see if that key exists later in the keyed list.
Here is code that uses a for
loop with no body to
test the results of the lsearch
and continue looping
if it's not a valid position.
################################################################
# proc getValue {list key}--
# Retrieve the value associated with a key
# Arguments
# list The keyed list
# key The key to search for
#
# Results
# No side effects.
#
proc getValue {list key} {
set start 0
for {set pos [lsearch $list $key]} \
{($pos >= 0) && (($pos & 1) == 1)} \
{set pos [lsearch -start $pos+1 $list $key]} {
}
if {$pos >= 0} {
return [lindex $list $pos+1]
} else {
return ""
}
}
The while
command would make this code simpler. Look up
the while
command and modify the code to use that instead.
deleteElement
procedure to the keyedListLib.tcl
file and then add a test similar to the testGetVal
procedure to
test it. You'll need new global variables.
replaceValue
procedure to the keyedListLib.tcl
file and then add a test similar to the testGetVal
procedure to
test it. You'll need new global variables.
The testReplaceValue
procedure can increment through a
set of numbers, replacing a the value associated with a key with a
value one lower.
This is an example of a test that checks that a procedure can work, but
does not test that the procedure is robust. The bad getValue
procedure passes this test. Change the test in this solution to use a value
one higher instead of one lower and see if the original getValue
procedure still passes the test.
After clicking Add Pair 4 times, Test Replace twice, and Test Delete once, it should look like this:
set if [open $fileName r]
set d [read $if]
close $if
set datalist [split $d \n]
This functionality can be put into a procedure that looks like this:
################################################################
# proc readFileAsList {fileName}--
# read in a file and convert it to a list delimited by newlines
# Arguments
# fileName Name of the file to read
#
# Results
# no side effects
#
proc readFileAsList {fileName} {
set if [open $fileName r]
set d [read $if]
close $if
return [split $d \n]
}
Here's a file of gem colors that starts like this:
Black Obsidian Onyx
Blue Aquamarine Blue Lace Agate Iolite Lapis Lazuli Sapphire Tanzanite Topaz Turquoise
write an application to read in this file and create a keyed list with each gem as a key and the appropriate color as the value.
You'll need to use nested loops to solve this.
If you use puts
to display the gem color list, it should start like
this:
Obsidian Black Onyx Black Aquamarine Blue {Blue Lace} Blue Iolite Blue
January Garnet
February Amethyst
March Aquamarine
April Diamond
Add some code to the previous program to read this file, convert it to a list and then generate a display that looks like this:
You can use the getValue
function from keyedListLib.tcl
file and the gem color keyed list to find out the color of each birthstone.
16:02:14 7723 2999 192.168.1.2 -> mail.emich.edu 443
16:02:19 534 2183 192.168.1.2 -> 0.channel41.facebook.com 80
16:02:40 20247 1098 192.168.1.249 -> 204.176.49.116 80
16:03:17 534 2183 192.168.1.2 -> 0.channel41.facebook.com 80
16:04:15 534 2181 192.168.1.2 -> 0.channel41.facebook.com 80
16:04:52 460 882 192.168.1.231 -> 204.176.49.2 80
This report shows
It would be useful to know how many bytes were transferred by which IP addresses.
To do that, we could create a keyed list that uses the local IP address as the key.
An application can have more than one keyed list. For instance, we can have one keyed list of values for the sizes of packets we've read and one for the quantities of bytes written.
The trick to adding new data to the list is knowing what data is new and what data belongs to an IP address that's already been used.
The getValue
procedure will return an empty string when
you request a value for a key that isn't in the list. We can use that
behavior to determine whether or not a key has been added.
These variables are used in this sample code:
localIP
in
inputByLocalIP
inTot
set inTot [getValue $inputByLocalIP $localIP]
if {$inTot eq ""} {
set inTot 0
set inputByLocalIP [appendKeyedPair $inputByLocalIP $localIP $inTot]
}
set inTot [expr {$in + $inTot}]
set inputByLocalIP [replaceValue $inputByLocalIP $localIP $inTot]
Extend this code to read the file and generate a report of IP addresses, total bytes read by that IP address and total bytes written by that IP address.
The output should resemble this:
192.168.1.2 8829671 1173032
192.168.1.249 25348 4973
192.168.1.231 21367 3130
192.168.1.250 1590 2688