18
\$\begingroup\$

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.

\$\endgroup\$
0

12 Answers 12

7
\$\begingroup\$

Use lmap instead foreach. This requires Tcl 8.6.

The syntax is the same, but lmap returns a list with the result of each loop.

\$\endgroup\$
4
\$\begingroup\$

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.

\$\endgroup\$
4
\$\begingroup\$

On my answer https://codegolf.stackexchange.com/a/107557/29325 I can demonstrate:

  1. Usually set j 0;while \$j<$n;{...;incr j} is shorter than the equivalent for {set j 0} {$j<$n} {incr j} {...}

  2. When the looping variable begins at 1, we can do the increment as part of the while test condition, avoiding to write before set i 1 unnecessarily: while {[incr i]<=$n} {...} instead of set 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!

\$\endgroup\$
4
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
3
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

I'm using Tcl 8.0.5, but I believe the following are applicable to all recent versions.

  1. Use rename to rename rename:

    rename rename &
    

    The & can be any identifier; & just reminds me of "references" in C.

  2. Use the renamed rename to rename set:

    & set =
    

    Again, the = can be any identifier; = is just intuitive to me.

  3. 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               
    
  4. Next, compact frequently used subcommands like

    = I index
    = L length
    

    so you can do things like

    S $I $x 7
    S $L $x
    
  5. Some obvious miscellanea:

    1. lappend can set the first element of a list if it doesn't yet exist (no need to initialize).
    2. You can set arrays without using array, e.g. set doesNotExist(7) 43.
    3. You can use strings ("a b c") instead of [list a b c].
    4. You can interpolate in strings like so: foo${a}bar.
    5. You can use two\ words rather than "two words". (Remember in general that for contiguous strings without spaces, double quotes can be omitted!)
    6. You can almost always rewrite fors as whiles to save a character or two, since a while can simultaneously check and increment while a for uses separate blocks.
  6. 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 for join, sp for split, st for string, w for while, and so on.

\$\endgroup\$
3
  • 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\$ Cool, thanks! I've credited you here. There were some issues with that technique, however, that I didn't encounter with the unknown route: see here and here. \$\endgroup\$ Commented 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\$ Commented Mar 17, 2014 at 20:36
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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

\$\endgroup\$
1
\$\begingroup\$

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

\$\endgroup\$
1
  • \$\begingroup\$ In this case, you can omit the split. \$\endgroup\$ Commented Aug 2, 2017 at 20:54
1
\$\begingroup\$

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

See official mathop doc page

\$\endgroup\$
0
\$\begingroup\$

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 and lassign generate the same byte count

  • >5, lassign is golfier

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.