将任意集合分成两个固定大小的子集

时间:2014-08-05 12:22:32

标签: haskell filter subset

为了将任意集合分成两个子集l和r,其中l具有固定大小n,我编写了以下代码:

 parts :: Int -> [a] -> [([a], [a])]
 parts n ls = (filter len . f) ls
  where len (lhs,rhs) = length lhs==n  -- the condition that the left subset is of fixed size n
        f []     = [([],[])]
        f (x:xs) = [ (x:l,r) | (l,r)<-f xs] ++ [ (l,x:r) | (l,r)<-f xs]

以下是其效果的示例。评估parts 2 "12345"收益率:

[ ("12","345")
, ("13","245")
, ("14","235")
, ("15","234")
, ("23","145")
, ("24","135")
, ("25","134")
, ("34","125")
, ("35","124")
, ("45","123")
]

请注意,我的解决方案会枚举所有子集,然后过滤掉所需的子集。我想你的子集功能很熟悉:

 subsets :: [a] -> [[a]]
 subsets []  = [[]]
 subsets (x:xs) = map (x:) (subsets xs) ++ subsets xs

就个人而言,我发现我的解决方案令人失望。它从较大的集合中过滤正确的答案。我向读者提出的问题是: 你能想出一个相当于parts的函数,但是如果没有这个a-posteriory过滤器会直接产生答案吗?

3 个答案:

答案 0 :(得分:1)

好的,这就是我想出来的:

parts :: Int -> [a] -> [([a],[a])]
parts n list = parts' 0 (length list) [] [] list where
  parts' _  _ ls rs []        = [(ls,rs)]
  parts' n' l ls rs as@(x:xs) | n' >= n     = [(reverse ls, reverse rs ++ as)]
                              | n' + l <= n = [(reverse ls ++ as, reverse rs)]
                              | otherwise   = parts' (n' + 1) (l - 1) (x : ls) rs xs 
                                           ++ parts' n' (l - 1) ls (x : rs) xs

如果子集的元素与原始集合中的元素顺序无关,则可以删除reverse的四种用法。

答案 1 :(得分:1)

subsets的启发,您最终可能

import Control.Arrow (first, second)
-- first f (x,y) = (f x, y) ; second f (x, y) = (x, f y)

parts n xs = parts' 0 xs where    
    parts' l (x:xs) | l <  n = map (first (x:))  (parts' (l + 1) xs) ++ -- 1a
                               map (second (x:)) (parts' l xs)          -- 1b
    parts' l xs     | l == n = [([],xs)]                                -- 2
    parts' _ _ = []                                                     -- 3

l包含到目前为止第一对的长度。只要该对尚不够长,我们将获取列表的第一个元素,并将其附加到我们对中的所有第一个元素(1a)。我们还要将它映射到第二个元素(1b)。请注意,在这种情况下,第一对的长度没有增加。

当第一对恰好足够长时(2),我们将把所有其他元素放入该对的后半部分。

当守卫的要求不成立(列表用尽)时,我们返回[](3)。这种方法还保留了元素的相对排序:

> parts 2 "12345"
[
  ("12","345"),
  ("13","245"),
  ("14","235"),
  ("15","234"),
  ("23","145"),
  ("24","135"),
  ("25","134"),
  ("34","125"),
  ("35","124"),
  ("45","123")
]

这种方法也适用于无限列表:

> map (second (const "...")) $ take 5 $ parts 3 [1..]
[([1,2,3],"..."),([1,2,4],"..."),([1,2,5],"..."),([1,2,6],"..."),([1,2,7],"...")]

(成对中的第二个元素仍然是无限列表)

答案 2 :(得分:1)

可以使用zipWithinits的{​​{1}}进行滑动拆分。对于每个拆分,为较小的子列表生成解决方案,并将元素附加到拆分点到所有此类解决方案。

tails