Version 1 of foriter - a loop command

Updated 2005-12-13 19:54:01

Sarnold -- 2005/12/13

Typically, when you do some list processing, you can use the foreach command which is efficient, but a construct like:

 for {set i 0} {$i<[llength $mylist]} {incr i} {...}

is slow because it has to call llength at each iteration. I admit it is not new. So a construct like :

 for {set i 0;set len [llength $mylist]} {$i<$len} {incr i} {...}

would be faster, but less readable as there is a new variable that does not mean anything in the rest of the program.

In Python, you can get more speed by the range() function:

 for i in range(l.length):
     # some code

I submit a new command called foriter that mimics Python's functionality:

 foriter loopvar ?start? end ?increment? body

 loopvar - the name of a variable (existing or not) that holds the counter
 start - defaults to 0
 increment - defaults to 1

Then,

 foriter i start end increment body

is equivalent to:

 for {set i $start} {$i<$end} {incr i $increment} $body

Some benchmarks show that it is faster than for in many cases.


Here is an implementation in C:

 critcl::ccode {
     #include <stdio.h>
 }

 package provide Foriter 0.1

 critcl::ccommand foriter {dummy interp objc objv} {
     int result;
     /* foriter loop : from start to end-1 increment by <increment> do <body> */
     Tcl_Obj *obj_body = NULL;
     /* the int values of the loop range */
     int start = 0;
     int end;
     int increment = 1;
     /* the name of the variable to set the counter */
     char *counter_name = NULL;


     if (objc<4 || objc>6) {
         Tcl_WrongNumArgs(interp, 1, objv, "foriter varname ?start? end ?increment? body");
         return TCL_ERROR;
     }

     if (objc == 4) {
         result = Tcl_GetIntFromObj(interp, objv[2], &end);
         if (result != TCL_OK) {
             return result;
         }
         obj_body = objv[3];
     } else  {
         /*  the user provided 'start' as 2nd argument,
         and the 'end' of the range is then the 3rd argument  */

         result = Tcl_GetIntFromObj(interp, objv[2], &start);
         if (result != TCL_OK) {
             return result;
         }

         result = Tcl_GetIntFromObj(interp, objv[3], &end);
         if (result != TCL_OK) {
             return result;
         }

         if (objc == 5) {
             obj_body = objv[4];
         } else  {
             obj_body = objv[5];
         }
     }

     if (objc == 6) {
         result = Tcl_GetIntFromObj(interp, objv[4], &increment);
         if (result != TCL_OK) {
             return result;
         }
     }

     Tcl_LinkVar(interp, counter_name=Tcl_GetString(objv[1]), &start, TCL_LINK_INT);

     if ((end-start) * increment < 0) {
         /*  the iterating range goes the other way
         than incrementation does                 */
         Tcl_SetObjResult(interp, Tcl_NewStringObj(
         "invalid range : an endless loop would occur", -1));
         return TCL_ERROR;
     }

     /* please note that a negative increment could be used
     (I wonder if one shall do so ?)
     now, the loop begins                                */
     for ( ; (increment > 0)? (start < end): (start > end) ;  ) {
         /* we are into the loop */
         /* printf("."); */
         result = Tcl_EvalObjEx(interp, obj_body, 0);
         if ((result != TCL_OK) && (result != TCL_CONTINUE)) {
             if (result == TCL_ERROR) {
                 Tcl_SetObjResult(interp, Tcl_NewStringObj(
                 "error in foriter body", -1));
                 /*  when an error occurs, we quit the loop
                 and clean up things like References    */
                 break;
             }
             /*  when the user breaks the evaluation
             we have to break out of the loop */
             break;
         }
         /* updating the integer value */
         start += increment;
         Tcl_UpdateLinkedVar(interp, counter_name);

     }
     Tcl_UnlinkVar(interp, counter_name);

     if (result == TCL_ERROR) {
         return result;
     }

     Tcl_ResetResult(interp);
     return TCL_OK;
 }

Category Critcl