tags:

views:

1219

answers:

2

I'd like to find elisp's analog of:

sum(n for n in numbers if n % 2) # Python
numbers.select { |n| n % 2 != 0 }.inject { |a, b| a + b } # Ruby

Imperative way:

(defun oddp (number)
  (not (= (mod number 2) 0)))

(defun sum-odd-with-dolist (list)
  (let ((acc 0))
    (dolist (item list acc)
      (if (oddp item)
          (setq acc (+ item acc))))))

From Porting Common Lisp:

(defun sum-odd-with-dolist-incr (list)
  (let ((total 0)) 
    (dolist (item list) 
      (if (oddp item)
          (incf total item))) 
      total))

Using 'cl-*' loop:

(defun sum-odd-with-loop (list)
  (loop for x in list if (oddp x) sum x))

(sum-odd-with-loop '(1 2 3))
4

Is there a more idiomatic way to do it (that does not require cl-* packages)?

Related:

How to sum a list of numbers in Emacs Lisp?

+3  A: 

(apply '+ (delq nil (mapcar (lambda (x) (and (= 1 (% x 2)) x)) '(1 2 3 4 5))))
rzab
Does it copy the whole list if the first argument is even?
J.F. Sebastian
There is no arguments, i've wrote just an expression. And what it does is this: it filters the list and then applies '+. Filtering constructs new list, indeed. Anyway, the result does not rely on first argument being even or odd. So i'm not sure i understand your question right..
rzab
s/argument/1st element of the list/. Documentation for `delq` says that it can't delete the first element in-place (or I've misunderstood something).
J.F. Sebastian
I was completely wrong about delq, it may modify the list in case "object" is not the first element. So, would remq be proper, as it creates a new list?
rzab
Although, mapcar constructs new list just for delq/remq, so original list stays intact.
rzab
So, yeah, anyway, thanks for pointing out the modifying effect of delq :)
rzab
+7  A: 

The idiomatic way to do it is to use the functions and macros in the cl packages. They come as standard with Emacs Lisp and there's nothing wrong with using them.

I doubt you'll find a way to do it that's as terse and clear as

(loop for x in list if (oddp x) sum x)

There are more functional ways to do it, such as

(apply #'+ (remove-if-not #'oddp list))

but this uses remove-if-not which is from the cl-seq package. You could write out a loop by hand:

(let ((sum 0)) (dolist (x list sum) (when (oddp x) (incf sum x))))

but this uses dolist and incf which are both in the cl-macs package. Basically you can't escape the cl package: oddp itself is a cl function!

My best effort using absolutely no cl facilities is this:

(apply #'+ (mapcar (lambda (x) (* x (mod x 2))) list))

but it would absurd to use this in practice instead of the (loop ...) version.

Gareth Rees
Thanks. I like variant w/ remove-if-not despite it is a CL (though works without #). What is the online reference for such functions, is it http://www.gnu.org/software/emacs/manual/elisp.html ?
J.F. Sebastian
It's http://www.gnu.org/software/emacs/manual/cl.html — but if you have Emacs, you may already have the cl manual; try typing C-h i m cl RET.
Gareth Rees
Common Lisp itself has a HyperSpec, which is online in various places, including here: http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
Gareth Rees