Reputation: 1031
The Problem: I am trying to convert
"%0 %1 ... %n"
to
"[lindex $someList 0] [lindex $someList 1] ... [lindex $someList n]"
with the regex
regsub -all "(%\c*)" $string "\[lindex \$someList \0\]" string
and it isn't working - I get, instead:
"[lindex $someList ]0 [lindex $someList ]1 ... [lindex $someList ]n"
The Context:
Essentially I am trying to write a function that will perform some action over all permutations of a given set of lists (or in other words their cartesian product), for example if I had the following:
set action "puts \"%1 hello %0 world\""
and a set of lists
[1 2 3] [a b]
then on invocation of
foreach_n $action [list 1 2 3] [list a b]
I expect the outcome
a hello 1 world
b hello 1 world
a hello 2 world
b hello 2 world
a hello 3 world
b hello 3 world
The functions I have written to do this (haven't checked the edge cases fully yet) are:
proc foreach_n { action args } {
foreach el [lindex $args 0] {
foreach_n_helper $action [list $el] {*}[lrange $args 1 end]
}
}
proc foreach_n_helper { action fixed_elem_values args } {
foreach el [lindex $args 0] {
set fixed_vals [list {*}$fixed_elem_values $el]
if {[llength $args] > 1} {
foreach_n_helper $action $fixed_vals {*}[lrange $args 1 end]
} else {
#I simply cannot get this bit correct
regsub -all "(%\c*)" $action "\[lindex \$fixed_vals \0\]" action
puts "$fixed_vals"
puts $action
#eval $action
}
}
}
and by running
foreach_n $action [list 1 2 3] [list a b]
I see that the statements I would want to eval for each permutation are:
1 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
1 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
2 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
2 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
3 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
3 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
So the recursion is as I want it, but the whole thing would crash if I actually tried to eval them since the index is outside of [lindex ...] As per the document at https://www.tcl.tk/man/tcl8.5/TclCmd/regsub.htm I tried bracing the subSpec "[lindex \$fixed_vals \0]" and the output was
1 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
1 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"
2 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
2 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"
3 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
3 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"
which presumably means I am misinterpreting what was stated in the document (or otherwise ...). I have also tried just "random" twiddling and have gotten nowhere.
Can anyone help? And if there is a better way of accomplishing my goal here I would be happy to hear it.
Note: I realize I could achieve my objective by employing the following pattern:
foreach el1 $list1 {
foreach el2 $list2 {
.
.
.
... foreach eln $listn {
call some function taking n parameters
}
}
...
}
}
but this is precisely the pattern I want to avoid!
Upvotes: 1
Views: 91
Reputation: 13252
If you are producing a string in one procedure and then want to substitute tokens in that string with generated strings, a regular expression is possibly the least practical way to do so.
Converting the string "%0 %1 ... %n"
to "[lindex $someList 0] [lindex $someList 1] ... [lindex $someList n]"
is fairly straightforward as the resulting string can be constructed from the input string:
foreach group "%0 %1 %2 %3" {
lappend res "\[lindex \$somelist [string range $group 1 end]]"
}
join $res
If you want the contents of the input string to select items from a data list it's even simpler:
set data [list foo bar baz qux]
set input "%0 %3 %1 %2 %1"
# use lmap in Tcl 8.6
foreach i [lsort -unique $input] d $data {lappend map $i $d}
string map $map $input
# => foo qux bar baz bar
And so on. Everything is already in place in Tcl, there's no need to bring in regular expressions.
Documentation: foreach, join, lappend, lindex, lsort, set, string
Upvotes: 1
Reputation: 246799
I didn't read every detail. Are you missing the subst
command?
% set string "%0 %1 ... %3"
%0 %1 ... %3
% set new [regsub -all {%(\d+)} $string {[lindex $someList \1]}]
[lindex $someList 0] [lindex $someList 1] ... [lindex $someList 3]
% set someList {first second third fourth}
first second third fourth
% subst $new
first second ... fourth
Upvotes: 2