Discussion:
How to add two sequences of different lengths?
H Singh
2020-07-14 22:16:07 UTC
Hi,

I am trying to add two sequences like this:

[1 2 3 4 5 6 7 8]
[2 1 2 1 2 1 2 1]

and I can accomplish this using this one liner code in elisp.

(mapcar* (lambda (x y) (+ x y)) ; element-wise addition
[1 2 3 4 5 6 7 8]
[2 1 2 1 2 1 2 1]
)

Now, instead of the first sequence [1 2 3 4 5 6 7 8]
I can be given a sequence of indefinite length
such as
[1 2 3 4 5 6 7 8 9 10 11 12]
and what I really want to do is
to add 2 to each odd element and 1 to each even element

Thus, I want to be able to modify the above function to something like:

(mapcar* (lambda (x y) (????)) ;
[1 2 3 4 5 6 7 8] ; the length of this sequence is not clear ahead of time.
[2 1]
)

There are many ideas that come to my mind based on other languages but due to lack of experience in elisp, I cannot complete the solution.

For example, in matlab, it is possible to create a vector of any length on the fly.

Suppose, I get the (length [1 2 3 4 5 6 7 8]) and extend [1 2] to that.

Also,

instead of [1 2 .. 8] I can have [1 2 3 4 5 6 7 8 9] so its is not commensurate with [1 2].

I am looking for various styles, using lets say:

mapcar, mapcar*,

and perhaps some with and without macros.

Readability is more important than elegance. I do not want write once code that is not easily remembered.

H Singh
Ben Bacarisse
2020-07-14 22:52:37 UTC
Post by H Singh
There are many ideas that come to my mind based on other languages but
due to lack of experience in elisp, I cannot complete the solution.
For example, in matlab, it is possible to create a vector of any length on the fly.
Suppose, I get the (length [1 2 3 4 5 6 7 8]) and extend [1 2] to that.
Also,
instead of [1 2 .. 8] I can have [1 2 3 4 5 6 7 8 9] so its is not commensurate with [1 2].
mapcar, mapcar*,
One way: make a circular list.

(defun tie (l) (setf (cdr (last l)) l) l)
(mapcar #'+ '(1 2 3 4 5) (tie '(1 2)))
Post by H Singh
and perhaps some with and without macros.
OK. You can use the loop macro and do it with calculation:

(loop for e in '(1 2 3 4 5) for i from 0 collect (+ (rem i 2) 1 e))
--
Ben.
H Singh
2020-07-15 00:31:07 UTC
Post by Ben Bacarisse
Post by H Singh
There are many ideas that come to my mind based on other languages but
due to lack of experience in elisp, I cannot complete the solution.
For example, in matlab, it is possible to create a vector of any length on the fly.
Suppose, I get the (length [1 2 3 4 5 6 7 8]) and extend [1 2] to that.
Also,
instead of [1 2 .. 8] I can have [1 2 3 4 5 6 7 8 9] so its is not
commensurate with [1 2].
mapcar, mapcar*,
One way: make a circular list.
(defun tie (l) (setf (cdr (last l)) l) l)
(mapcar #'+ '(1 2 3 4 5) (tie '(1 2)))
Post by H Singh
and perhaps some with and without macros.
(loop for e in '(1 2 3 4 5) for i from 0 collect (+ (rem i 2) 1 e))
You multi-posted on (at least) comp.lang.lisp as well. That often leads
to people doing unnecessary work when helping. Cross posting is usually
preferred.
--
Ben.

------------

You know, this is not my fault. I am using google groups to post this. It DID not allow me to cross post as I wanted to do it by entering the other group as it did not show me groups list.

Also, it is not allowing me to reply to any messages except for the first one. (This I figured out) thats why you see all the greaterthan signs.

You can try it yourself as google keeps on changing its system. I have no other nntp access.
---------

Now then: thanks for your reply. I tried both of them inside emacs and they gave error.

I also entered
(require 'cl) ; for rudimentary commonlisp support.

Modifying your first one like this makes it work in emacs.

(defun tie (l) (setf (cdr (last l)) l) l)
(mapcar* (lambda (x y) (+ x y)) '(1 2 3 4 5) (tie '(2 1)))

For the second one

(loop for e in '(1 2 3 4 5) for i from 0 collect (+ (rem i 2) 1 e))

Debugger entered--Lisp error: (void-function rem)

Perhaps, you can explain a bit on the operation of your "tie"
and the second one also.
Ben Bacarisse
2020-07-15 01:39:34 UTC
Post by H Singh
Post by H Singh
You multi-posted on (at least) comp.lang.lisp as well. That often leads
to people doing unnecessary work when helping. Cross posting is usually
preferred.
You know, this is not my fault. I am using google groups to post
this.
Sorry. I did not want to sound too critical. I was as much an alert to

And now you've multi-posted this reply to comp.lang.lisp where my
comment looks silly! Oh well... This is Usenet in the age of Google.
Post by H Singh
Now then: thanks for your reply. I tried both of them inside emacs and they gave error.
Yes, they would. This is another way that cross posting would have
helped. I'd have known you wanted elisp solutions!
--
Ben.
Dmitry Alexandrov
2020-07-16 14:38:02 UTC
Post by H Singh
I have no other nntp access.
Why? Who banned you at, say, news.aioe.org or news.mixmin.net?
Post by H Singh
I also entered
(require 'cl) ; for rudimentary commonlisp support.
Please note, that this usage is somewhat deprecated [1].

You are supposed to use âcl-â prefixed functions instead:

(cl-mapcar #'+ '(1 2 3 4 5 6 7 8) '#0=(2 1 . #0#))
;; â (3 3 5 5 7 7 9 9)

(Regarding â#0=â see (info "(elisp) Circular Objects"))

[1] (info "(cl) Organization")

But @***@google.com is absolutely right, that using circular structures for loop control is more of an abuse of the language than a neat improvement of âreadabilityâ.

Thus, upon returning to your initial example, that used vectors rather than lists, youÊŒd find out that, while it still works so far:

(cl-map 'vector #'+ '(1 2 3 4 5 6 7 8) '#0=(2 1 . #0#))
;; â [3 3 5 5 7 7 9 9]

this:

(cl-map 'vector #'+ [1 2 3 4 5 6 7 8] '#0=(2 1 . #0#))

would fail with:

Debugger entered--Lisp error: (circular-list (2 1 . #2))
cl--mapcar-many(+ ([1 2 3 4 5 6 7 8] (2 1 . #4)) accumulate)
cl-mapcar(+ [1 2 3 4 5 6 7 8] (2 1 . #3))
apply(cl-mapcar + [1 2 3 4 5 6 7 8] (2 1 . #4))
cl-map(vector + [1 2 3 4 5 6 7 8] (2 1 . #4))

2020-07-15 16:47:21 UTC
... [snip] ... in elisp
Now, instead of the first sequence [1 2 3 4 5 6 7 8]
I can be given a sequence of indefinite length
such as
[1 2 3 4 5 6 7 8 9 10 11 12]
and what I really want to do is
to add 2 to each odd element and 1 to each even element
(mapcar* (lambda (x y) (????)) ;
[1 2 3 4 5 6 7 8] ; the length of this sequence is not clear ahead of time.
[2 1]
)
Ben Bacarisse suggests using a circular list for the list of things to add.
This will work as long as there is at least one finite list in the mapcar.

I'm not sure about the rules for elisp, but in Common Lisp using circular
lists is technically non-conforming code. The spec requires that the lists
be "proper lists", which excludes circular lists. But practically speaking,
all of the implementations handle circular lists, but without guarantee.

This gives you the most general solution, as you can easily change the values
to add and even the pattern to be arbitrary rather than just even or odd.

For a specific solution to your task, there are other mapping solutions,
in part depending on what you mean by even and odd element. If this is
determined by the value of the element, then there is a simple solution:
(mapcar* (lambda (x) (+ x (if (oddp x) 1 2))) [1 2 3 5 6 8 11])

If it is based on the index, you can do this with a similar construct, but
using a free variable in the mapping lambda function to keep track of the index
since lisp doesn't have anything like the Python "enumerate" built-in.
(let ((index 0)) ;; 1-based indexing.
(mapcar* (lambda (x) (setq index (1+ index)) (+ x (if (oddp index) 1 2)))
[1 2 3 5 6 8 11]))

If elisp has an increment function like Common Lisps's INCF you could use that
in place of the (SETQ ...). Note that this solution uses side effects from the
mapped function, so it won't be a purely functional answer.
Ben Bacarisse
2020-07-15 19:48:41 UTC
... [snip] ... in elisp
Now, instead of the first sequence [1 2 3 4 5 6 7 8]
I can be given a sequence of indefinite length
such as
[1 2 3 4 5 6 7 8 9 10 11 12]
and what I really want to do is
to add 2 to each odd element and 1 to each even element
(mapcar* (lambda (x y) (????)) ;
[1 2 3 4 5 6 7 8] ; the length of this sequence is not clear ahead of time.
[2 1]
)
Ben Bacarisse suggests using a circular list for the list of things to add.
This will work as long as there is at least one finite list in the mapcar.
I'm not sure about the rules for elisp, but in Common Lisp using circular
lists is technically non-conforming code. The spec requires that the lists
be "proper lists", which excludes circular lists. But practically speaking,
all of the implementations handle circular lists, but without
guarantee.
I'd forgetting that. Thanks.
This gives you the most general solution, as you can easily change the values
to add and even the pattern to be arbitrary rather than just even or odd.
For a specific solution to your task, there are other mapping solutions,
in part depending on what you mean by even and odd element. If this is
(mapcar* (lambda (x) (+ x (if (oddp x) 1 2))) [1 2 3 5 6 8 11])
If it is based on the index, you can do this with a similar construct, but
using a free variable in the mapping lambda function to keep track of the index
since lisp doesn't have anything like the Python "enumerate" built-in.
Right, but loop can help in some of these situations:

(loop for e in '(5 4 3 2 1) and i from 1 collecting (+ e i))
(let ((index 0)) ;; 1-based indexing.
(mapcar* (lambda (x) (setq index (1+ index)) (+ x (if (oddp index) 1 2)))
[1 2 3 5 6 8 11]))
If the pattern for the second list really should come from a list
(i.e. it's hard or messy to calculate) it could be written: