I have a list as belows:
test = {a[2] r[5] f[6] t[8]} {d[32] g[66] k[88]} {w[2] e[33]}
The size of the test list is variable and can have any number of elements.
I want the total to be calculated as:
total = 4 + 3 + 2 = 9
I am trying something like this but it gives an error.
set result ""
set index1 ""
foreach index1 [llength $test] {
set value [llength [lindex $test $index1]]
result1 = expr [$value + $result1]
puts $result1
}
It gives the error below:
invalid command name "0"
Thanks.
To do something for every member of a list, always use foreach, and incr is great for various sorts of counting things. In this case:
set total 0; # In case the input list is empty
foreach sublist $test {
incr total [llength $sublist]
}
# The value you are looking for is in the “total” variable
Related
I have written the following proc in tcl which gives a permutation of the set {1, 2, ..., n} for some positive integer n:
proc permu {n} {
set list {}
while {[llength $list] < $n} {
set z [expr 1 + int(rand() * $n)]
if {[lsearch $list $z] == -1} {
lappend list $z
}
}
return $list
}
I have used some code snippets from tcl-codes which I found on other web sites in order to write the above one.
The following part of the code is problematic:
[lsearch $list $z] == -1
This makes the code quite inefficient. For example, if n=10000 then it takes a few seconds
until the result is displayed and if n=100000 then it takes several minutes. On the other hand, this part is required as I need to check whether a newly generated number is already in my list.
I need an efficient code to permute the set {1, 2, ..., n}. How can this be solved in tcl?
Thank you in advance!
Looking up a value in a list is a problem that grows in runtime as the list gets larger. A faster way is to look up a key in a dictionary. Key lookup time does not increase as the size of the dictionary increases.
Taking advantage of the fact the Tcl dictionary keys are ordered by oldest to most recent:
proc permu {n} {
set my_dict [dict create]
while {[dict size $my_dict] < $n} {
set z [expr 1 + int(rand() * $n)]
if {![dict exists $my_dict $z]} {
dict set my_dict $z 1
}
}
return [dict keys $my_dict]
}
This fixes the problem of slow list lookup, but the random number z is now the limiting factor. As the dict size approaches $n you need to wait longer and longer for a new value of z to be a unique value.
A different faster approach is to first assign the numbers 1 to n as value to randomized keys in a dict. Next, you can get values of each sorted key.
proc permu2 {n} {
# Add each number in sequence as a value to a dict for a random key.
set random_key_dict [dict create]
for {set i 1} {$i <= $n} {incr i} {
while {1} {
set random_key [expr int(rand() * $n * 100000)]
if {![dict exists $random_key_dict $random_key]} {
dict set random_key_dict $random_key $i
break
}
}
}
# Sort the random keys to shuffle the values.
set permuted_list [list]
foreach key [lsort -integer [dict keys $random_key_dict]] {
lappend permuted_list [dict get $random_key_dict $key]
}
return $permuted_list
}
I am having trouble finding a way to calculate the median and average of a list of numbers and the resources online seem to be really limited with Tcl. So far I managed to only print the numbers of the list.
Your help would be greatly appreciated.
proc ladd {l} {
set total 0
set counter 0
foreach nxt $l {
incr total $nxt
incr counter 1
}
puts "$total"
puts "$counter"
set average ($total/$counter)
puts "$average"
}
set a [list 4 3 2 1 15 6 29]
ladd $a
To get the average (i.e., the arithmetic mean) of a list, you can just do:
proc average {list} {
expr {[tcl::mathop::+ {*}$list 0.0] / max(1, [llength $list])}
}
That sums the values in the list (the trailiing 0.0 forces the result to be a floating point value, even if all the added numbers are integers) and divides by the number of elements (or 1 if the list is empty so an empty list gets a mean of 0.0 instead of an error).
To get the median of a list, you have to sort it and pick the middle element.
proc median {list {mode -real}} {
set list [lsort $mode $list]
set len [llength $list]
if {$len & 1} {
# Odd number of elements, unique middle element
return [lindex $list [expr {$len >> 1}]]
} else {
# Even number of elements, average the middle two
return [average [lrange $list [expr {($len >> 1) - 1] [expr {$len >> 1}]]]
}
}
To complete the set, here's how to get the mode of the list if there is a unique one (relevant for some applications where values are selected from a fairly small set):
proc mode {list} {
# Compute a histogram
foreach val $list {dict incr h $val}
# Sort the histogram in descending order of frequency; type-puns the dict as a list
set h [lsort -stride 2 -index 1 -descending -integer $h]
# The mode is now the first element
return [lindex $h 0]
}
I'll leave handling the empty and non-unique cases as an exercise.
I have a input file name "input.dat" with the values as:
7 0
9 9
0 2
2 1
3 4
4 6
5 7
5 6
And I want to add/subtract any number from column 2 by converting it into a list using Tcl Script. I have written the Tcl Script as follows:
set input [open "input.dat" r]
set data [read $input]
set values [list]
foreach line [split $data \n] {
if {$line eq ""} {break}
lappend values [lindex [split $line " "] 1]
}
puts "$values-2"
close $input
But the output comes out to be: 0 9 2 1 4 6 7 6-2
Can anybody help me, how to fix this problem ? or what is the error in the script ? It's also helpful if anybody can help me with a correct script.
I'm still not 100% sure what you want, but the options all seem to be solvable with the lmap command, which is for applying an operation to each element of a list.
Here's how to concatenate each element with -2:
set values [lmap val $values {
string cat $val "-2"
}]
Here's how to subtract 2 from each element:
set values [lmap val $values {
expr {$val - 2}
}]
puts will treat it as a string, you'll have to use [expr $val - 2]
NOTE: If it doesn't work, it is possible your input list is a string not int or float (Depends on how the values were read). In this case you can use:
scan $val %d tmp
set newval [expr $tmp - 2]
puts $newval
This will convert your string to int before applying mathematical expressions. You can similarly convert to float by using %f in scan instead of %d
I am new to Tcl so i am learning the basics. I wrote a function to calculate the sum of an array and print its elements. Here is the code
proc print_sum { tab } {
set s 0
foreach key [array names tab] {
puts "${key}=$tab($key)"
incr $s $tab($key)
}
puts "the sum = $s"
}
Here is how I called it:
print_sum tab
and I created the tab like this:
set tab("1") 41
set tab("m2") 5
set tab("3") 3
set tab("tp") 9
set tab("2") 7
set tab("100") 16
But the output is wrong! It outputs 0 instead of the actual sum and it does not output any element. But when I used the code directly without writing it in a function, it works.
The issue is that you're passing the string "tab" to the proc, and then you store that in the variable name "tab". This is just a plain variable, not an array, so when you do array names tab, you get an empty list back. The foreach loop loops zero times, and the sum is still zero.
You need to use the upvar command to link to the "tab" array in the caller's stack frame:
proc print_sum { arrayName } {
upvar 1 $arrayName a ;# "alias" the array in the caller's scope
set s 0
foreach key [array names a] {
puts "${key}=$a($key)"
incr s $a($key) ;# increment the *variable* not the *variablevalue*
}
puts "the sum = $s"
}
print_sum tab
outputs
"tp"=9
"100"=16
"1"=41
"2"=7
"3"=3
"m2"=5
the sum = 81
Hi I need to add numbers in a column till pattern matches and then to start adding numbers after pattern matches, for example:
start 1
start 2
start 3
pattern
start 4
start 5
start 6
I need to have sum as 6 till pattern and 15 after pattern separately, i tried regexp start but it adds all the numbers in 2nd column irrespective of 'pattern', i know sed works, but i need in tcl-regexp only
With minimal change to your current code and your current attempt/method to reach the desired outcome, this is what I suggest:
set sum1 0
set sum2 0
set ind 0
set skip true
while {![eof $file]} {
# Notice the change of $x to x here
gets $file x
if {[regexp start $x]} {
set ind [lindex $x 1]
# Depending on $skip, add the number to either sum1 or sum2
if {$skip == "true"} {
set sum1 [expr $sum1 + $ind]
} else {
set sum2 [expr $sum2 + $ind]
}
}
if {[regexp pattern $x]} {
set skip "false"
}
}
puts $sum1
puts $sum2
Though, I would use the following to make things a bit simpler:
set sum 0
while {[gets $file x] != -1} {
# if there line has "pattern, then simply print the current sum, then resets it to zero
if {[regexp pattern $x]} {
puts $sum
set sum 0
} elseif {[regexp {start ([0-9]+)} $x - number]} {
# if the line matches 'start' followed by <space> and a number, save that number and add it to the sum
# also, I prefer using incr here than expr. If you do want to use expr, brace your expression [expr {$sum+$ind}]
incr sum $number
}
}
# puts the sum
puts $sum