查找列表元素的任意长度子集

时间:2011-12-09 22:56:35

标签: algorithm recursion common-lisp combinations

  

免责声明:我很确定我已经设法搞砸了一些非常简单的东西,可能是因为我一直在讨论这个问题。   等待一些缓慢的C ++构建时“真正的工作”,所以我的头脑不是   在正确的地方。

看着 What's the most efficient way of generating all possible combinations of skyrim (PC Game) potions?我有一种天真的想法,即在Lisp中它将是一个非常非常简单的递归过滤器,用于生成大小为“n”的所有组合。在R中给出的答案很优雅,很好地展示了语言,但combn(list,n)方法引起了我的注意。 (http://stat.ethz.ch/R-manual/R-patched/library/utils/html/combn.html

(defun combn (list n)
  (cond ((= n 0) nil)
        ((null list) nil)
        ((= n 1) (mapcar #'list list))
        (t (mapcar #'(lambda (subset) (cons (car list) subset))
                   (combn (cdr list) (1- n))))))

(combn '(1 2 3 4 5 6 7 8 9) 3)

((1 2 3) (1 2 4) (1 2 5) (1 2 6) (1 2 7) (1 2 8) (1 2 9))

除此之外,这只会返回第一组组合......我无法准确地将我的脑袋包裹在错误的位置。似乎(= n 1)案例工作正常,但t案例应该采取不同的做法,例如从列表中删除(1 2)并重复?

所以,我试图修复它,变得更糟糕了:

 (defun combn (list n)
  (cond ((= n 0) nil) ((= n 1) (mapcar #'list list))
        ((null list) nil)
        (t (cons (mapcar #'(lambda (subset) (cons (car list) subset))
                         (combn (cdr list) (1- n)))
                 (combn (cdr list) n)))))

这是(t cons(的错误......我想。但是,如果cons是错误的答案,我不确定什么是正确的......? (减少使用2来演示输出......)

(combn '(1 2 3 4 5 6 7 8 9) 2)
(((1 2) (1 3) (1 4) (1 5) (1 6) (1 7) (1 8) (1 9))
 ((2 3) (2 4) (2 5) (2 6) (2 7) (2 8) (2 9))
 ((3 4) (3 5) (3 6) (3 7) (3 8) (3 9))
 ((4 5) (4 6) (4 7) (4 8) (4 9))
 ((5 6) (5 7) (5 8) (5 9))
 ((6 7) (6 8) (6 9))
 ((7 8) (7 9))
 ((8 9))
  NIL)

......看起来是正确的,除了无关的嵌套和最后的奖励NIL。 (我原以为((null list) nil)会过滤掉那个?)

我做错了什么? : - (

(而且,是否有一个更有效的标准例程?)

2 个答案:

答案 0 :(得分:1)

是的,cons不正确,你需要追加。这也是最后得到NIL的原因。我不能写Lisp,所以我会给你Haskell:

comb :: Int -> [a] -> [[a]]
comb 0 _ = [[]]
comb k (x:xs) = [x:ys | ys <- comb (k-1) xs] ++ comb k xs
comb _ _ = []

这是短暂而甜蜜的,但效率低下(并且没有检查否定k)。它经常会尝试选择比列表更长的元素。为了防止这种情况,人们会记录仍有多少元素可用。

comb :: Int -> [a] -> [[a]]
comb 0 _ = [[]]
comb k xs
  | k < 0     = []
  | k > len   = []
  | k == len  = [xs]
  | otherwise = go len k xs
    where
      len = length xs
      go l j ys
        | j == 1 = map (:[]) ys
        | l == j = [ys]
        | otherwise = case ys of
                        (z:zs) -> [z:ws | ws <- go (l-1) (j-1) zs] ++ go (l-1) j zs

丑陋但有效率。

答案 1 :(得分:1)

使用Common Lisp的解决方案。

请注意,如果传入的列表不能被指定的数字整除,则此版本会故意使用assert给您一个可持续的错误,但它很容易让它只是放置它任何&#34;剩余的&#34;最后一个较短列表中的项目,或使用error完全保释,而无法进行交互式修复。

基于方案&#39; srfi-1,由我调整为CL,并由Rainer Joswig大大改进

(defun split-by (list n &aux length)

"splits a list into multiple lists of length n.

Parameters:
  * list - the list to be split
  * n    - the size of the lists it should be broken into.

Returns:
 A list of smaller lists of the specified length (or signals an error).

Examples:
  (split-by '(1 2 3 4) 2)   ; => ((1 2) (3 4))
  (split-by '(1 2 3)   2)   ; => not evenly divisible"

  (assert (zerop (mod (setf length (length list)) n))
      (list)
    "list is not evenly divisible by ~A: ~A" n list)

  (if (plusp length)
      (cons (subseq list 0 n)
            (split-by (subseq list n) n))
    '()))