njamescouk
njamescouk

Reputation: 179

conceptual error with control flow in Tcl

I have a collection of things, some of them empty.

I want to form a collection of non empty things, separated by separator.

this is essentially what I do in c++, but fails with any and all combinations of $ signs etc. etc.

I've already got a work around thanks, I'd like to know why and how this fails.

set q1 "a"
set q2 ""
set q3 "c"
set q4 d
set q5 ""    

set answer ""

set needSeparator 0
foreach { var } { 
    q1 q2 q3 q4 q5 
    } {
    if { $var ne "" } {
        if {$needSeparator} {
            append answer " separator "
        }
        append answer $var
        set needSeparator 1
    }
}

# expecting answer to be "a separator c separator d"
puts $answer

edit 2021-09-14

Following on from @Shawn

<         if { $var ne "" } { 
--- 
>         if { [set elem [set $var]] ne "" } { 

<         append answer $var 
--- 
>         append answer $elem 

on my effort does the job.

not quite sure how set is doing the dereferencing there but that's one for another day.

this was a minimal example so the rather more funky answers are too involved for someone trying to program in c++ :-). The qNs are horrible and come from different places, but the final code example is sweet and works translated back into my real problem - see below

# build compound SELECT 

set q1 [select $mapText "final_text"] 
set q2 [select $parish "parish"] 
set q3 [select $la "local_authority"] 
set q4 [sqSelect $five00] 
set q5 ""     
if {$nation ne "All"} { 
    set q5 {SELECT pin_id AS id FROM gazetteer WHERE nation = '} 
    append q5 $nation "'\n" 
} 
 
set compound {} 

foreach clause {q1 q2 q3 q4 q5} { 
    if {[set q [set $clause]] ne ""} { 
        lappend compound $q 
    } 
} 

if {[llength compound] == 0} { return ""} 
 
set res "WITH pinIds AS (\n" 
append res [join $compound "INTERSECT\n    "] ")\n" 

Thanks for your help

Upvotes: 1

Views: 85

Answers (2)

glenn jackman
glenn jackman

Reputation: 246877

Not an answer, but a response to the array for comments on Shawn's answer

A Tcl implementation of array for

proc array_for {vars arrayName body} {
    if {[llength $vars] != 2} {
        error {array for: "vars" must be a 2 element list}
    }
    lassign $vars keyVar valueVar

    # Using the complicated `upvar 1 $arrayName $arrayName` so that any
    # error messages propagate up with the user's array name
    upvar 1 $arrayName $arrayName \
            $keyVar    key \
            $valueVar  value

    set sid [array startsearch $arrayName]
    # If the array is modified while a search is ongoing, the searchID will
    # be invalidated: wrap the commands that use $sid in a try block.
    try {
        while {[array anymore $arrayName $sid]} {
            set key [array nextelement $arrayName $sid]
            set value [set "${arrayName}($key)"]
            uplevel 1 $body
        }
    } trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
        puts stderr [list $e]
        dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
        return -options $e
    } finally {
        array donesearch $arrayName $sid
    }
    return
}

and add to the array ensemble:

set map [namespace ensemble configure array -map]
dict set map for ::array_for
namespace ensemble configure array -map $map

Given that, an array values subcommand can be easily created (to pair with array names)

proc array_values {arrayName} {
    upvar 1 $arrayName ary
    set values [list]
    array for {name value} ary {lappend values $value}
    return $values
}

Upvotes: 0

Shawn
Shawn

Reputation: 52449

You're better off using a list, dict or array to store related values instead of a bunch of different variables. But any way your data is stored, lappend the non-empty values to a list or otherwise filter out the empty ones and join the result:

#!/usr/bin/env tclsh

set data {a "" c d ""}

# Using foreach
set answer {}
foreach elem $data {
    if {$elem ne ""} {
        lappend answer $elem
    }
}
puts [join $answer " separator "]

# Using lmap for a more functional style; note eq instead of of ne
set answer [lmap elem $data { if {$elem eq ""} continue; set elem }]
puts [join $answer " separator "]

# Using a dict
set data [dict create q1 a q2 "" q3 c q4 d q5 ""]
set answer {}
# Dict traversal happens in the same order keys were added
dict for {_ elem} $data {
    if {$elem ne ""} {
        lappend answer $elem
    }
}
puts [join $answer " separator "]

When iterating through a list of variable names, you have to use set to get the value of the current name (In your code, $var is q1, q2, etc. which are always going to be not equal to an empty string):

set answer {}
foreach varname {q1 q2 q3 q4 q5} {
    set elem [set $varname]
    if {$elem ne ""} {
        lappend answer $elem
    }
}
puts [join $answer " separator "]

Upvotes: 3

Related Questions