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.
13 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
whiletest condition, avoiding to write beforeset i 1unnecessarily: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->gelassign->lasexpr->expputs->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
renameto renamerename:rename rename &The
&can be any identifier;&just reminds me of "references" in C.Use the renamed
renameto 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 WA 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 . 2Next, compact frequently used subcommands like
= I index = L lengthso you can do things like
S $I $x 7 S $L $xSome obvious miscellanea:
lappendcan 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\ wordsrather than"two words". (Remember in general that for contiguous strings without spaces, double quotes can be omitted!) - You can almost always rewrite
fors aswhiles to save a character or two, since awhilecan simultaneously check and increment while aforuses 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
jforjoin,spforsplit,stforstring,wforwhile, and so on.
- 1\$\begingroup\$ If you want to emulate interactive abbreviations, use
info script {};set tcl_interactive 1\$\endgroup\$Johannes Kuhn– Johannes Kuhn2014-02-11 23:24:03 +00:00Commented Feb 11, 2014 at 23:24 - \$\begingroup\$ Cool, thanks! I've credited you here. There were some issues with that technique, however, that I didn't encounter with the
unknownroute: see here and here. \$\endgroup\$Andrew Cheong– Andrew Cheong2014-02-12 00:08:51 +00:00Commented Feb 12, 2014 at 0:08 - \$\begingroup\$ The question asks for tips which are somewhat specific to Tcl. The ternary operator is included in the tips for all languages. \$\endgroup\$Peter Taylor– Peter Taylor2014-03-17 20:36:56 +00:00Commented 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.
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 Sometimes it is worth to replace the two set statements to concatenate strings by only one append 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 [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
- 1\$\begingroup\$ In this case, you can omit the
split. \$\endgroup\$Johannes Kuhn– Johannes Kuhn2017-08-02 20:54:29 +00:00Commented Aug 2, 2017 at 20:54
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,
setis golfier=5,
setandlassigngenerate the same byte count>5,
lassignis golfier
Some commands like
lindex, have some very basic math understanding. So instead of needing to write:lindex $L [expr [lsearch $L $argv]+1]one can write
lindex $L [lsearch $L $argv]+1
Can be seen on my answer https://codegolf.stackexchange.com/a/161121/29325
If there is possibility to do sequencial
unsetoperations, they an be all done in te same line likeunset x yCan be seen in my answer https://codegolf.stackexchange.com/a/163386/29325
For repeated spaces
[format %$n\s ""]renders less bytes than[string repe \ $n]
Can be seen on https://codegolf.stackexchange.com/a/140248/29325
- For cases where there are consistent separation of single spaces,
lappendmay be very practical
Can be seen on https://codegolf.stackexchange.com/a/107557/29325
- Returning more than one value from a
proccan be byte-efficiently done with alistcommand.
Can be seen on https://codegolf.stackexchange.com/a/162288/29325
- If you need to do a
puts ""and aunsetof one or more variables, you can merge together the two commands like inputs [unset x], to shave bytes off.
Can be seen on Not too hard to code this right?