如何在Lisp中比较两个列表

时间:2019-02-26 23:43:11

标签: list lisp common-lisp

我对Lisp仍然非常陌生,正在寻找解决特定问题的Lisp方法。

我有两个列表:

Setq list-a '(2,3))
Setq list-b '(1,2,3))

我需要确定列表a中的元素是否出现在列表b中,即2和3是否应该连续出现在列表b中。

如果要在JS中解决此问题,我将在b中找到a的第一个元素的索引(即列表b中的2的索引),然后检查连续的位置。

由于我对Lisp的了解非常有限,所以我想知道是否有任何内置函数可以使用。

PS。这两个列表的所有元素都是唯一的。

2 个答案:

答案 0 :(得分:4)

在ANSI Common Lisp中,search函数将确定较长序列的子序列是否等效于较短序列:

[1]> (search '(2 3) '(1 2 3))
1

例如,在这里search(2 3)的位置1处发现了(1 2 3)

search与其他类型的序列(例如向量和字符串)一起使用:

[2]> (search "ef" "abcdef")
4

如果搜索失败,search返回nil

答案 1 :(得分:3)

这里的答案不依赖于使用为您执行此操作的CL函数。在现实生活中,这将是一件愚蠢的事情:该语言具有一些功能,可以为您解答这些问题,从而避免像这样无休止地重新发明轮子。但是,出于教育目的,编写这样的函数来查看如何将算法转换为代码很有趣。

算法。

  • 确定A是否为B的子列表:

    • 如果A是空列表,则为空;
    • 如果A不为空,而B为空,则不为空;
    • 如果A是B的前导子列表,则它是子列表;
    • 如果A是B尾部的子列表,则它是B的子列表。
  • 确定A是否为B的前导子列表:

    • 如果A是空列表,则为空;
    • 如果A不为空,而B不为空;
    • 如果A的第一个元素等于B的第一个元素,并且A的其余部分是B的其余部分的前导子列表,那么它就是。

实际上,我们可以稍微简化一下:我们不需要在两个地方都对空列表进行所有检查。但是我没有在下面做这件事,因为它很容易出错(这个答案的先前版本 did 弄错了!)。

因此,我们要做的就是将该描述转换为Lisp。

注释。

  • 这依赖于本地辅助功能leading-sublist-p来告诉您它是否是上述的主要子列表。
  • 这不是完全可靠的CL,因为它假定将优化尾调用:如果不优化,则长列表将耗尽堆栈。但是它比我认为的等效显式迭代版本还要漂亮。
  • 这根本不尝试处理可能的循环性,因此在存在循环列表的情况下会出现终止问题(如果其中一个列表是循环的,则不会终止)。
  • 您可以采用通常的CL方式,使用test关键字参数指定元素比较功能,默认值为eql
  • 实际上,主循环也是any-sublist-p的局部函数,它的存在纯粹是为了避免必须在迭代过程中传递关键字参数(我本来根本没有将其传递下来,然后决定地,我不想考虑循环中关键字参数解析的任何可能的开销。

这里是:

(defun sublistp (a b &key (test #'eql))
  ;; is A a sublist of B, comparing elements with TEST.
  ;;
  ;; Return two values: either NIL and NIL if it is not a leading
  ;; sublist or T and the tail of B at which it matched.
  ;;
  ;; This works by asking whether A is a leading sublist of successive
  ;; tails of B
  ;;
  (labels ((leading-sublist-p (x y)
             ;; is X a leading sublist of Y?
             (cond ((null x)
                    ;; the empty list is a leading sublist of any list
                    t)
                   ((null y)
                    ;; a non-empty list is not the leading sublist of
                    ;; the empty list
                    nil)
                   ((funcall test (first x) (first y))
                    ;; otherwise X is a leading sublist of Y if the
                    ;; first two elements compare the same and the
                    ;; tail of X is a leading sublist of the tail of Y
                    (leading-sublist-p (rest x) (rest y)))))
           (any-sublist-p (x y)
             ;; this does the work: it's here merely to avoid having
             ;; to pass the TEST argument down in the recursion.
             (cond ((null x)
                    ;; the empty list is a sublist of any list
                    (values t y))
                   ((null y)
                    ;; a non-empty list is not a sublist of an empty
                    ;; list
                    (values nil nil))
                   ((leading-sublist-p x y)
                    ;; if X is a leading sublist of Y it's a sublist
                    (values t y))
                   (t
                    ;; otherwise X is a sublist of Y if it is a
                    ;; sublist of the tail of Y
                    (any-sublist-p x (rest y))))))
    (any-sublist-p a b)))

对于增加值,这是一个通过比较连续尾部和原始参数来检测一些但不是全部圆度的版本。这很便宜(每个循环额外进行两次eq测试),但是找不到所有的循环性:为此,您需要成熟的发生检查,这很昂贵。

(defun sublistp (a b &key (test #'eql))
  ;; is A a sublist of B, comparing elements with TEST.
  ;;
  ;; Return two values: either NIL and NIL if it is not a leading
  ;; sublist or T and the tail of B at which it matched.
  ;;
  ;; This works by asking whether A is a leading sublist of successive
  ;; tails of B
  ;;
  (labels ((leading-sublist-p (x y)
             ;; is X a leading sublist of Y?
             (cond ((null x)
                    ;; the empty list is a leading sublist of any list
                    t)
                   ((null y)
                    ;; a non-empty list is not the leading sublist of
                    ;; the empty list
                    nil)
                   ((funcall test (first x) (first y))
                    ;; otherwise X is a leading sublist of Y if the
                    ;; first two elements compare the same and the
                    ;; tail of X is a leading sublist of the tail of Y.
                    (let ((rx (rest x))
                          (ry (rest y)))
                      ;; If the tail of X is A then A is circular at
                      ;; this point and we should give up & similarly
                      ;; for Y.  Note this does not find all
                      ;; circularities, but finding some is perhaps
                      ;; better than not finding any.
                      (when (eq rx a)
                        (error "A is trivially circular"))
                      (when (eq ry b)
                        (error "B is trivially circular"))
                      (leading-sublist-p rx ry)))))
           (any-sublist-p (x y)
             ;; this does the work: it's here merely to avoid having
             ;; to pass the TEST argument down in the recursion.
             (cond ((null x)
                    ;; the empty list is a sublist of any list
                    (values t y))
                   ((null y)
                    ;; a non-empty list is not a sublist of an empty
                    ;; list
                    (values nil nil))
                   ((leading-sublist-p x y)
                    ;; if X is a leading sublist of Y it's a sublist
                    (values t y))
                   (t
                    ;; otherwise X is a sublist of Y if it is a
                    ;; sublist of the tail of Y
                    (any-sublist-p x (rest y))))))
    (any-sublist-p a b)))

以下是此版本检测微不足道的参数:

> (sublistp (let ((a (list 1)))
                          (setf (cdr a) a)
                          a)
                       '(1 2 3 4))

Error: A is trivially circular
  1 (abort) Return to top loop level 0.

对于hack的价值,这是一个明确的迭代版本:我觉得这很难理解。

(defun sublistp (a b &key (test #'eql))
  ;; is A a sublist of B, comparing elements with TEST.
  ;;
  ;; Return two values: either NIL and NIL if it is not a leading
  ;; sublist or T and the tail of B at which it matched.
  ;;
  ;; This works by asking whether A is a leading sublist of successive
  ;; tails of B
  ;;
  (flet ((leading-sublist-p (x y)
           ;; is X a leading sublist of Y?
           (loop for first-cycle = t then nil
                 for xt = x then (rest xt)
                 for yt = y then (rest yt)
                 unless first-cycle     ;circularity only after 1st cycle
                 do (cond
                     ;; If the tail of X is A then A is circular at
                     ;; this point and we should give up & similarly
                     ;; for Y.  Note this does not find all
                     ;; circularities, but finding some is perhaps
                     ;; better than not finding any.
                     ((eq xt a)
                      (error "A is trivially circular"))
                     ((eq yt b)
                      (error "B is trivially circular")))
                 do (cond
                     ((null xt)
                      ;; the empty list is a leading sublist of any
                      ;; list
                      (return-from leading-sublist-p t))
                     ((null yt)
                      ;; a non-empty list is not the leading
                      ;; sublist of the empty list
                      (return-from leading-sublist-p nil))
                     ((not (funcall test (first xt) (first yt)))
                      ;; leading elements differ: fail
                      (return-from leading-sublist-p nil))))))
    (cond ((null a)
           ;; the empty list is the sublist of any list
           (values t b))
          ((null b)
           ;; no non-empty list is the sublist of any list
           (values nil nil))
          (t
           (loop for bt = b then (rest b)
                 do (cond 
                     ((null bt)
                      (return-from sublistp (values nil nil)))
                     ((leading-sublist-p a bt)
                      (return-from sublistp (values t bt)))))))))