[comp.lang.lisp] EQUAL on circular lists, yet again.

fy@lucid.com (Frank Yellin) (06/18/91)

Ton Kostelijk posts the following algorithm from Bernhard Pfahringer.
> (defun equal-circle (cons1 cons2)
>   (or (eql cons1 cons2)
>       (and (consp cons1)
>            (consp cons2)
>            (let ((f2 (first cons2))
>                  (r2 (rest cons2)))
>              (unwind-protect
>                  (progn (setf (first cons2) (first cons1)
>                              (rest cons2) (rest cons1))
>                         (and (equal-circle (first cons1) f2)
>                              (and (equal-circle (first cons1) f2)
>                                  (equal-circle (rest cons1) r2))))
>               (setf (first cons2) f2 (rest cons2) r2))))))

I received mail from Jim Boyce (the originator of the idea for my original
algorithm) this morning.  He sent me an example for which the above
algorithm goes into an infinite loop, even though it should return 't.

Imagine four cons cells, which we'll call a, b, c, and d, in which the car
and cdr of each of the four cells are themselves one of the four cons cells.

Trivially, any two should be equal-circle to each other, since c {a|d}* r
applied to any of the four cells gives you another of the cells.

Here's what a, b, c, and d look like:

           a       b       c       d
        (c . b) (a . c) (a . c) (d . d)


Now try (equal-circle a d)
                                a          b          c          d

(equal-circle a d)           (c . b)    (a . c)    (a . c)    (d . d)
  BASH d=d.d                 (c . b)    (a . c)    (a . c)    (c . b)
  (equal-circle c d)         (c . b)    (a . c)    (a . c)    (c . b)
    BASH d=c.b               (c . b)    (a . c)    (a . c)    (a . c)
    (equal-circle a c)       (c . b)    (a . c)    (a . c)    (a . c)  **
      BASH c=a.c             (c . b)    (a . c)    (c . b)    (a . c)
      (equal-circle c a) => t
      (equal-circle b c)     (c . b)    (a . c)    (c . b)    (a . c)
        BASH c=c.b           (c . b)    (a . c)    (a . c)    (a . c)
        (equal-circle a c)   (c . b)    (a . c)    (a . c)    (a . c)  **

The two lines marked ** are identical, but one is a subproblem of the
other.  So the result is infinite recursion.

Here's the example in "standard" lisp notation:
   (equal-circle '#1=(#3=(#1# . #3#) . #2=(#1# . #3#))   '#4=(#4# . #4#))
Try it and watch your stack overflow.

The moral:  rplaca and rplacd are dangerous toys, even when they go by
other names.  


-- Frank Yellin
   fy@lucid.com

donc@isi.edu (Don Cohen) (06/20/91)

It seems to me that the solution has to postulate a set of
equivalences.  Whenever you come across a non-list you can
potentially determine that the structures are not equivalent.
Otherwise you simply have to postulate that they are, and
check their cars and cdrs.  In order to avoid infinite
recursion, of course, any time that you come across a pair
that was already postulated equivalent, you don't recur.
The transitivity of equivalence can be used to reduce the
amount of checking.  The <angle brackets> below indicate
code to be filled in.

(defun equal-circle (x y &aux <equivalence-data>)
  (labels
   ((presume-equivalent (x y)
	<merge the equivalence classes of x and y>)
    (presumed-equivalent (x y)
	<determine whether the equivalence classes of x and y are the same>)
    (test-equivalent x y)
      (if (or (eq x y) (presumed-equivalent x y)) t
	(if (not (and (consp x) (consp y))) (return-from equal-circle nil)
	  (progn (presume-equivalent x y)
		 (test-equivalent (car x) (car y))
		 (test-equivalent (cdr x) (cdr y))))))
   (test-equivalent x y)))

There are pretty efficient ways of finding and merging equivalence classes.