TCL create a list - arrays

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

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-Tk How to catch floating point numbers from a file

i'm finding some troubles working on a file containing some floating numbers.
These are some rows from my file:
174259 1.264944 -.194235 4.1509e-5
174260 1.264287 -.191802 3.9e-2
174261 1.266468 -.190813 3.9899e-2
174262 1.267116 -.193e-3 4.2452e-2
What i'm trying to do is to find the row where is my desire number (e.g "174260") and extract the following three numbers.
this is my code:
set Output [open "Output3.txt" w]
set FileInput [open "Input.txt" r]
set filecontent [read $FileInput]
set inputList [split $filecontent "\n"]
set Desire 174260
set FindElem [lsearch -all -inline $inputList $Desire*]
set Coordinate [ regexp -inline -all {\S+} $FindElem ]
set x1 [lindex $Coordinate 1]
set y1 [lindex $Coordinate 2]
set z1 [lindex $Coordinate 3]
puts $Output "$x1 $y1 $z1"
Using the regexp method for a string "{\S+}" i obtain as last character a curly brackets:
1.264287 -.191802 3.9e-2}
I don't know how to extract only the numbers value and not the entire string.
I'd be really tempted in this case to go with the simplest possible option.
set Output [open "Output3.txt" w]
set FileInput [open "Input.txt" r]
set Desire 174260
while {[gets $FileInput line] >= 0} {
lassign [regexp -inline -all {\S+} $line] key x1 y2 z1
if {$key == $Desire} {
puts $Output "$x1 $y1 $z1"
}
}
close $FileInput
close $Output
Failing that, your problem is that you're using lsearch -all -inline, which returns a list, and then processing that list as a string with regexp. You should handle that using:
foreach found $FindElem {
set Coordinate [ regexp -inline -all {\S+} $found ]
set x1 [lindex $Coordinate 1]
set y1 [lindex $Coordinate 2]
set z1 [lindex $Coordinate 3]
puts $Output "$x1 $y1 $z1"
}
This isn't really as good as just understanding the lines properly in the first place, and working with the data one line at a time is pretty trivial.

Retrieving modelsim signals into tcl

How can I retrieve a Modelsim signal value in this form x y into tcl so I can process x and y individually?
Currently I have this line in tcl to trace a signal value
when {/currentstate/comp_occupy} {set comp [exa
{/currentstate/comp_occupy}]}
This signal is a 2D array in Modelsim which is shown like x y in the widget.
This snippet should trace that variable
trace variable comp w grid_monitor
proc grid_monitor {name arrayindex op} {
global comp flag_ttt cells
if {$flag_ttt == 1} {
puts $comp
puts [llength $comp]
}
}
What I get out of this proc is like this {x y} but I have no idea how I can separate x and y. First I thought that's a list but llength returns 1!
Any idea how I can go about doing this? Or rather, how can I turn it into a proper list?
Thanks
Since we established that the braces were literal braces, you can trim them out. Once done, you can then split to get a list:
proc grid_monitor {name arrayindex op} {
global comp flag_ttt cells
if {$flag_ttt == 1} {
set new_comp [split [string trim $comp "{}"]]
puts $new_comp
puts [llength $new_comp]
}
}
string trim will trim from $comp the characters contained within the quotes, that is { and }. split will then split the string on space to give a list.
And if you want to assign x and y to the above, you can use lindex or lassign (if you have Tcl8.5 or later):
proc grid_monitor {name arrayindex op} {
global comp flag_ttt cells
if {$flag_ttt == 1} {
set new_comp [split [string trim $comp "{}"]]
puts $new_comp
puts [llength $new_comp]
set x [lindex $new_comp 0]
set y [lindex $new_comp 1]
puts "x is $x and y is $y"
}
}
Or...
set new_comp [split [string trim $comp "{}"]]
puts $new_comp
puts [llength $new_comp]
lassign $new_comp x y
puts "x is $x and y is $y"
In Tcl 8.5 the syntax to convert a string containing a valid list is to use the new expand operator:
set comp {*}$comp
It isn't clear if current versions of Modelsim have upgraded beyond 8.4 in which you need to do the same with eval:
eval set comp $comp
This uses the interpreter to do what it does best and avoids manually massaging the string.

After using array get, how to remove the indexes in TCL

I have a proc which finishes off with an array. To return it I use "array get" to retrive a list. This list however does not only contain my array entrys, but also their index:
So my array [ a b c d ] turns into a list { 0 a 1 b 2 c 3 d }
How can i get rid of these index numbers without disturbing the list order?
A few options, apart from using foreach:
# [array get] actually returns a dictionary
puts [dict values $list]
# Could do this too
set entrylist {}
dict for {- entry} $list {
lappend entrylist $entry
}
puts $entrylist
There are more possibilities in Tcl 8.6:
puts [lmap {- entry} $list {set entry}]
(There's also a dict map, but it isn't useful here.)
I like dict values…
The simplest and most basic way I think would be to use a foreach loop:
% set list {0 a 1 b 2 c 3 d}
% set entrylist {}
% foreach {id entry} $list {
% lappend entrylist $entry
% }
% puts $entrylist
a b c d
If you already have an array and work with Tcl 8.5 or later, use dict values:
set arr(0) a
set arr(1) b
set arr(2) c
set arr(3) d
puts [dict values [array get arr]]
But it is better to use a simple list:
set mylist {a b c d}
lset list 1 boo
puts [lindex $mylist 1]
lappend mylist eff
Arrays are associative. Always.

Resources