7

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?

Community
  • 1
  • 1
jfs
  • 399,953
  • 195
  • 994
  • 1,670

3 Answers3

14

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
  • 64,967
  • 9
  • 133
  • 163
  • 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 ? – jfs Feb 26 '09 at 20:56
  • 3
    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 Feb 26 '09 at 21:53
  • 1
    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 Feb 26 '09 at 21:55
6

(apply '+ (delq nil (mapcar (lambda (x) (and (= 1 (% x 2)) x)) '(1 2 3 4 5))))
rzab
  • 1,657
  • 11
  • 7
  • Does it copy the whole list if the first argument is even? – jfs Feb 26 '09 at 20:44
  • 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 Feb 27 '09 at 09:20
  • 1
    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). – jfs Mar 01 '09 at 15:53
  • 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 Mar 02 '09 at 10:07
  • Although, mapcar constructs new list just for delq/remq, so original list stays intact. – rzab Mar 02 '09 at 10:22
  • So, yeah, anyway, thanks for pointing out the modifying effect of delq :) – rzab Mar 02 '09 at 10:24
2

I agree with Gareth Rees's answer, but if you use functional programming and data manipulation heavily in your code, i strongly recommend installing Magnar Sveen's dash.el list API. It has ridiculous amount of functions for writing concise and elegant functional code. Incidentally, it itself is written without cl library. Summing up odd numbers in a list would then be:

(-sum (--filter (= (mod it 2) 1) '(1 2 3 4 5)))

-sum is short for (-reduce '+ xs). Functions starting with 2 hyphens are anaphoric macros, they expose a temporary variable. For example, here instead of passing (lambda (x) (= (mod x 2) 1)) to --filter we simply write (= (mod it 2) 1), where it is used for a local variable.

Community
  • 1
  • 1
Mirzhan Irkegulov
  • 17,660
  • 12
  • 105
  • 166
  • A more concise way: `(-sum (-filter #'oddp '(1 2 3 4 5)))`, or my favourite, using threading macros: `(->> '(1 2 3 4 5) (-filter #'oddp) (-sum))`. – Pkkm Sep 15 '15 at 17:58
  • Good catch! But `oddp` is from `cl.el`, so if for some reason you don't use it in your code, you have to use `(= (mod it 2) 1)`. Do you mind if I integrate your comment in my answer (with attribution)? – Mirzhan Irkegulov Sep 15 '15 at 18:22
  • No problem, feel free to integrate with or without attribution. – Pkkm Oct 01 '15 at 21:27