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

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.

Related

Print contents of several arrays in columns sorted by the key in tcl

I have several arrays with same key range. I am trying to print them as columns sorted by key value.
For eg: binA($i), binB($i), binC($i) I:1-100
Expected output:
i A B C
1 binA(1) binB(1) binC(1)
2
3
....
100 .....
Can anyone please show me how to do this in TCL?
untested
set bins {A B C}
foreach i [lsort -integer [array names binA]] {
set fields [list $i]
foreach bin $bins {
lappend fields [set bin${bin}($i)]
}
puts [join $fields]
}

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

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

How to return the value from the proc using TCL

I have a sample proc
proc exam {return_value} {
set input "This is my world"
regexp {(This) (is) (my) (world)} $input all a b c d
set x "$a $b $c $d"
return x }
After the above proc execution i will get all a b c d value in single list, so if i want only b value from the above proc, now am doing [lindex [exam] 1].
I am looking for other way to get output in different manner instead of using lindex or returun_value(b) can give the my expected output
The usual way of returning multiple values is as a list. This can be used with lassign at the call site so that the list is broken into multiple variables immediately.
proc exam {args} {
set input "This is my world"
regexp {(This) (is) (my) (world)} $input all a b c d
set x "$a $b $c $d"
return $x
}
lassign [exam ...] p d q bach
You can also return a dictionary. In that case, dict with is a convenient way to unpack:
proc exam {args} {
set input "This is my world"
regexp {(This) (is) (my) (world)} $input all a b c d
return [dict create a $a b $b c $c d $d]
}
set result [exam ...]
dict with result {}
# Now just use $a, $b, $c and $d
Finally, you can also use upvar inside exam to bring the caller's variables into scope, though there it's usually wisest to only do that with variables that the caller gives you the name of.
proc exam {return_var} {
upvar 1 $return_var var
set input "This is my world"
regexp {(This) (is) (my) (world)} $input all a b c d
set var "$a $b $c $d"
return
}
exam myResults
puts "the third element is now [lindex $myResults 2]"
You can use dict and choose such key-value mapping that will make your intent clear:
return [dict create width 10 height 200 depth 8]
I think there are no ways in Tcl to return multiple values other than compound data structures or yield from a coroutine.

List of Array tcl

I have 5 arrays and i want to iterate over them one by one, so I'm thinking to add these arrays to another array and access them one by one with indexes
array set ListOfArrays {1 $array1 2 $array2 3 $array3 4 $array4 5 $array5}
for { set i 1} { $i <= 5 } {incr i} {
set list $ListOfArrays($i)
foreach {key} [array names list] {
puts $key
}
}
The output is empty...
what is wrong?!
Tcl's arrays can't be put in a list; they're (collections of) variables, not values. But you can put the names of the arrays in.
array set ListOfArrays {1 array1 2 array2 3 array3 4 array4 5 array5}
for { set i 1} { $i <= 5 } {incr i} {
upvar 0 $ListOfArrays($i) list
foreach {key} [array names list] {
puts $key
}
}
The upvar 0? It makes a local alias to a variable, and it's great for this sort of thing.
As an alternative to the array-based solutions, a solution using dictionaries. I borrow the data from Jerry's answer.
set dict1 {1 a 2 b}
set dict2 {3 c 4 d}
set dict3 {5 e 6 f}
set dict4 {7 g 8 h}
set dict5 {9 i 10 j}
set dicts [list $dict1 $dict2 $dict3 $dict4 $dict5]
foreach dict $dicts {
foreach key [dict keys $dict] {
puts $key
}
}
Unlike arrays, dictionaries can be passed as values and evaluated using $. This means that you can put your five dictionaries in a list and traverse that list with foreach. In the inner loop, each key is printed.
foreach dict $dicts {
dict for {key -} $dict {
puts $key
}
}
is basically the same thing.
Documentation: dict, foreach, list, puts, set

Resources