Common LISP (SBCL): Returning values from within l

2019-07-29 17:09发布

问题:

Preface: I'm currently taking a condensed course that is apparently taught in LISP and I've never worked with LISP in my life so I had to learn the language over a weekend. I apologize in advance for the abysmal code. I'm just familiar enough with LISP's syntax to get the code working and not much more.

I'm currently working on a program that solves the map coloring problem. This code takes a sequence where the first element of each sub sequence is a state and the second element represents a color. ex: '((A R) (B G) (C G) (D Y) (E B) (F B)) and then checks to make sure that no state has the same color as a state it's constrained by (defined by the constraint list). I know there are probably a lot of cleaner and simpler ways to do this but what I'm currently struggling with is having my dolist loops immediately return the value T whenever the if statement is met. So far I've been unable to get the functions to simply return a value and had to resort to this really ugly/wrong method of setting a variable to true and waiting for the loop to finish in order to make the code work. I've tried using return and just having T inside the if statements but, in both cases, the loop would finish instead of returning a value and I have no idea why.

(setq constraint '((A (B C E)) (B (A E F)) (C (A E F)) (D (F)) (E (A B C F)) (F (B C D E))))

(defun check_constraint (f s)
    (setf ans nil)
    (dolist (state constraint)
        (if (eq (first state) f)
            (if (search (list s) (second state))
                (setf ans T) ;;where I want it to just return T
            )
        )
    )
    ans
)

;;ex: ((A R) (B R)  (C B)  (D R)  (E B)  (F G))
(defun check_conflict (lst)
    (setf anb nil)
    (dolist (state lst)
        (dolist (neighbor (remove state lst))
            (if (check_constraint (first state) (first neighbor))
                (if (eq (second state) (second neighbor))
                    (setf anb T)) ;;where I want it to just return T
            )
        )
    )
    anb
)

EDIT: I ended up just fixing this with recursion. The code is cleaner now but I'd still love to know what my issue was. This is the recursive code.

(setq constraint '((A (B C E)) (B (A E F)) (C (A E F)) (D (F)) (E (A B C F)) (F (B C D E))))

(defun check_constraint (lst f s)
    (COND
        ((null lst) nil)
        ((search (list (car (car lst))) f)
            (if (search s (second (car lst))) T))
        (t (check_constraint (cdr lst) f s))
    )
)

(defun check_neighbor (check lst)
    (COND
        ((null lst) nil)
        ((check_constraint constraint (list (car check)) (list (first (first lst))))
            (if (eq (second check) (second (car lst))) T))
        (t (check_neighbor check (cdr lst)))
    )
)

;;(check_state '((A R) (B R) (C B) (D R) (E B) (F G)))
(defun check_state (lst)
    (COND
        ((null lst) nil)
        ((check_neighbor (car lst) (cdr lst)) T)
        (t (check_state (cdr lst)))
    )
)

回答1:

First a few style issues. You should use DEFVAR or DEFPARAMETER to declare global variables. Those should also have asterisks around the name to show that they are global (or special actually).

(defparameter *constraint*
  '((A (B C E))
    (B (A E F))
    (C (A E F))
    (D (F))
    (E (A B C F))
    (F (B C D E))))

The lisp convention for naming things is to use dashes between words (CHECK-CONSTRAINT instead of CHECK_CONSTRAINT). You should also prefer full words for variable names instead of abbreviations (LIST instead of LST). The closing parentheses should not be written on their own line.

Then the actual problem. You can use RETURN to return a value from a block named NIL. Loops establish such a block, so you can write the first function like

(defun check-constraint (first second)
  (dolist (state *constraint*)
    (when (and (eq first (first state))
               (member second (second state)))
      (return t))))

It's better to use WHEN instead of IF when there's only a then-branch. I also combined the two IFs into one using AND. Since you were wrapping S in a list for using SEARCH, I figured you probably want to use MEMBER instead (although I'm not sure since I don't exactly know what the code is supposed to do). You can change that back if it's wrong.

You probably could also simplify it to

(defun check-constraint (first second)
  (member second (second (find first *constraint* :key #'first))))

In the second function you have two loops. If you use RETURN to return from the inner one, you just end up continuing the outer loop and ignoring the return value. So you have to use RETURN-FROM to return from the function instead of the inner loop.

(defun check-conflict (list)
  (dolist (state list)
    (dolist (neighbor (remove state list))
      (when (and (check-constraint (first state) (first neighbor))
                 (eq (second state) (second neighbor)))
        (return-from check-conflict t)))))