views:

277

answers:

8

Can you guys think of the shortest and the most idiomatic solution to all-but-one function?

;; all-but-one
;; checks if all but one element in a list holds a certain property
;; (all-but-one even? (list 1 2 4)) -> true
;; (all-but-one even? '(1)) -> true
;; (all-but-one even? '(2 4)) -> false

Edit: all but EXACTLY one.

+5  A: 

If the first element has the specified property, call all-but-one on the remainder of the list.

If the first element does not have the specified property, call all on the remainder of the list.

Anon.
Nice solution, but I have to ask (I'm a relative LISP newb compared to most LISPers) if the recursion will be optimized away?
San Jacinto
Define optimized away. In general, no, it'll remain recursive. Lisp as an idea has no concept of the optimization or anything. A single implementation/interpreter may implement Tail Call Optimization which would effectively make this code iterative, yes. :)
Tony k
The tail-call optimization is what i was referring to. Thanks. :)
San Jacinto
@Anthony Kanago: Scheme very much has a concept of tail-call optimization: http://www.cs.grinnell.edu/courses/Scheme/r5rs-html/r5rs_22.html
Pillsy
Anthony was talking about LISP and not Scheme. Could Anon write a code so that I could profile it?
kunjaan
Are you asking me to turn it into code because you are unable to, or is it just because you're lazy?
Anon.
Shuldn't it be the opposite of what you have written?If first element does not have property then call `all`.If first element does have property then call `all-but-one`.
rvirding
You are correct. That is how I had it originally - I must have become confused somewhere and changed it. Thanks.
Anon.
+1  A: 
(define (all-but-one p? xs)
  (= (length (filter p? xs)) (- (length xs) 1)))

OK, how about this: not so short, but just one pass over the list. You could do the same sort of thing using a fold.

(define (all-but-one p? xs)
  (let loop ((len 0) (sat 0) (tmp xs))
    (if (null? tmp)
        (= sat (- len 1))
        (loop (+ len 1)
              (if (p? (car tmp)) (+ sat 1) sat)
              (cdr tmp)))))
Ian Ross
nah too inefficient.
kunjaan
but definitely short, +1
deau
+4  A: 

With a better name:

(define (all-except-one pred l) (= 1 (count (negate pred) l)))

(But this is PLT specific.)

Eli Barzilay
nice. i didnt know about (count pred list) .
kunjaan
A: 

generalized Anon's idea.

(define (all-but-n n pred lst)
  (if (null? lst)
      (zero? n)
      (if (pred (car lst))
          (all-but-n n pred (cdr lst))
          (if (zero? n)
              #f
              (all-but-n (- n 1) pred (cdr lst))))))

(define (all-but-one pred lst) (all-but-n 1 pred lst))
torus
+2  A: 

The PLT solution is elegant, and ordinarily I prefer to use built-in higher-order functions as opposed to writing my own recursive functions. But if you want an efficient recursive solution with no allocation and no arithmetic, here it is:

(define (all-but-one pred l)
  (if (null? l) 
     #f
     ((if (pred (car l)) all-but-one all) pred (cdr l))))

The recursive call is in tail position, so both Scheme and Common LISP will compile this code into a tight loop. Some people might prefer this equivalent code:

(define (all-but-one pred l)
  (if (null? l) 
     #f
     (if (pred (car l))
        (all-but-one pred (cdr l))
        (all pred (cdr l)))))
Norman Ramsey
The "tight loop" is still there in the H.O. version, `count` is doing that just fine. The main extra cost would come from wrapping the predicate. (And BTW, Common Lisp in general will not optimize it to a loop, only some compilers will.)
Eli Barzilay
@Eli: Agreed. It's just that count does some arithmetic. Since arithmetic is basically free, I should not obsess over it. Does Common LISP not guarantee proper tail calls?
Norman Ramsey
Since the introduction of the JIT in PLT, arithmetics became extremely cheap. (As for CL -- many people suffer from the "bad for debugging" opinion...)
Eli Barzilay
A: 

A solution that should work on all decent Scheme implementations:

(define (all-but-one? pred values)

  (define (count-neg x)
    (if (not (pred x)) 1 0))

  (let loop ((c 0) (values values))
    (if (and (not (null? values))
         (<= c 1))
    (loop (+ c (count-neg (car values))) (cdr values))
    (= c 1))))
Vijay Mathew
A: 
(define (all-but-one? p ls)
  (define (all? ls)
    (or (null? ls)
        (and (p    (car ls))
             (all? (cdr ls))))
  (define (loop ls)
    (cond ((null? ls)   #f)
          ((p (car ls)) (all? (cdr ls)))
          (else         (loop (cdr ls)))))
  (loop ls))
antti.huima
+2  A: 

Common Lisp:

(defun all-but-one-p (predicate sequence)
  (= 1 (count-if-not predicate sequence)))

Example:

CL-USER 92 > (all-but-one-p #'evenp '(1 2 3))
NIL

CL-USER 93 > (all-but-one-p #'evenp '(1 2 4))
T

This LOOP-based version quits early if more than one element delivers a negative result for the predicate.

(defun all-but-one-p (predicate list)
  (= 1 (loop with not-pred = (complement predicate)
             for item in list count (funcall not-pred item) into counter
             when (> counter 1) do (return-from all-but-one-p nil)
             finally do (return counter))))
Rainer Joswig