Say I have a TCL list:
set myList {}
lappend myList [list a b 1]
lappend myList [list c d 2]
.....
Now I want to modify the list like this:
foreach item $myList {
lappend item "new"
}
But at the end I have not modified list. Why? item is a reference on the list item no?
item is not a reference to the list item. It is a copy. To do what you want, you could do this:
set newlist {}
foreach item $myList {
lappend item "new"
lappend newlist $item
}
set mylist $newlist
To edit the list “in place”, you can do this:
set idx -1
foreach item $myList {
lappend item "new"
lset myList [incr idx] $item
}
You can also do this if you've got Tcl 8.6 (notice that I'm not actually using $item; it's just convenient looping):
set idx -1
foreach item $myList {
lset myList [incr idx] end+1 "new"
}
But it won't work on 8.5, where lset will only replace existing items (and sublists).
for {set i 0} {$i < [llength $myList]} {incr i} {
set item [lindex $myList $i]
lappend item new
set myList [lreplace $myList $i $i $item]
}
If your list is very large, some efficiencies can be made (e.g. the K combinator). They would just add complexity here.
foreach item $myList {
lappend item "new"
}
What you're doing here, is getting a variable (called item), and modifying it to also contain 'new'. basically, you get lists that look like {a new}, {b new} and so on. But you leak those variables at the end of each iteration.
What do you really want your list to look like when you're done?
Related
My list :
set myList "{
{key value} {key1 value1}
{
{key2 value2} {key3 value3}
}
}"
Desired output :
{{key value} {key1 value1} {{key2 value2} {key3 value3}}}
What I’ve tried so far:
set myList [string trim $myList]
set newList [string map {\n "" \t " "} $myList]
regsub -all {^\{\s+\{} $newList "{{" newList ; # start of list
regsub -all {\}\s+\}$} $newList "}}" newList ; # end of list
regsub -all {\}\s+\{} $newList "} {" newList ; # middle of list
regsub -all {\{\s+\{} $newList "{{" newList ; # middle of list again
regsub -all {\}\s+\}} $newList "}}" newList ; # middle of list again and again
It works , but I've used many regsub command.
Is it possible to limit them ? or a different approach, without regsub...
First off, doing it with regsub in 8.6 requires two, where one of them is decidedly ugly (using lookaheads):
regsub -all {(\{)\s+(?=[{}])|(\})\s+(?=\})} [regsub -all {(\})\s+(?=\{)} $myList {\1 }] {\1\2}
In 8.7, you can do it with one using the -command option (because you need the different substituent in some cases) but the RE itself will be uglier.
You can do it without regsub, but only if you know how deep you want to normalize (i.e., what the logical structure is); in your case, twice is enough:
lmap item $myList {
lmap subitem $item {
list {*}$subitem
}
}
The command list {*}$thing is rather like concat $thing except that it is a proper list-native. concat isn't (and we have the test cases to prove it).
This removes leading/trailing whitespace from each line and also newlines
regsub -all -line {(^\s+)|(\s+$)|\n} $myList ""
# => {{key value} {key1 value1}{{key2 value2} {key3 value3}}}
It does remove the space between sublists though.
regsub -all -line {(^\s+)|(\s+$)|\n} $myList "" flattened
regsub -all {\}\{} $flattened "} {"
# => {{key value} {key1 value1} {{key2 value2} {key3 value3}}}
I am having difficulty writing this small code, from which the three variables present are to be output. But when running with tcl, only the first variable is displayed in duplicate: Diego Diego Diego ; and not in sequence as I would like it to be: Diego Henrique Guilherme.
My sample code:
set name0 "\[Diego\]"
set name1 "\[Henrique\]"
set name2 "\[Guilherme\]"
set lst {}
lappend lst [list $name0 $name1 $name2]
set num {0 1 2}
foreach a $lst b $num {
set x [lindex $a $b]
regexp "\[\[(.*?)\](.*?)\]" $x value out
puts $out
}
I wasn't able to identify the error. If anyone can point out to me the flaw I'll be grateful.
I want to get this output: Diego Henrique Guilherme
As written, lst become a list of one element where the first element is a list of three items.
Instead of
set lst {}
lappend lst [list $name0 $name1 $name2]
just do
set lst [list $name0 $name1 $name2]
I'm reading a large file and I'm only interested in small part of the file as shown below.
TC.0.Type = Bob 1
TC.1.Type = Mark 1
TC.2.Type =
TC.3.Type =
TC.4.Type = Fred 1
TC.5.Type =
TC.6.Type =
TC.7.Type =
TC.8.Type =
TC.9.Type = Fred 1
I've created a variable that is now holds this information
data = "{Bob 1} {Mark 1} {} {} {Fred 1} {} {} {} {} {Fred 1}"
TC is always between 0-9, so length is known.
What I would like to do is:
1) If there are multiple instances of "Fred 1" and delete it.
2) Find the first empty slot and determine the index.
Question 1)
Is it typical to get brackets when using lappend? I expected this to be only in the case of empty fields
set data ""
for {set j 0} {$j < 10} {incr j} {
lappend data $fromfile
}
puts "Data in list = $data"
Question 2) I've even tried using regexp to pick out empty but don't seem to be successful.
Find empty field {}
set j 0
for {set i 0} {$i < $ldata} {incr i} {
# set nline [split $data "\s"]
# puts "data ($i) = $nline"
if {[regexp {\{.*\}} $data]} {
puts " Found {}"
incr j
puts "j = $j"
}
}
Find field with name e.g. Bob 1
for {set i 0} {$i < $ldata} {incr i} {
if {[regexp {\{.*[a-zA-Z0-9]\}} $data]} {
puts " Found something with names"
}
}
Would appreciate if someone can advice and guide.
The lsearch command is going to be tremendously useful for what you are doing, especially with the -all option.
set data "{Bob 1} {Mark 1} {} {} {Fred 1} {} {} {} {} {Fred 1}"
puts [lsearch -all -exact $data "Fred 1"]
# ==> 4 9
We can also use it to remove specific values:
puts [lsearch -all -inline -exact -not $data "Fred 1"]
# ==> {Bob 1} {Mark 1} {} {} {} {} {} {}
To find the first empty slot, we just do:
puts [lsearch -exact $data ""]
# ==> 2
We most definitely would expect braces back from list operations; that's how empty list elements are expressed.
Is there a way to get items from a list according to some function?
I know there is a way to get items by regular expression by using lsearch -regexp but It's not what I need.
In Tcl 8.6, you can use the lmap command to do this by using continue to skip the items you don't want (or break to indicate that you've done enough processing):
set items {0 1 2 3 4 5 6 7 8 9 10}
set filtered [lmap i $items {if {$i==sqrt($i)**2} {set i} else continue}]
# Result: 0 1 4 9
This can be obviously extended into a procedure that takes a lambda term and a list.
proc filter {list lambda} {
lmap i $list {
if {[apply $lambda $i]} {
set i
} else {
continue
}
}
}
set filtered [filter $items {i { expr {$i == sqrt($i)**2} }}]
It's possible to do something similar in Tcl 8.5 with foreach though you'll need to do more work yourself to build the list of result items with lappend…
proc filter {list lambda} {
set result {}
foreach i $list {
if {[apply $lambda $i]} {
lappend result $i
}
}
return $result
}
Usage is identical. (Tcl 8.4 and before — now unsupported — don't support the apply command.)
I have this kind of list :
{ A D C } { D S D } { A S D } { Y D D }
I want to list all the index that have duplicates in the same index of the sublist.
For example if I want to serach every "D" at index 2 in sublist, I want to know the index of the list (here 0 and 3)
here is the code :
proc findElement {lst idx value} {
set i 0
foreach sublist $lst {
if {[string equal [lindex $sublist $idx] $value]} {
return $i
}
incr i
}
return -1
}
When i call it findElement $toto 1 D
it returns only 0 !
Why ?
Because you have a return statement when it finds a match when $i = 0.
Try the following which instead returns a list of all the matching indexes
proc findElement {lst idx value} {
set i 0
set return_list [list]
foreach sublist $lst {
puts "i=$i sublist=$sublist"
if {[string equal [lindex $sublist $idx] $value]} {
puts "Found $i"
lappend return_list $i
}
incr i
}
return $return_list
}
You can do a shorter and faster version with lsearch -all -exact -index.
proc findElement {lst idx value} {
return [lsearch -all -exact -index $idx $lst $value]
}