替换(嵌套)中的符号? LISP 列表

Replacing symbols in a (nested)? List in LISP

我提供的列表格式如下:

(test '((Q H)(A D)(J C)(Q S)(3 S)))

目的是搜索列表并将符号 J Q K 和 A 替换为相应的数字 11 12 13 和 14。目前我的功能是这样的(对不起):

    (defun test (hand)
  (cond ((equal (first (first hand)) 'J)
         (setf (first (first hand)) '11))
        ((equal (first (first hand)) 'Q)
         (setf (first (first hand)) '12))
        ((equal (first (first hand)) 'K)
         (setf (first (first hand)) '13))
        ((equal (first (first hand)) 'A)
         (setf (first (first hand)) '14))
        (t (print '(It's ogre now))))
  (cond ((equal (first (second hand)) 'J)
         (setf (first (second hand)) '11))
        ((equal (first (second hand)) 'Q)
         (setf (first (second hand)) '12))
        ((equal (first (second hand)) 'K)
         (setf (first (second hand)) '13))
        ((equal (first (second hand)) 'A)
         (setf (first (second hand)) '14))
        (t (print '(It's ogre now))))
  (cond ((equal (first (third hand)) 'J)
         (setf (first (third hand)) '11))
        ((equal (first (third hand)) 'Q)
         (setf (first (third hand)) '12))
        ((equal (first (third hand)) 'K)
         (setf (first (third hand)) '13))
        ((equal (first (third hand)) 'A)
         (setf (first (third hand)) '14))
        (t (print '(It's ogre now))))
  (cond ((equal (first (fourth hand)) 'J)
         (setf (first (fourth hand)) '11))
        ((equal (first (fourth hand)) 'Q)
         (setf (first (fourth hand)) '12))
        ((equal (first (fourth hand)) 'K)
         (setf (first (fourth hand)) '13))
        ((equal (first (fourth hand)) 'A)
         (setf (first (fourth hand)) '14))
        (t (print '(It's ogre now))))
  (cond ((equal (first (fifth hand)) 'J)
         (setf (first (fifth hand)) '11))
        ((equal (first (fifth hand)) 'Q)
         (setf (first (fifth hand)) '12))
        ((equal (first (fifth hand)) 'K)
         (setf (first (fifth hand)) '13))
        ((equal (first (fifth hand)) 'A)
         (setf (first (fifth hand)) '14))
         (t (print '(It's ogre now))))
  (print hand))

我确信有一种更简洁的方法可以做到这一点。谁能指出我正确的方向?

如要将J/Q/K/A出现的次替换为相应的数字,可以使用SUBST函数:

(subst 11 'J (subst 12 'Q (subst 13 'K (subst 14 'A hand))))

或者,用一个循环,如果你有更多的东西可以替代,它看起来会更好:

(loop for (letter value) in '((J 11) (Q 12) (K 13) (A 14))
      for hand% = (subst value letter (or hand% hand))
      finally (return hand%))

让我们开始吧。 [dons code-review hat]


首先,您的程序将改变其输入,这被认为是错误的风格。

CL-USER> (defun test (hand)
  ...
  (print hand))
TEST
CL-USER> (defparameter *a-hand* '((Q H)(A D)(J C)(Q S)(3 S)))
*A-HAND*
CL-USER> (test *a-hand*)

(IT 'S OGRE NOW) 
((12 H) (14 D) (11 C) (12 S) (3 S)) 
((12 H) (14 D) (11 C) (12 S) (3 S))
CL-USER> *a-hand*
((12 H) (14 D) (11 C) (12 S) (3 S))
CL-USER> 

这是因为你到处都在使用 setf 来改变你的论点。执行该突变的替代方法是建立一个新的对列表,其中第一个元素替换为它的数字表示。构建一个新的 list 并应用一些转换恰好是 Common Lisp 中 mapcar 函数的目的。为了应用该策略,我们需要弄清楚您对传入列表的每个元素做了什么。从上面的模式应该很明显:

...
(cond ((equal (first (first hand)) 'J)
         (setf (first (first hand)) '11))
        ((equal (first (first hand)) 'Q)
         (setf (first (first hand)) '12))
        ((equal (first (first hand)) 'K)
         (setf (first (first hand)) '13))
        ((equal (first (first hand)) 'A)
         (setf (first (first hand)) '14))
        (t (print '(It's ogre now))))
...

所以。让我们得到一个单独的函数,它接受一个 list 并且有时用某个数字替换它的第一个元素。

(lambda (lst)
  (cond ((equal (first lst) 'J) 
         (cons '11 (cdr lst)))
        ((equal (first lst) 'Q)
         (cons '12 (cdr lst)))
        ((equal (first lst) 'K)
         (cons '13 (cdr lst)))
        ((equal (first lst) 'A)
         (cons '14 (cdr lst)))
        (t lst)))

请注意,我们已将 (first (first hand)) 替换为对我们的参数 (first lst) 的引用,并且我们现在使用 cons 来构建一个新列表,该列表的第一个替换元素位置。我们现在可以通过调用 mapcar.

将此函数映射到您的 hand
(defun test (hand)
  (print (mapcar (lambda (lst)
                   (cond ((equal (first lst) 'J) 
                          (cons '11 (cdr lst)))
                         ((equal (first lst) 'Q)
                          (cons '12 (cdr lst)))
                         ((equal (first lst) 'K)
                          (cons '13 (cdr lst)))
                         ((equal (first lst) 'A)
                          (cons '14 (cdr lst)))
                         (t lst)))
                 hand)))

这不再对您的论点造成明显的副作用。

CL-USER> (defun test (hand)
       (print (mapcar (lambda (lst)
                (cond ((equal (first lst) 'J) 
                   (cons '11 (cdr lst)))
                  ((equal (first lst) 'Q)
                   (cons '12 (cdr lst)))
                  ((equal (first lst) 'K)
                   (cons '13 (cdr lst)))
                  ((equal (first lst) 'A)
                   (cons '14 (cdr lst)))
                  (t lst)))
              hand)))
STYLE-WARNING: redefining COMMON-LISP-USER::TEST in DEFUN
TEST
CL-USER> (defparameter *a-hand* '((Q H)(A D)(J C)(Q S)(3 S)))
*A-HAND*
CL-USER> (test *a-hand*)

((12 H) (12 D) (12 C) (12 S) (12 S)) 
((12 H) (12 D) (12 C) (12 S) (12 S))
CL-USER> *a-hand*
((Q H) (A D) (J C) (Q S) (3 S))
CL-USER> 

您正在打印您的输出(连同原件中的一些 It's ogre now 行),而不仅仅是 return 打印输出。正如您在 REPL 中看到的那样,这将导致显示多个副本,如果您想稍后编写 test 函数,打印将不会真正为您做任何事情。如果您需要 print 输出,最好将那部分逻辑留给调用者,而只专注于在函数本身中进行替换。

(defun test (hand)
  (mapcar (lambda (lst)
            (cond ((equal (first lst) 'J) 
                   (cons '11 (cdr lst)))
                  ((equal (first lst) 'Q)
                   (cons '12 (cdr lst)))
                  ((equal (first lst) 'K)
                   (cons '13 (cdr lst)))
                  ((equal (first lst) 'A)
                   (cons '14 (cdr lst)))
                  (t lst)))
          hand))

我们在传递给 mapcarlambda 表单中仍然有一些重复发生。特别是,每个子句都涉及 cons 将新值添加到输入的 cdr(或同义词 rest)上。由于 Common Lisp 中的流程控制结构也是 return 值的函数,因此我们可以减少它们的重复性。

(defun test (hand)
  (mapcar (lambda (lst)
            (cons
             (cond ((equal (first lst) 'J) '11)
                   ((equal (first lst) 'Q) '12)
                   ((equal (first lst) 'K) '13)
                   ((equal (first lst) 'A) '14)
                   (t (car lst)))
             (cdr lst)))
          hand))

cond 一开始可能不是在这里使用的最佳选择。因为我们一直在做同样的检查,而且检查恰好是相等的,所以我们可以改用 case.

(defun test (hand)
  (mapcar (lambda (lst)
            (cons
             (case (first lst)
               (J '11)
               (Q '12)
               (K '13)
               (A '14)
               (t (first lst)))
             (cdr lst)))
          hand))

或者,您可以定义 table 个值,然后查找其中的第一个元素。

(defun test (hand)
  (let ((table '(J 11 Q 12 K 13 A 14)))
    (mapcar 
     (lambda (lst)
       (cons (or (getf table (car lst)) (car lst))
             (cdr lst)))
     hand)))

最后,您不需要引用数字 Common Lisp。他们已经在自我评估了。

(defun test (hand)
  (mapcar (lambda (lst)
            (cons
             (case (first lst)
               (J 11)
               (Q 12)
               (K 13)
               (A 14)
               (t (first lst)))
             (cdr lst)))
          hand))

如果您刚刚开始编程,我建议您通读 these fantastic tutorials for Racket,而不是立即钻研 Common Lisp。