TCL: List concat doesnt work - arrays

I am trying to join the below two elements as a single list
Tried concat , join , lappend etc but its not working
1134-1215 { W.1.Agg-251 ethernet4/28 island ethernet9/7}
I need the output of (links)list whose 1st element is 1134 , second element as W.1.Agg-251 etc
for {set index 0 } { $index <=328 } {incr index} {
lappend links [lindex $result2 $index] [lindex $list4 $index]
}
puts "===>$links"
where result 2 is a list like {1134-1150 1151-1600 .. }
and list4 is a list like {W.1.Agg-251 ethernet4/28 island ethernet9/7 X.1.Agg-251 ethernet4/29 island ethernet9/9... }

You probably meant to do something like
set links [concat $links [lindex $result2 $index] [lindex $list4 $index]]

Related

Obtaining array values in Tcl

I have tcl arrays having the names as 0,1,2,.. and the values contain meaningful information.
I want to use a single command like [array names $my_array] to get the values of the array.
Right now I only see this option,(lengthy but gets job done)
for {set index 0} {$index<[array size my_array]} {incr index} {
lappend my_list_values $my_array($index)
}
You can use array get to fetch all the elements of the array, and a foreach loop that consumes multiple elements of its list in each iteration (Plus sorting by key to get a reproducible order):
% set foo(0) a
a
% set foo(1) b
b
% set foo(2) c
c
% foreach {key val} [lsort -integer -stride 2 [array get foo]] { lappend values $val }
% puts $values
a b c
%
Or with array names:
foreach key [lsort -integer [array names foo]] { lappend values $foo($key) }
Here's a proc implementing a foreach functionality for arrays. In Tcl 8.7 this will be builtin: array for
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "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
}
You can add this to the array ensemble:
set map [namespace ensemble configure array -map]
dict set map foreach ::array_foreach
namespace ensemble configure array -map $map
then
% array set a {foo bar baz qux}
% array foreach {key value} a {puts "key=$key, value=$value"}
key=foo, value=bar
key=baz, value=qux

How would I sort file using Tcl according to size of those files?

I have a list of files and a separate list of sizes of those files using "file size <file_name>".
I am required to sort the files in ascending order based on the size and then feed it further for processing.
Can someone provide a step by step process I could follow?
This is what I have done so far
set direc "<Any direcotry to look files at>"
set folderFiles [glob -directory $direc -nocomplain -type f *.xml]
set fileSizes []
puts "Files to be processed are:"
puts "$folderFiles"
puts "Sizes of files in this order are:"
foreach tempFile $folderFiles {
lappend fileSizes [file size $tempFile]
}
puts $fileSizes
set fileDict [dict create [lindex $folderFiles 0] [lindex $fileSizes 0]]
for {set i 1} {$i < [llength $folderFiles]} {incr i} {
dict lappend fileDict [lindex $folderFiles $i] [lindex $fileSizes $i]
}
puts $fileDict
So, this gives me a dictionary where keys -> files and values -> file sizes. I just need to sort this dictionary based on values which are file sizes.
The first thing you need to do is to get the list of filenames and their sizes. You can keep the sizes separately.
set filenames [glob -type f *.foo]; # Or whatever
set sizes [lmap f $filenames {file size $f}]
Then we sort the sizes, but get the indices of the sort back rather than the sorted list.
set indices [lsort -indices -integer $sizes]
Now, we use those indices to construct the sorted filenames:
set filenames [lmap idx $indices {lindex $filenames $idx}]
We can combine some of these things into a helper procedure:
proc SortFilesBySize {filenames} {
set sizes [lmap f $filenames {file size $f}]
return [lmap idx [lsort -indices -integer $sizes] {lindex $filenames $idx}]
}
set filenames [glob -type f *.foo]; # Or whatever
puts [join [SortFilesBySize $filenames] "\n"]
One way:
#!/usr/bin/env tclsh
proc zip {list1 list2} {
lmap a $list1 b $list2 { list $a $b }
}
proc heads {pairs} {
lmap pair $pairs { lindex $pair 0 }
}
proc sort_by_size {names sizes} {
heads [lsort -integer -increasing -index 1 [zip $names $sizes]]
}
set names {a.txt b.txt c.txt}
set sizes {3 2 1}
puts [sort_by_size $names $sizes]
Combines the names and sizes into a list of pairs of filename and size, sorts based on size, and then returns just the reordered filenames. Essentially a tcl version of perl's classic Schwartzian Transform idiom.

TCL create a list

I'm an absolute beginner and can't quite wrap my head around Tcl. I need some help with something that I think is very basic. Any help would be appreciated. I have a text file that I want to import into Tcl. I'll give you the syntax of the file and my desired way to store it:
text FILE to import into Tcl:
Singles 'pink granny fuji'
Singles2 'A B C D E'
Couples 'bread butter honey lemon cinnamon sugar'
Couples2 'A1 A2 B1 B2 C1 C2 D1 D2'
My desired format:
For lines 1 & 2:
Singles
[pink granny fuji] ( 3 single elements)
Singles2
[A B C D E] (5 single elements)
For lines 3 & 4:
Couples
[bread butter
honey lemon
cinnamon sugar] (3 x 2 array)
Couples2
[A1 A2
B1 B2
C1 C2
D1 D2] (4 x 2 array)
The import text file can in theory have any number of elements, but lines 3&4 will always be an even number of elements so that they are pairs, so I know a for each loop is needed to capture any number of elements. I know the code will also need to strip the apostrophes from the text file.
I'm just really struggling at the moment, really appreciate any help at all, thank you :)
here is solution. It works perfectly. I hope you are looking for similar solution.
set handle [open "text_import" "r"]
set content [read $handle]
regsub -all {'} $content "" content
#set content [split [read $handle] \n]
set content [split $content \n]
foreach ele $content {
puts $ele
if {[regexp -nocase -- "(\[^ \]+) +(.*)" $ele - key val]} {
puts $key
if {[regexp -nocase -- "single" $key]} {
set val1 [split $val " "]
set arr($key) $val1
} elseif {[regexp -nocase -- "couple" $key]} {
set biggerList [list]
set val2 [split $val " "]
for {set i 0} {$i < [llength $val2]} {incr i 2} {
set tempList [list [lindex $val2 $i] [lindex $val2 [expr $i + 1]]]
lappend biggerList $tempList
}
set arr($key) $biggerList
}
}
}
parray arr
~
One possible solution. Instead of loading the textfile as data, we can load it as Tcl source if we make the right definitions.
proc Singles args {
set str [string trim [join $args] ']
puts "\[$str]"
}
proc Singles2 args {
set str [string trim [join $args] ']
puts "\[$str]"
}
proc Couples args {
set list [split [string trim [join $args] ']]
foreach {a b} $list {
lappend list2 "$a $b"
}
set str [join $list2 \n]
puts "\[$str]"
}
proc Couples2 args {
set list [split [string trim [join $args] ']]
foreach {a b} $list {
lappend list2 "$a $b"
}
set str [join $list2 \n]
puts "\[$str]"
}
source textfile.txt
Documentation:
foreach,
join,
lappend,
list,
proc,
puts,
set,
source,
split,
string

Tcl: processing variables in a list

Maybe this is pretty stupid, but I really can't find a soulution. I created two variables and want to transform them into lists.
This commands are tool specific, but they work the way I want:
redirect max_transition {report_constraint -view $pargs(-scenario) -drv_violation_type {max_transition} -all_violators} -variable
redirect max_capacitance {report_constraint -view $pargs(-scenario) -drv_violation_type {max_capacitance} -all_violators} -variable
Now I want to create tcl lists out of them. I could use a loop, because the data has the same structure.
set reports {$max_transition $max_capacitance}
set report_length [llength $reports]
for {set i 0} {$i < $report_length} {incr i} {
set tns_value 0
set max_wns 0
set vios 0
set report [lindex $reports $i]
puts $report
# remove all uneccessary white spaces
set no_space [regexp -all -inline {\S+} $report]
# insert a new line for every path
set insert_lines [string map {" U_" \nU_} $no_space]
# create list out of result reports
set report_list [split $insert_lines "\n"]
if {[llength $report_list] > 1} {
for {set i 1} {$i < [llength $report_list]} {incr i} {
# get value of violation
set slack [lindex [split [lindex $report_list $i] " "] 3]
set tns_value [expr $tns_value + $slack]
if {$vios == 0} {set max_wns $slack}
incr vios
}
}
# write out values
puts "$pargs(-scenario), $report, $max_wns, $tns_value, $vios"
}
But this does not work out. The loop just puts out the variable's names (because of "puts $report") but not its content.
If I do it without a loop (so for each variable the same code consecutively), I get the lists I want.
So how can I process these variables as a whole in a loop?
The problem lies in the below loop variable i, it is overriding variable value of outer-loop. Try changing inner-loop variable to j.
for {set i 1} {$i < [llength $report_list]} {incr i} {
# get value of violation
set slack [lindex [split [lindex $report_list $i] " "] 3]
set tns_value [expr $tns_value + $slack]
if {$vios == 0} {set max_wns $slack}
incr vios
}
It's hard to write an answer for this since so much is unknown. To begin with, you should probably change to assignment by list and a foreach loop like this:
set reports [list $max_transition $max_capacitance]
foreach report $reports {
Since you don't really need to use a for loop here, it makes sense to simplify it. Please comment and I will iteratively improve the answer if I can.

Changing item in list by index in TCL

Im trying to work with this awful language and I have to change an item of list by accessing to it with index
set FileRead [open "$flPath" r]
set Data [read $FileRead]
set DataList [split $Data "\n"] #Guess that it creates a list, not an array right?
for {set i 1} { $i < [llength $DataList]} {incr i} {
set line [lindex $DataList $i]
#Some changes on $line
lreplace $DataList $i $line # Thought this should replace the DataList[$i] with my $line
}
I dont understand the awful syntax of TCL! How can I realise it?
You shouldn't just call a language awful because you don't know how to use it, otherwise, just don't use it at all...
In any case, you should read the documentation of lreplace. You supply:
lreplace list first last ?element element ...?
To replace the line in the list of DataList, you thus should change your line to:
lreplace $DataList $i $i $line
This will replace the ith item in DataList with the string in line.
However, this will not change the list DataList at all you need to set the result of the lreplace to a variable (lreplace does not alter the list DataList directly) like...
set DataList [lreplace $DataList $i $i $line]
Though if you are changing only one item in the list at a time, it would be better to use lset:
set FileRead [open "$flPath" r]
set Data [read $FileRead]
set DataList [split $Data "\n"] #Guess that it creates a list, not an array right?
for {set i 1} { $i < [llength $DataList]} {incr i} {
set line [lindex $DataList $i]
# Some changes on $line
lset DataList $i $line
}
lset unlike lreplace does not need to be set since it changes the list in addition to returning the substituted list.
If you are changing every item in a list, and the new value can be produced by a simple script, lmap (list map) is the way to go. The command creates a list with the same number of items as the original list, where each item in the new list has the value of the script with the value of the corresponding item in the original list inserted. Say you want to count characters for each item in a list:
set list [list can be produced]
lmap item $list {
list $item [string length $item]
}
# => {{can 3} {be 2} {produced 8}}
Note that this command does not change the value of list: you need to assign it back to update the value.
The linsert (list insert) adds new items to a list without removing any of the existing elements:
set list [list can be produced]
linsert $list 1 not
# => {can not be produced}
The lreplace (list replace) is mostly useful for insertions that change the number of elements in a list:
set list [list can be produced]
lreplace $list 0 1 can sometimes but not always be
# => {can sometimes but not always be produced}
set list [list the new value can be produced]
lreplace $list 1 1
# => {the value can be produced}
Again, the value of list isn't changed by linsert or lreplace.
If you want to change an item in a list in place, lset (list set) is your friend:
set list [list the new value can be produced]
lset list 1 correct
# => {the correct value can be produced}
This command does change the value of list, because it belongs to the family of setting commands that take a variable name rather than a variable value, and modify the value in some way.
Documentation: linsert, list, lmap, lreplace, lset, set, string

Resources