Tcl: best way to test for empty list - tcl

Sorry, that's a very trivial question, but it seems to be so trivial that I can't find any recommendation: What is the best way in Tcl to test whether a list is empty? I would go for if {$myList eq ""} ..., or is there a better way? Thanks for your help!

If you know you have a list (because you've made it that way or are using other operations that treat the value as such), the best empty check is looking at the llength of it to see if it is zero. I usually prefer to use the result as if it was boolean, like this, but comparing to zero also works just fine.
if {[llength $theList]} {
puts "the list is NOT empty"
} else {
puts "the list IS empty"
}
Yes, this will incur the cost of converting the value to a list, but that conversion is cached within the value itself (this is a type of “internal representation”) so the next operation to use it as a list is then much faster as it doesn't have to reparse anything.

I just want to share some test code which shows that if {$myList eq ""}... can be much slower than if {![llength $myList}} ...:
set repeats 10
foreach n {10 100 1000 10000 20000} {
puts "n = $n"
puts [time {
set l [list]
for {set i 0} {$i < $n} {incr i} {
lappend l $i
}
} $repeats]
puts [time {
set l [list]
for {set i 0} {$i < $n} {incr i} {
lappend l $i
if {![llength $l]} {
puts "empty (llength)"
}
}
} $repeats]
puts [time {
set l [list]
for {set i 0} {$i < $n} {incr i} {
lappend l $i
if {$l eq ""} {
puts "empty (eq)"
}
}
} $repeats]
}
Here is the output; for each value of n: baseline (no check), check with llength, check with eq:
n = 10
5.0 microseconds per iteration
14.1 microseconds per iteration
14.0 microseconds per iteration
n = 100
40.6 microseconds per iteration
44.0 microseconds per iteration
105.8 microseconds per iteration
n = 1000
374.4 microseconds per iteration
440.7 microseconds per iteration
6240.1 microseconds per iteration
n = 10000
3534.4 microseconds per iteration
4485.8 microseconds per iteration
667206.0 microseconds per iteration
n = 20000
7576.7 microseconds per iteration
9051.9 microseconds per iteration
2761285.3 microseconds per iteration
I would guess that the solution using if {$l eq ""}... is very slow since does a conversion from a list to a string every time the check is done. Or is there a different explanation?

Related

How to create an efficient permutation algorithm in Tcl?

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
}

Loop Calling Processes and assigning values in Tcl

I am currently using a loop to iterate values into a process (neighbors) in Tcl.
for {set i 0} {$i < $val(nn)} {incr i} {
for {set j 0} {$j < $val(nn)} {incr j} {
$ns at 0.0 "neighbors $node($i) $node($j) $i $j"
}
}
The above allows all the values I need to go into the process. However, inside the process some values are assigned (to a list) and no longer needed to be looped through. A brief snippet from the process:
} else {
puts "..... Do nothing .... $nd1 - $nd2"
if {([lsearch -exact $heads $nd1] != -1) || ([lsearch -exact $members $nd1] != -1) } {
incr $nd1
}
}
This is the end of a loop in the process. The puts is just a marker, but it checks if an item is contained in either of 2 two lists. If it is in either list, increment it, and move on the next possible value. That value needs to no longer be checked/looped through because it has already been put inside a list.
How do I prevent the value from continuing to be used? The 'process calling' loop will ALWAYS override what happens in the process, so even assigned values will continue to be used. Is there a different way to call processes in Tcl? Or at least, a different way to feed values to a process in Tcl? Or I guess, pull them out?
As a note, here is my process that I want to feed to (n1 and n2 are memory locations, nd1 nd2 are actual number identifiers)
set heads {}
set members {}
proc neighbors {n1 n2 nd1 nd2} {
global heads members bool allNodes
puts "Members --- $members"
puts "heads --- $heads"
if {([lsearch -exact $heads $nd1] == -1) && ([lsearch -exact $members $nd1] == -1) } {
lappend heads $nd1
set currentHead $n1
puts "Current Head: $currentHead $n1 $nd1"
} else {
puts "..... Do nothing .... $nd1 - $nd2"
if {$nd1 in $heads || $nd1 in $members} then return
#here I want it to go to the next nd1 value and NEVER use it again if it
#has already been processed
}
#Otherwise, do neighbor test with nd2
If the neighbors operation is symmetric (often true), you do the check of everything against everything else like this:
for {set i 0} {$i < $val(nn)} {incr i} {
for {set j $i} {$j < $val(nn)} {incr j} {
$ns at 0.0 [list neighbors $node($i) $node($j) $i $j]
}
}
with the inner loop starting at $i (or [expr {$i - 1}] if you don't want to check things against themselves) instead of zero. This ensures that $j is always not less than $i, effectively (approximately) halving the amount of work you need to do.
(Style point: it's considered good style to use [list ...] to prepare code for later execution, and not "..."; the former is more efficient, and the latter has some ugly cases when working with values that may have spaces in.)
What you can't do (at least not easily; there might be a method to do it) is use the result of the neighbors operation to stop future calls to neighbors from happening, as you've already scheduled them to occur by the time any of them can possibly express an opinion. It's probably easier in your case to keep a state variable and check against it for the option to do an early reject. That's a fundamental limitation of using delayed command invocation instead of direct: passing values back to do things like skipping future iterations is quite difficult (and downright tricky before Tcl 8.6; that has coroutine which simplifies the task a lot).
It feels like you want to do this:
proc neighbors {n1 n2 nd1 nd2} {
global heads members bool allNodes
if {$nd1 in $heads || $nd2 in $members} then return
... do the neighborly stuff ...
}
See https://tcl.tk/man/tcl8.6/TclCmd/expr.htm#M15 for the in operator.

For loop increment by a non-integer in TCL

I want to implement the following C code in TCL:
Float power_pitch = 8
float offset = 7.5
float threshold = 100
for(i=power_pitch+offset ; i<= threshold ; i += power_pitch)
I want to implement above forloop in TCL. I have tried the following code in TCL:
set power_pitch 8
set offset 7.5
set threshold 100
for { set i [expr $power_pitch + $offset] } { $i <= $threshold } { incr i $power_pitch}
but when I execute above code I get the following error:
expected integer but got "15.5 "
while executing incr i $power_pitch
Could you help me to implement above forloop in TCL?
The incr command only works with integers. Otherwise, use:
set i [expr {$i + $power_pitch}]
The for command itself won't mind. (Be aware of float rounding issues; they're not Tcl-specific, but can hit with anything that isn't an integer multiple of a power of 2…)
Donal has already provided the answer to the question, I'd just like to make an observation in two points about the for command.
for is very nearly free-form
while an integral counting loop is a typical use of for, it's by no means the only option
The for command has the synopsis
for start test next body
where start, next, and body are command strings (i.e. appropriate as an argument to eval; they can be empty, contain single commands, or be full scripts) and test is a boolean expression string (i.e. appropriate as an argument to expr and evaluating to something that is or can be coerced into a boolean value).
Usually, start is used to set up for test and body, and next is supposed to bring the state incrementally closer to having test return a false value, but that's just a convention, not a requirement. The following are perfectly valid (but rather smelly) invocations:
for {set n 0 ; puts -nonewline X} {[incr n] < 5} {puts -nonewline X} {
puts -nonewline [string repeat - $n]
}
for {set f [open foo.txt] ; set n 0} {$n >= 0} {puts $line} {
set n [chan gets $f line]
}
Give for any combination of command strings and boolean expression, and it will run. It might execute its body forever or not even once, but it will run. Don't limit yourself to for {set i 0} {$i < 10} {incr i} {...} invocations, that's 1950s thinking.
Even if you just want to use it for counting loops, there are still lots of options, for instance:
for {set i 0} {$i < $limit} {incr i} {...} ;# simple increment
for {set i 0} {$i < $limit} {incr i $n} {...} ;# stepping increment/decrement
for {set i 0} {$i < $limit} {incr i $i} {...} ;# doubling increment
for {set i 0} {$i < $limit} {set i [expr {...}]} {...} ;# arbitrary change
Free your mind, the rest will follow.
Documentation: chan, expr, for, incr, open, puts, set, string

expected integer but got "floating point number" error

I try to write a very simple program in TCL using list.
Below is the list
list { 1 2 3 4 5 6 1.5 7 }
Below is my code
set sum 0
for {set i 0} {$i < [llength $list]} {incr i} {
incr sum [lindex $list $i]
}
puts $sum
On executing the above program I am getting the below error due to floating point value of 1.5 in the list
expected integer but got "1.5"
(reading increment)
invoked from within
"incr sum [lindex $list $i]"
I searched on internet and could not find anything relevant.
Please advise how do I handle the floating point value?
While using incr command, variable must have value that can be interpreted as a an integer. See tcl wiki.
If variable is a non-integral real number, [incr] could not be used, but [set] could:
set sum 0
for {set i 0} {$i < [llength $list]} {incr i} {
set sum [expr {$sum + [lindex $list $i]}]
}
puts $sum
Omsai's answer should solve your problem, but a cleaner solution is to use foreach:
set sum 0
foreach n $list {
set sum [expr {$sum + $n}]
}
puts $sum
Summing up a list of numeric values can also be done with the ::tcl::mathop::+ command:
::tcl::mathop::+ {*}$list
This looks more complicated that it is. The + command isn't available in the regular namespace, so you need to specify where it comes from (the ::tcl::mathop namespace). The command expects to get each operand as a separate argument, so if they are in a list you need to expand that list using the {*} prefix.
foreach and the various mathop commands are documented here: foreach, mathop.
(Note: the 'Hoodiecrow' mentioned in the comments is me, I used that nick earlier.)
Tcl gives an error if you will try
incr a 1.5
you have to change the logic.
clearly you want to add all the numbers in the list. and answers are easy and many. But i will give you the shortest way:
set l { 1 2 3 4 5 6 1.5 7 }
set sum [expr [join $l +]]
NO LOOPING REQUIRED.

how to increment variable by 0.5 in using for loop?

for {set i 0} {$i < 5} {incr i} {
puts "I inside first loop: $i"
}
Is it possible to increment i by .5 instead of 1 ?
Now the above code is providing below output:
i inside first loop: 0
i inside first loop: 1
i inside first loop: 2
i inside first loop: 3
i inside first loop: 4
but I need something like this:
I inside first loop: 0
I inside first loop: .5
I inside first loop: 1
... so on
Tcl's incr command only handles integer values. The recommended way of getting a loop value that steps by some fractional value is to use a integer loop counter and then compute the fractional value from it:
for {set i_int 0} {$i_int < 5} {incr i_int} {
set i [expr {$i_int * 0.5}]
puts "I inside first loop: $i"
}
This is important when the fractional step is not a simple multiple of a power of two; while 0.5 can be represented exactly in binary floating point arithmetic (it's 2-1 after all) 0.1 can't (just as 1/3 can't be written exactly in a finite number of decimal places).
By default, incr increments by 1 unit when no specific number is mentioned.
incr i 2
will increment i by 2 on each iteration.
incr i -1
will decrement i by 1 on each iteration.
You can thus change the number to be whatever you need it to be.
The only problem is that you can only increment by an integer. So you'll have to use something else for the 0.5. You can use expr perhaps?
for {set i 0} {$i < 5} {set i [expr {$i+0.5}]} {
puts "I inside first loop: $i"
}
EDIT: Actually, Donal's answer is better since it doesn't have the rounding errors :)
for {set i 0} {$i < 5} {incr i} {
puts "I inside first loop: [expr $i*0.5]"
}