Surprising behavior of dolist with break condition.

Started by TedWalther, April 03, 2015, 12:32:03 AM

Previous topic - Next topic

TedWalther

This affects dotimes, dolist, and the rest of the family.



I had a situation where I needed to scan through a list of numbers, and find the list index of the number that is just less than my target number.



Example:



(set 'foo '(1 5 10 20 100 105))

(dotimes (i (+ -1 (length foo)) (> (foo i) 50)) (foo i))
=> true
;; was expecting return value of 20 (last value before triggering break condition)


With the break condition, the loop always returns true.  This isn't what I expect; I expect the last value evaluated inside the form, not the condition test.  I want to abort the loop as soon as I find the value I'm looking for.  Is this wrong?  It affects pretty much all the looping constructs, and was very surprising to me.  Without the break condition, the loops return the last value evaluated in the body.



Also, is there a better idiom to do such scanning, some sort of binary search function?



I understand it is useful to know whether the break condition was triggered or not, couldn't that be saved in a system variable $brk the way regex output is?  Also, even leaving $idx behind after a form evaluation would be nice too.
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.

rrq

#1
Surprisingly often I also have that need of getting the index for the element prior to the match, and I've zeroed in on a phrase like the following:
(let (i (find 50 foo >=)) (if (nil? i) (- (length foo) 1) (zero? i) nil (- i 1)))
In that way the looping is within the primitive, and it avoids indexing, which might be slow especially for longer lists.



Regarding the loop return value I think I would agree that it lacks coherence as is. However, as I use those forms only imperatively, that return value is outside my box.

newBert

#2
Here is another solution :


(set 'foo '(1 5 10 20 100 105))
(last (ref-all 50 foo > true))   ;-> 20
<r><I>>Bertrand<e></e></I> − <COLOR color=\"#808080\">><B>newLISP<e></e></B> v.10.7.6 64-bit <B>>on Linux<e></e></B> (<I>>Linux Mint 20.1<e></e></I>)<e></e></COLOR></r>

TedWalther

#3
Ralph and NewBert, thank you for those answers.  Ralph, that is amazing, I keep forgetting about the "find" function, that was beautifully done.



I ended up going with this before I read your solutions:


(let (m 0) (while (> jday (lst m)) (++ m))))

In my application I didn't need a nil for no match, because I had a guarantee that the 0 index always pointed to a value that was less than the value I was testing.
Cavemen in bearskins invaded the ivory towers of Artificial Intelligence.  Nine months later, they left with a baby named newLISP.  The women of the ivory towers wept and wailed.  \"Abomination!\" they cried.