lanour
lanour

Reputation: 123

Common Lisp - apply function to every other element in list

I want to apply the function (* x 2) to every other element in a list and return the entire list using the loop macro. The solution I've come up with so far is this:

(defun double-every-other (xs)
  (loop for x in xs by #'cddr collect (* x 2)))

However, this will double every other element and only return the elements that were doubled, so if I executed:

(double-every-other '(1 2 3 4))

The result would be:

'(4 8)

But I want the result to be:

'(1 4 3 8)

Is there a way I can do this using (loop)?

Upvotes: 6

Views: 2982

Answers (5)

Vatine
Vatine

Reputation: 21288

You could use the loop "on" list iteration primitive. This takes a list of loop variables that will be "smeared" across the list, with the last being the tail of the entire remaining list. The conditional loop for is necessary to avoid multiplying nil if we have an odd number of arguments.

(defun double-every-other (list)
  (loop for (single double tail) on list by #'cddr
    if (null double)
      collect single
    else
      append (list single (* 2 double))))

And if we try to run it:

* (double-every-other '(1 2 3 4 5))

(1 4 3 8 5)

Upvotes: 0

leetwinski
leetwinski

Reputation: 17849

another version, without loop at all:

(defun make-cycled (&rest items)
  (setf (cdr (last items)) items))

(mapcar #'funcall
        (make-cycled #'identity (lambda (x) (* 2 x)))
        '(10 9 8 7 6 5 4 3))

;;=> (10 18 8 14 6 10 4 6)

Upvotes: 3

coredump
coredump

Reputation: 38967

Another version with less math:

(defun double-every-other (list)
  (loop
     for (a b) on list by #'cddr
     collect a
     when b collect (* b 2)))

(double-every-other '(1 2 3 4))
=> (1 4 3 8)

(double-every-other '(1 2 3 4 5))
=> (1 4 3 8 5)

Obviously, you won't be able to abstract the N as easily as the other answer (if you are thinking "macro", stop now). Here we iterate using the on keyword, which means each sublist is visited in turn. Since we use by #'cddr, every other sublist is skipped. The destructuring syntax (a b) binds the first and second elements of the visited list.

Upvotes: 11

Rainer Joswig
Rainer Joswig

Reputation: 139401

(defun double-every-other (xs)
  (loop for x in xs
        for doublep = nil then (not doublep)
        collect (if doublep (* x 2) x)))

Upvotes: 6

Renzo
Renzo

Reputation: 27434

You can for instance test an integer increasing while the list is scanned:

(defun double-every-other (xs)
  (loop for x in xs
     for i from 1
     if (oddp i)
     collect x
     else collect (* x 2)))

Upvotes: 7

Related Questions