为了将任意集合分成两个子集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过滤器会直接产生答案吗?
答案 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)
可以使用zipWith
和inits
的{{1}}进行滑动拆分。对于每个拆分,为较小的子列表生成解决方案,并将元素附加到拆分点到所有此类解决方案。
tails