Helpful function: first-that

For the Compleat Fan
Locked
Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Helpful function: first-that

Post by Jeff »

Returns the first item that satisfies a predicate. Unlike find and ref, this returns the entire element, rather than its index.

Code: Select all

(define (first-that lambda-p lst)
  "Returns first item in list that satisfies lambda-p."
  (if (lambda-p (first lst))
    (first lst)
    (first-that lambda-p (rest lst))))
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Fanda
Posts: 253
Joined: Tue Aug 02, 2005 6:40 am
Contact:

Post by Fanda »

There is a function called 'exists' that should be able to do the same.

Fanda

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Thanks, I was looking for that ;)
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Lutz
Posts: 5289
Joined: Thu Sep 26, 2002 4:45 pm
Location: Pasadena, California
Contact:

Post by Lutz »

Beyond the fact Fanda mentioned that the built-in 'exists' does the same functionality, I wanted to make some other comments about this code:

Code: Select all

(define (first-that lambda-p lst) 
  "Returns first item in list that satisfies lambda-p." 
  (if (lambda-p (first lst)) 
    (first lst) 
    (first-that lambda-p (rest lst))))

This is the kind of algorithm people use who have learned Scheme or have been teached other older LISP dialects favouring recursion and trying to avoid iteration.

The principle used here is iterating through a list by applying an operation to the first element and then recursing the function with the rest of the list.

newLISP has many built-in function to make this type of problem easier to code and much faster in execution:

Code: Select all

(define (first-that lambda-p lst)
	(first (filter lambda-p lst)))
or to retrieve all for that lambda-p is not true:

Code: Select all

(define (first-that-not lambda-p lst)
	(first (clean lambda-p lst)))

Lutz

ps: this is not to criticize Jeff's code but to point out differences between programming in newLISP versus what is normally teached when learning Lisp ;-)

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Good point, Lutz. I do tend toward recursion and older lispy techniques. And we should always try the existing solution first before writing our own.

I did not use filter because (filter lambda-p lst) first expands by applying lambda-p to each item in lst. My solution short-circuits when it hits the first non-nil evaluation.

I did not imagine that filter would be faster since it is applying a lambda as well and does not short-circuit. Is there a fault in that logic?

PS I of course don't take offense Lutz. Code doesn't get better if it doesn't get comments :)
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Post by rickyboy »

Jeff,
Love the recursion! ;-) It's missing the base case, but looks beautiful anyway.

Lutz,
Your version, of course, doesn't need a base case, but Jeff is right about the short-circuiting logic. If he rewrote his short-circuiting version iteratively, it would probably be the most efficient lambda implementation. Your version would be pretty much be the most efficient way to implement it, if newLISP's evaluator was non-eager; then filter would only need to return its output's head to first, and not bother to process the rest of its input -- like UNIX pipelines, naturally short-circuiting!
(λx. x x) (λx. x x)

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Rickyboy,

The base case is nil because it is technically a predicate. If nothing in the list evaluates as t, it should simply not return any elements - an empty list.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Post by rickyboy »

Here's why the base case (which checks for the end of list) is needed:

Code: Select all

> (rest '())
()
> (first '())
nil
So, something like (first-that string? '(a-symbol 42)) will cause a stack overflow.
(λx. x x) (λx. x x)

Jeff
Posts: 604
Joined: Sat Apr 07, 2007 2:23 pm
Location: Ohio
Contact:

Post by Jeff »

Ah, I assumed that it would return nil. I wasn't thinking and got sloppy :). I should have done:

Code: Select all

(if (rest lst) (first-that lambda-p (rest lst)) nil)
or something on the last line there.
Jeff
=====
Old programmers don't die. They just parse on...

Artful code

Locked