6

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)?

5 Answers 5

11

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Does not work for a list with an odd number of elements.
@Renzo Thanks, I missed it
7

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)))

Comments

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

Comments

3

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)

1 Comment

An interesting hack. Do I understand correctly that make-cycled is creating a self-referential list, that's thus effectively infinite (or rather, it's a sort of circular ("cycled") list), so that the mapcar/funcall combo can just walk through as much of that as is needed to get through the data? Interesting indeed.
0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.