What general tips do you have for golfing in Tcl? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to Tcl (e.g. "remove comments" is not an answer). Please post one tip per answer.
12 Answers
Subcommands and options can (usually) be abbreviated. This can save quite a bit, but you must test as not everything can be shortened this way (regsub
's options can't, for example).
You can then use this with the magic of namespace
to do some truly evil things. Consider this:
namespace exp *;namespace en cr -c ?
After that, ?
is now a magical command that lets you abbreviate any global Tcl command, and all without the nasty uncertainty of messing around with unknown
.
On my answer https://codegolf.stackexchange.com/a/107557/29325 I can demonstrate:
Usually
set j 0;while \$j<$n;{...;incr j}
is shorter than the equivalentfor {set j 0} {$j<$n} {incr j} {...}
When the looping variable begins at 1, we can do the increment as part of the
while
test condition, avoiding to write beforeset i 1
unnecessarily:while {[incr i]<=$n} {...}
instead ofset i 1;while \$i<=$n;{...;incr i}
ATTENTION: One can only do 2. in the case of an outer loop! I could not apply it to my j
variable as it needs to be reset to 1 in the outside of its own inner loop! And incr j
would acquire the value that was set on the last step of the inner loop, instead of grabbing an undefined variable j
to assume 0
and increment it to 1
!
Sometimes it is worth to use time {script} n
where n
is the number of iterations instead of a normal while
or for
loop. Although time
's purpose is not looping, the achieved effect is the same.
I made changes recently to my own answers following this direction.
UPDATE: I've just discovered an easy to fall pitfall: You can not replace a for
or a while
by a time
block, if it contains a break
or a continue
.
Use the interactive shell. This allows you to abbreviate the command names as long as only 1 command starts with the remaining letters.
Example:
gets
->ge
lassign
->las
expr
->exp
puts
->pu
And interactive solutions are free :P
Background:
When tclsh
runs with a terminal as input device, it sets the variable tcl_interactive
to 1
. This causes unknown
(default procedure that will be called if a command can not be found) to search for commands that starts with that name.
Downside: it will print the result of every line, use ;
instead of newlines.
Ohh, and it might invoke external commands like w
, which is a good abbreviation of while
.
else is optional
As it is said on if manual page, else
is implicit on if
block constructs. So what is
if ... {} else {}
can become
if ... {} {}
as you can see on some of my answers.
I'm using Tcl 8.0.5, but I believe the following are applicable to all recent versions.
Use
rename
to renamerename
:rename rename &
The
&
can be any identifier;&
just reminds me of "references" in C.Use the renamed
rename
to renameset
:& set =
Again, the
=
can be any identifier;=
is just intuitive to me.Now, rename other commands that are worth renaming, e.g.
& regsub R & string S & while W
A command is worth renaming if, given its length n, and occurrences k, k(n-1)-(n+4) > 0. Solving for k, the formula becomes
k > (n+4)/(n-1)
. Here's a reference table that makes it easy:length of minimum example(s) command occurrences ------------------------------------------------ 2 6 if (consider renaming to "?") 3 4 for, set (consider renaming to "=") 4 3 eval, expr, incr (consider renaming to "+"), info, join, proc, puts, scan 5 3 break, catch, lsort, split, subst, trace, unset, while 6 3 format, lindex, lrange, regexp, regsub, rename, return, string, switch 7 2 foreach, lappend, linsert, llength, lsearch, unknown . 2 lreplace . 2 continue . 2
Next, compact frequently used subcommands like
= I index = L length
so you can do things like
S $I $x 7 S $L $x
Some obvious miscellanea:
lappend
can set the first element of a list if it doesn't yet exist (no need to initialize).- You can set arrays without using
array
, e.g.set doesNotExist(7) 43
. - You can use strings (
"a b c"
) instead of[list a b c]
. - You can interpolate in strings like so:
foo${a}bar
. - You can use
two\ words
rather than"two words"
. (Remember in general that for contiguous strings without spaces, double quotes can be omitted!) - You can almost always rewrite
for
s aswhile
s to save a character or two, since awhile
can simultaneously check and increment while afor
uses separate blocks.
For larger programs, here's a trick I thought of but haven't yet applied:
proc unknown {c args} {eval [info commands $c*] $args}
This emulates interactive command abbreviations! It costs 54 characters, but now you can use
j
forjoin
,sp
forsplit
,st
forstring
,w
forwhile
, and so on.
-
1\$\begingroup\$ If you want to emulate interactive abbreviations, use
info script {};set tcl_interactive 1
\$\endgroup\$ Commented Feb 11, 2014 at 23:24 -
-
\$\begingroup\$ The question asks for tips which are somewhat specific to Tcl. The ternary operator is included in the tips for all languages. \$\endgroup\$ Commented Mar 17, 2014 at 20:36
May be it should be integrated on another answer, but here it goes:
When a proc
has only one parameter it could be written as
proc p a {DO THINGS}
instead of
proc p {a} {DO THINGS}
The same applies to a two parameters proc
using a backslash; it can be written as
proc p a\ b {DO THINGS}
instead of
proc p {a b} {DO THINGS}
For an higher number of parameters the {}
render shorter code.
Sometimes it is worth to replace the two set
statements to concatenate strings by only one lappend
statement. On a construction like, one can substitute
set s ""
loop {
# ...
set s $s\X
}
by
loop {
# ...
append s X
}
The append
command has an incr
like behaviour, which initializes a not yet defined variable.
Take care to not mistake append
by lappend
If you are handling a list with an operation that syntactically is interleaving between each element, sometimes you can join
elements to do a specific operation, instead of traversing it.
On https://codegolf.stackexchange.com/a/127042/29325 there is an example:
puts \n[expr [join [split [read stdin]] +]]
It read stdin
gives 23 214 52
then split will give the list {23 214 52}
. After, [join {23 214 52} +]
will return the string 23+214+52
. Finally expr 23+214+52
does the job of summing
-
\$\begingroup\$ In this case, you can omit the
split
. \$\endgroup\$ Commented Aug 2, 2017 at 20:54
If you have large codes, it's possible to avoid multiple usages of expr
using namespace pat tcl::mathop
at the beginning. It provides prefix-syntax operation as a regular Tcl fonction. For example:
namespace pat tcl::mathop
set sum [+ 1 2 3]
set prod [* {*}{1 2 3 4}]
puts $sum\ $prod
When you have several variables that are been set
on subsequent lines you can use one lassign
instead of several set
instructions to achieve the same effect.
One example is my own answer https://codegolf.stackexchange.com/a/105789/29325
To decide, one just needs to weight the number of variables (assuming 1 letter variables, as it is expected when golfing):
<5,
set
is golfier=5,
set
andlassign
generate the same byte count>5,
lassign
is golfier