查找集合列表中的所有超集

时间:2016-11-11 05:21:28

标签: set common-lisp reduce

以下函数旨在返回集合列表中的所有超集 - 即删除列表中任何其他集合的子集。所以(超集'((1 2)(1 2 3)(3 2)(3 5)(5 3))) - > ((1 2 3)(3 5))。是否有更优雅/有效的方式来思考如何做到这一点?它似乎大致符合common-lisp“reduce”模型,除了两个非重叠集合简单地减少到它们自己。感谢您的任何简化或见解:

(defun supersets (sets)
  (let ((remaining-sets sets))
    (loop for set1 in sets
      do (loop for set2 in (set-difference remaining-sets (list set1))
           when (subsetp set1 set2)
            do (setq remaining-sets
                     (set-difference remaining-sets (list set1)))
               (return))
      finally (return remaining-sets))))

4 个答案:

答案 0 :(得分:3)

通过使用三个基本函数可以找到更有效的解决方案:

(defun supersets (sets)
  (delete-duplicates (sort (copy-list sets) #'<= :key #'length) :test #'subsetp))

首先,列表按其元素的长度排序(copy-list需要sort,因为delete-duplicates破坏性地修改了它的参数),所以我们现在有一个按长度递增的集合列表,所以每个集合只能是以下元素的子集。

然后我们应用:test原语函数,重新定义副本不是作为一个等于另一个的元素,而是作为另一个的子集。这是通过关键参数delete-duplicates完成的,该参数用于测试元素是否与另一个元素相等(即子集)。

请注意,我们可以使用remove-duplicates代替非破坏性版本copy-list,因为我们已使用:key获取了列表的新副本。

<强>加成

上述功能通过解释来定义:

  

删除列表

中任何其他集的子集的任何集合

as:从列表中删除设置所有元素 X,使得另一个元素Y与X⊆Y。

最后,这是一个在更一般的比赛中使用的函数的版本。它是为序列定义的,不仅对于列表,还有两个可选的关键字参数:test(defun supersets (sets &key (key #'identity) (test #'eql)) "Given a sequence of lists representing sets, remove all sets that are contained in others. The input sequence can be modified. The keyword parameter :key is a function that, applied to each element of the input list, returns the set that must be considered for the checking. The keyword parameter :test is a function used when testing the equality for the elements of the set." (delete-duplicates (sort sets #'<= :key (lambda(x) (length (funcall key x)))) :key key :test #'(lambda (set1 set2) (subsetp set1 set2 :test test)))) (supersets (copy-list '( ((1 2) (a)) ((1 2 3) (b)) ((3 2) (c)) ((4 5) (d)) ((5 4) (e)) )) :key #'car) ; => (((4 5) (D)) ((1 2 3) (B))) (supersets #( ((1 2) (a)) ((1 2 3) (b)) ((3 2) (c)) ((4 5) (d)) ((5 4) (e)) ) :key #'car) ; => #(((5 4) (E)) ((1 2 3) (B))) (supersets (copy-list '(("abc") ("bc" "abc"))) :test #'string=) ; => (("bc" "abc")) ,而可以破坏性地修改传递给它的序列(因此,使用注意,如果必须重复使用,则在传递之前复制序列,或者是常量列表。)

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);

答案 1 :(得分:1)

如果问题的定义是(如帖子中所述),

  

删除列表

中任何其他集的子集的任何集合

这将是一个文字实现(我使用了来自alexandria的CURRY。如果你不能拥有依赖项,你可以用常规LAMBDA替换它:

(defun supersets (sets)
  (remove-if (lambda (set)
               (some (curry #'subsetp set)
                     (remove set sets)))
             sets))

CL-USER> (supersets '((1 2) (1 2 3) (3 2) (3 5) (5 3)))
((1 2 3))

请注意,这也会从列表中删除(3 5),因为它是SUBSETP的{​​{1}}。如果要保留它,可以定义(5 3)的版本,该版本考虑相等的列表而不是子集。当然,那么你在结果中也会有SUBSETP。我没有看到(5 3)的任何合理解释,但不包括(3 5)

编辑:此处还有一个版本,其中包含(5 3)(3 5)之一。

(5 3)

另一个编辑:更高效的版本。这样,它就不会使用(defun supersets (sets) (let ((sets (remove-duplicates sets :test #'set-equal))) (remove-if (lambda (set) (some (curry #'subsetp set) (remove set sets))) sets))) 不断创建新列表。

REMOVE

答案 2 :(得分:0)

认为对这3个提供的功能中的每一个进行计时可能会很有趣。 (不是最好的,但只是在SBCL下获得5套10M次的给定列表的超集。)

davypough (me):  5.7 sec  4.8B cons
Renzo:           3.1      800M
jkiiski          5.0      2.2B

(注意:更改前2&#34;将jkiiski中的&#34; s删除为&#34;删除&#34; s。)

如果您不介意后续问题,那么如何估计这些序列函数的计算复杂性(如果没有深入到汇编代码中,或者运行增加N的实验并试图辨别曲线的形状)?例如,我的猜测是原始的删除重复是n平方?

答案 3 :(得分:0)

@Renzo。惊人。但是,对于我的应用程序,我需要将您的函数概括为包括:key和:test参数。例如,(超集'(((1 2)(a))((1 2 3)(b))((3 2)(c))((4 5)(d))((5 4)(e ))):key#'car:test#'equal) - &gt; (((1 2 3)(b))((4 5)(d))),因为数字实际上是列表,而顶级设置项是列表对。我的尝试不起作用。你能明白为什么吗?

(defun supersets (sets &key (key #'identity) (test #'eql))
  (delete-duplicates
    (sort (copy-list sets) 
          #'<= :key #'(lambda (set)
                        (length (funcall key set))))
    :test #'(lambda (set1 set2)
              (subsetp set1 set2 :key key :test test))))

(ps:另外,我注意到如果我只是在“let”中重新绑定输入“sets”参数(避免使用“copy-list”),原始示例中10M执行的时间减少到仅1秒这是一般原则,还是我偏离基础?谢谢。