Reputation: 265
This is my code to generate a cartesian product:
(defun cartesian-product (LIST)
(LOOP FOR X IN LIST
NCONC
(LOOP FOR Y IN LIST
COLLECT (LIST X Y))))
I tried outputting one of the cartesian products with this:
(defun cartesian-product-generator (CALLBACK LIST)
(LOOP FOR X IN LIST
NCONC
(LOOP FOR Y IN LIST
DO(FUNCALL CALLBACK (LIST X Y)))))
However there are errors when I tried testing it with:
(cartesian-product-generator '(A B C))
Error: Too few arguments in call to #<Compiled-function cartesian-product-generator #x30200097E60F>:
1 argument provided, at least 2 required. While executing: cartesian-product-generator, in process listener(1).
I am new to LISP and would like to know why there's an error and how to fix this error. Ultimately, I would like to output each cartesian product per call of the function.
For example, if the lists consists of ((1 1) (1 2) (2 1) (2 2))
.
I would like to generate (1 1)
. Then (1 2)
. Then (2 1)
. Lastly, (2 2)
.
Upvotes: 2
Views: 1852
Reputation: 195
Let me give a more basic answer (without loops), but more general, i.e., cartesian products of two, three and four lists (your case is binary cartesian product with two identical arguments):
(defun make-tuples-with-object (obj lst)
(mapcar #'(lambda (x) (list obj x)) lst))
(defun cartesian-product (list-1 list-2)
(apply #'append (mapcar #'(lambda (x) (make-tuples-with-object x list-2)) list-1)))
(defun cartesian-product-ternary (list-1 list-2 list-3)
(mapcar #'(lambda (x) (cons (car x) (second x)))
(cartesian-product list-1 (cartesian-product list-2 list-3))))
(defun cartesian-product-quaternary (list-1 list-2 list-3 list-4)
(mapcar #'(lambda (x) (cons (car x) (second x)))
(cartesian-product list-1 (cartesian-product-ternary list-2 list-3 list-4))))
Upvotes: 0
Reputation: 671
Just want to leave my 2 cents here.
You can also do this with a macro
(defmacro cartesian-product (lists)
(let* ((indices (loop for i from 1 to (length lists)
collect (gensym (format nil "~a-i-" i))))
(initial-value `(loop for ,(car (last indices)) in ',(car (last lists))
collect `(,,@indices))))
(reduce
(lambda (x y)
`(loop for ,(car x) in ',(cadr x)
nconc ,y))
(mapcar #'list (butlast indices) (butlast lists))
:from-end t
:initial-value initial-value)))
which expands with
(cartesian-product ((H P) (a b c) (1 2 3 5)))
to
(loop for #:|1-i-806| in '(h p)
nconc (loop for #:|2-i-807| in '(a b c)
nconc (loop for #:|3-i-808| in '(1 2 3 5)
collect `(,#:|1-i-806| ,#:|2-i-807| ,#:|3-i-808|))))
and results into
((H A 1) (H A 2) (H A 3) (H A 5) (H B 1) (H B 2) (H B 3) (H B 5) (H C 1) (H C 2) (H C 3) (H C 5)
(P A 1) (P A 2) (P A 3) (P A 5) (P B 1) (P B 2) (P B 3) (P B 5) (P C 1) (P C 2) (P C 3) (P C 5))
I like it because it is quite simple and does not require recursion.
Upvotes: 0
Reputation: 38809
Your first code does work correctly.
(defun cartesian-product (list)
(loop
for x in list
nconc (loop for y in list
collect (list x y))))
Calling it with '(a b c)
returns a list:
((A A) (A B) (A C) (B A) (B B) (B C) (C A) (C B) (C C))
You want to avoid building a list and use a callback instead. To simplify, first try to only print elements instead of collecting them.
That means that you do not care about returning the generated values up to the caller: you just want to generate them and print them as soon as they are available.
Basically, you can replace all nconc
and collect
keywords by do
, and add a call to print
:
(defun cartesian-product (list)
(loop
for x in list
do (loop for y in list
do (print (list x y)))))
A quick test on the REPL with '(a b c)
should print the same
elements as previously, each one on a separte line.
Now, you can just generalize print
and call anything you want:
(defun map-cartesian-product (function list)
(loop
for x in list
do (loop for y in list
do (funcall function (list x y)))))
Just to see if it still works, do a quick test:
(map-cartesian-product #'print '(a b c))
This should have the same behaviour as before.
Since you only iterate over a list for side-effects, you can use
DOLIST
:
(defun map-cartesian-product (function list)
(dolist (x list)
(dolist (y list)
(funcall function (list x y)))))
Again, you can test that it still works as previously.
Upvotes: 7
Reputation: 2148
You may want to look into the cl-coroutine
library available via quicklisp
. Then your cartesian-product
could be written as
(cl-coroutine:defcoroutine cartesian-product (list)
(loop for x in list
do (loop for y in list
do (cl-coroutine:yield (list x y)))))
An example use could be:
(cl-coroutine:with-coroutine (cartesian-product)
(let ((x (list 1 2 3)))
(loop for y = (cartesian-product x)
while y do (print y))))
; outputs (1 1) (1 2) ... (3 3)
Upvotes: 3