Common Lisp - apply function to every other element in list
Asked Answered
A

5

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

Aldric answered 7/8, 2016 at 17:52 Comment(0)
M
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)))
Muezzin answered 7/8, 2016 at 18:14 Comment(0)
E
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.

Evonneevonymus answered 7/8, 2016 at 18:40 Comment(2)
Does not work for a list with an odd number of elements.Muezzin
@Muezzin Thanks, I missed itEvonneevonymus
M
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)))
Muezzin answered 7/8, 2016 at 18:14 Comment(0)
S
6
(defun double-every-other (xs)
  (loop for x in xs
        for doublep = nil then (not doublep)
        collect (if doublep (* x 2) x)))
Susquehanna answered 7/8, 2016 at 19:30 Comment(0)
P
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)
Privilege answered 9/8, 2016 at 13:21 Comment(1)
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.Ynes
R
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)
Richers answered 11/8, 2016 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.