Search code examples
loopslispcommon-lispgeneratorcartesian-product

How to generate one cartesian product in lisp?


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


Solution

  • 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.