Page 1 of 1

Surprising behavior of dolist with break condition.

Posted: Fri Apr 03, 2015 7:32 am
by 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:

Code: Select all

(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.

Re: Surprising behavior of dolist with break condition.

Posted: Fri Apr 03, 2015 9:19 am
by ralph.ronnquist
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:

Code: Select all

(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.

Re: Surprising behavior of dolist with break condition.

Posted: Sat Apr 04, 2015 7:43 am
by newBert
Here is another solution :

Code: Select all

(set 'foo '(1 5 10 20 100 105))
(last (ref-all 50 foo > true))   ;-> 20

Re: Surprising behavior of dolist with break condition.

Posted: Sun Apr 12, 2015 6:18 am
by TedWalther
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:

Code: Select all

(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.