A good example of such a loop is one which tests a list of values for a specified condition. E.g. test if all the integers in a list are even. Now there are several ways of going about this, some less efficient (both execution & programming) than others.
There's 4 major possibilities: ForEach / While / Recursion / VL-Some:
(defun TestEven-Foreach (SampleList / StillEven)
(setq StillEven T)
(foreach item SampleList
(setq StillEven (and StillEven (= (rem item 2) 0)))
)
)
(defun TestEven-While (SampleList / StillEven index)
(setq StillEven T
index (length SampleList)
)
(while (and StillEven (> (setq index (1- index)) -1))
(setq StillEven (= (rem (nth index SampleList) 2) 0))
)
StillEven
)
(defun TestEven-Recursive (SampleList /)
(or (not SampleList) (and (= (rem (car SampleList) 2) 0) (TestEven-Recursive (cdr SampleList))))
)
(defun TestEven-VL-Some (SampleList /)
(not (vl-some '(lambda (item) (/= (rem item 2) 0)) SampleList))
)
Which would you think is the most "efficient" to write and to execute? Well execution-wise, theoretically the foreach runs in order N (i.e. it iterates all the items even if the 1st was already uneven). The while iterates only up to an uneven (or end if none) - so this seems faster for the average case. Recursive & VL-Some has the least extras (temporary / state variables) and also uses no setq's - both also stop once an uneven is found so shouldn't be too different from the while. One caveat in AutoLisp is that recursion doesn't have much tail optimization (i.e. each call is placed on the stack and not cleared at the end), also arguments are passed by value (thus making a copy of the list for each "iteration" - could mean order N^2 ram usage), but worst is that ALisp has around 20k limit on nested recursive calls - so if your list is around 20K+ items you could expect an out-of-limit error.
So for my money I'd have "thought" it would be a toss-up between while & vl-some. Though looking deeper, I don't particularly like the (nth index SampleList) call ... that might slow down the while. Thus here's an alternative while:
(defun TestEven-While2 (SampleList / StillEven)
(setq StillEven T)
(while (and StillEven SampleList)
(setq StillEven (= (rem (car SampleList) 2) 0)
SampleList (cdr SampleList)
)
)
StillEven
)
And now for empirical tests, lets start with the extremes:
256 even numbers:
Benchmarking .................Elapsed milliseconds / relative speed for 16384 iteration(s):
(TESTEVEN-VL-SOME ALLEVEN).......1778 / 2.9 <fastest>
(TESTEVEN-WHILE2 ALLEVEN)........2667 / 1.93
(TESTEVEN-FOREACH ALLEVEN).......2824 / 1.82
(TESTEVEN-RECURSIVE ALLEVEN).....3323 / 1.55
(TESTEVEN-WHILE ALLEVEN).........5148 / 1 <slowest>
Already I can see I was right about the nth function. Yet also the vl-some & while2 doesn't give too much surprise - though the recursion seems a bit slow IMO. But this one is stacked in favour of foreach, since all have to run through the entire list anyway. And the difference between the while2 and foreach is extremely slight - take note that on another thread I've shown that there are variation if you compile the lisp to FAS/VLX.
Now for the 1st while's favourite, a list of 256 int's with one of the last being uneven:
Benchmarking ...................Elapsed milliseconds / relative speed for 65536 iteration(s):
(TESTEVEN-WHILE LASTUNEVEN)..........1217 / 10.67 <fastest>
(TESTEVEN-VL-SOME LASTUNEVEN)........6989 / 1.86
(TESTEVEN-WHILE2 LASTUNEVEN)........10624 / 1.22
(TESTEVEN-FOREACH LASTUNEVEN).......11279 / 1.15
(TESTEVEN-RECURSIVE LASTUNEVEN).....12980 / 1 <slowest>
Yep, it blows the socks off all of them! Even the 2nd while and the vl-some. I think because it's running into the uneven at such an early stage (due to it searching from the end instead of the beginning as the others do). Yet still the vl-some and while2 out performs the foreach and recursive - though not by much.
Now for one where the uneven number is around the start of the list:
Benchmarking ....................Elapsed milliseconds / relative speed for 131072 iteration(s):
(TESTEVEN-WHILE2 1STUNEVEN).........1950 / 20.87 <fastest>
(TESTEVEN-RECURSIVE 1STUNEVEN)......1965 / 20.71
(TESTEVEN-VL-SOME 1STUNEVEN)........4789 / 8.5
(TESTEVEN-FOREACH 1STUNEVEN).......11888 / 3.42
(TESTEVEN-WHILE 1STUNEVEN).........40700 / 1 <slowest>
Here the while2 comes into its own (as does the recursive). I think the vl-some uses most of its time in the to-ing-and-fro-ing with ARX - thus it takes more time to copy the list to the ObjectARX compiled code than simply checking a value. As expected foreach is out of its league here, but the 1st while is even more so!
Now for a more "average" case ... using 255 even numbers with an uneven placed in position 128:
Benchmarking .................Elapsed milliseconds / relative speed for 16384 iteration(s):
(TESTEVEN-VL-SOME MIDUNEVEN).......1201 / 2.57 <fastest>
(TESTEVEN-WHILE2 MIDUNEVEN)........1450 / 2.13
(TESTEVEN-RECURSIVE MIDUNEVEN).....1748 / 1.77
(TESTEVEN-FOREACH MIDUNEVEN).......2138 / 1.44
(TESTEVEN-WHILE MIDUNEVEN).........3089 / 1 <slowest>
This seems in line with my first thoughts. The 1st while is a bit of an anomaly due to the nth function call, but the while2 shows up the inefficiency of the foreach approach. The vl-some takes advantage of compiled code efficiency - but performs much the same idea as in the 1st while (though I think it might use an array which works much nicer with an index than a linked list).
In this example I'd go with the vl-some, since it's the most consistent (not to mention a whole lot less typing :kewl: ) - if pushed (e.g. only ALisp) then the while2. I'm a bit sceptical of the recursive, and since it's all over the place in extreme cases it's not ideal.
Of course (as the 1st while hints at), the internals of a loop has much more effect on efficiency than the loop itself. So some scenarios might just call for a different implementation inside the loop instead of a different type of loop, while others might benefit from a foreach. And as I've alluded to the foreach does get a bit of optimization if you compile it - which could shuffle the cards around a bit.
I'm not going to discuss the "correctness" of placing a return/goto/jump call inside the foreach (as the OP is basically asking), but that would make the for-like loop a lot more efficient now wouldn't it? Unfortunately the only way to even get near such in ALisp/VLisp is to use the
vl-exit-with-value function, which only works by exiting out of a compiled VLX (or so the help states) ... so you have to make a VLX with its own namespace simply to be able to return from somewhere in the middle of a loop! :ugly: Unfortunately there's no return/goto/jump in either ALisp or VLisp, but that might be a "good" thing in that it forces you to write more "understandable" code.