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.
MG Can you not just do something like this:
for {set i 0} "\$i < [llength $myList]" {incr i} {...}
so that the llength is only evaluated once (at the very beginning), and its result is used in future (for each loop)?
In Python, you can get more speed by the range() function (see also Integer range generator):
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.
LV So using foriter with the original example:
foriter i 0 [llength $mylist] {...}
and the llength only executes once, right?
Sarnold Exactly! And you may even omit the start argument:
foriter i [llength $mylist] {...}
KPV Two comments: first, calls to [llength] are extremely fast, probably just as fast as a variable assignment. Second, I bet that most of the time when a loop goes from 0 to [llength...] there will be, very early in the loop body, a call to [lindex...]. Thus, I believe that the construct that is really needed is a variation on foreach so that it has a counter variable.
Sarnold You are right, a foreach alternative is certainly better. But I still find Python's syntax appealing because it adds sugar.
Well, every programmer has been taught the for syntax early in his/her trainings, but I am *bored* with this syntax because I have to type too much. That is the major reason.
Benchmarks :
load foriter.dll foreach {nb times} {100 100 1000 10 10000 5} { puts "foriter: $nb increments" puts [time {foriter i $nb {}} $times] puts "for: $nb increments" puts [time {for {set i 0} {$i<$nb} {incr i} {}} $times] } puts OK
Results :
foriter: 100 increments 173 microseconds per iteration for: 100 increments 217 microseconds per iteration foriter: 1000 increments 1583 microseconds per iteration for: 1000 increments 2197 microseconds per iteration foriter: 10000 increments 15768 microseconds per iteration for: 10000 increments 21369 microseconds per iteration OK
Here is an implementation in C:
critcl::ccode { #include <stdio.h> extern int Tcl_IncrObjCmd(ClientData dummy, Tcl_Interp* interp, int objc, Tcl_Obj* CONST objv[]); } #package provide foriter 1.0 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; Tcl_Obj *obj_counter = NULL; /* the int values of the loop range */ int start = 0; int end; int increment = 1; int sign = 1; int isSharedObj = 0; int isSharedObjCounter = 0; /* the name of the variable to set the counter */ char *counter_name = NULL; /* the increment (1 by default) */ CONST char *str_increment = ""; 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]; /* the start of the number range */ obj_counter = Tcl_NewIntObj(0); if (obj_counter == NULL) { return TCL_ERROR; } } else { /* the user provided 'start' as 2nd argument, and the 'end' of the range is then the 3rd argument */ /* the start of the number range */ obj_counter = objv[2]; 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; } str_increment = Tcl_GetStringFromObj(objv[4], NULL); } if (increment == 0) { /* the increment is zero so there
Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot increment by zero", -1)); return TCL_ERROR; } 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; } if (increment<0) { sign=-1; } counter_name = Tcl_GetString(objv[1]); if (counter_name == NULL || strcmp(counter_name,"") == 0) { return TCL_ERROR; } /* the easiest way to initialize the counter */ //Tcl_LinkVar(interp, counter_name, &start, TCL_LINK_INT); /* end initialize the counter */ /* the cleanest way to initialize the counter */ //printf("A"); if ( Tcl_IsShared( obj_counter ) ) { //printf("A1"); obj_counter = Tcl_DuplicateObj( obj_counter ); Tcl_IncrRefCount( obj_counter ); isSharedObjCounter=1; //printf("A2"); } //printf("B"); obj_counter = Tcl_ObjSetVar2(interp, objv[1], NULL, obj_counter, TCL_LEAVE_ERR_MSG); if (obj_counter == NULL) { return TCL_ERROR; } //printf("C"); if ( Tcl_IsShared( obj_counter ) ) { //printf("C1"); obj_counter = Tcl_DuplicateObj( obj_counter ); Tcl_IncrRefCount( obj_counter ); isSharedObjCounter=1; //printf("C2"); } /* append the incr statement to the body */ if ( Tcl_IsShared( obj_body ) ) { obj_body = Tcl_DuplicateObj( obj_body ); Tcl_IncrRefCount( obj_body ); isSharedObj=1; } Tcl_AppendStringsToObj(obj_body, "\nincr ", counter_name, " ", str_increment, (char *)NULL); /* printf("after incr append\n"); */ /* please note that a negative increment could be used
/* for ( ; sign*start < sign*end ; start += increment ) */ for ( ; sign*start < sign*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; } /*printf("incrementing\n"); */ result = Tcl_GetIntFromObj(interp, obj_counter, &start); if (result != TCL_OK) { return result; } } if ( isSharedObj ) { Tcl_DecrRefCount( obj_body ); } if ( isSharedObjCounter ) { Tcl_DecrRefCount( obj_counter ); } //Tcl_UnlinkVar(interp, counter_name); /* printf("after DecrRefCount\n"); */ if (result == TCL_ERROR) { return result; } Tcl_ResetResult(interp); return TCL_OK; }
Category Command | Category Control Structure | Category Critcl