在Haskell中创建将一个(偶数)列表分成两个的所有可能性的最直接/最有效的方法是什么?我玩弄拆分列表的所有排列,但这将添加许多额外的 - 所有的实例,其中每一半包含相同的元素,只是以不同的顺序。例如,
[1,2,3,4] should produce something like:
[ [1,2], [3,4] ]
[ [1,3], [2,4] ]
[ [1,4], [2,3] ]
编辑:感谢您的评论 - 元素的顺序和结果的类型对我来说不如概念 - 来自一个组的所有两个组的表达式,其中元素顺序不重要。
答案 0 :(得分:4)
为了找到非空列表(偶数长度为n
)的所有分区为两个相等大小的部分,为了避免重复,我们可以假设第一个元素应该在第一部分中。然后,仍然可以找到将列表尾部拆分为长度n/2 - 1
和长度n/2
之一的所有方法。
-- not to be exported
splitLen :: Int -> Int -> [a] -> [([a],[a])]
splitLen 0 _ xs = [([],xs)]
splitLen _ _ [] = error "Oops"
splitLen k l ys@(x:xs)
| k == l = [(ys,[])]
| otherwise = [(x:us,vs) | (us,vs) <- splitLen (k-1) (l-1) xs]
++ [(us,x:vs) | (us,vs) <- splitLen k (l-1) xs]
如果适当调用,会分裂。然后
partitions :: [a] -> [([a],[a])]
partitions [] = [([],[])]
partitions (x:xs)
| even len = error "Original list with odd length"
| otherwise = [(x:us,vs) | (us,vs) <- splitLen half len xs]
where
len = length xs
half = len `quot` 2
生成所有分区而不会冗余地计算重复项。
luqui提出了一个很好的观点。我没有考虑过你想要用重复元素拆分列表的可能性。有了这些,它变得有点复杂,但并不多。首先,我们将列表分组为相同的元素(此处针对Ord
约束,仅针对Eq
完成,但仍可在O(length²)
中完成。这个想法是类似的,为了避免重复,我们认为前半部分包含的第一组元素多于第二组元素(或者,如果第一组中有偶数,则同样多,同样的限制适用于下一个小组等。)。
repartitions :: Ord a => [a] -> [([a],[a])]
repartitions = map flatten2 . halves . prepare
where
flatten2 (u,v) = (flatten u, flatten v)
prepare :: Ord a => [a] -> [(a,Int)]
prepare = map (\xs -> (head xs, length xs)) . group . sort
halves :: [(a,Int)] -> [([(a,Int)],[(a,Int)])]
halves [] = [([],[])]
halves ((a,k):more)
| odd total = error "Odd number of elements"
| even k = [((a,low):us,(a,low):vs) | (us,vs) <- halves more] ++ [normalise ((a,c):us,(a,k-c):vs) | c <- [low + 1 .. min half k], (us,vs) <- choose (half-c) remaining more]
| otherwise = [normalise ((a,c):us,(a,k-c):vs) | c <- [low + 1 .. min half k], (us,vs) <- choose (half-c) remaining more]
where
remaining = sum $ map snd more
total = k + remaining
half = total `quot` 2
low = k `quot` 2
normalise (u,v) = (nz u, nz v)
nz = filter ((/= 0) . snd)
choose :: Int -> Int -> [(a,Int)] -> [([(a,Int)],[(a,Int)])]
choose 0 _ xs = [([],xs)]
choose _ _ [] = error "Oops"
choose need have ((a,k):more) = [((a,c):us,(a,k-c):vs) | c <- [least .. most], (us,vs) <- choose (need-c) (have-k) more]
where
least = max 0 (need + k - have)
most = min need k
flatten :: [(a,Int)] -> [a]
flatten xs = xs >>= uncurry (flip replicate)
答案 1 :(得分:4)
这是一个紧密遵循定义的实现。
第一个元素总是进入左侧组。之后,我们将下一个head元素添加到一个或另一个组中。如果其中一个组变得太大,则别无选择,我们必须将所有其余组添加到较短的组中。
divide :: [a] -> [([a], [a])]
divide [] = [([],[])]
divide (x:xs) = go ([x],[], xs, 1,length xs) []
where
go (a,b, [], i,j) zs = (a,b) : zs -- i == lengh a - length b
go (a,b, s@(x:xs), i,j) zs -- j == length s
| i >= j = (a,b++s) : zs
| (-i) >= j = (a++s,b) : zs
| otherwise = go (x:a, b, xs, i+1, j-1) $ go (a, x:b, xs, i-1, j-1) zs
这会产生
*Main> divide [1,2,3,4]
[([2,1],[3,4]),([3,1],[2,4]),([1,4],[3,2])]
具有偶数长度列表的限制是不必要的:
*Main> divide [1,2,3]
[([2,1],[3]),([3,1],[2]),([1],[3,2])]
(代码以“差异列表”样式重写,以提高效率:go2 A zs == go1 A ++ zs
)。
编辑:这是如何运作的?想象一下,你自己坐在一堆石头上,把它分成两块。你把第一块石头放在一边,哪一块没关系(所以,左边说)。然后可以选择将每一块石头放在哪里 - 除非两个桩中的一个相比变得太小,因此我们必须立即将所有剩余的石头放在那里。
答案 2 :(得分:3)
Daniel Fischer的回答是解决问题的好方法。我提供了一种更糟糕(效率更低)的方式,但更明显(对我而言)对应于问题描述。我将列表的所有分区生成两个相等长度的子列表,然后根据您的等价定义过滤掉相应的分区。我通常解决问题的方法是这样开始 - 创建一个尽可能明显的解决方案,然后逐渐将其转换为更有效的解决方案(如果需要)。
import Data.List (sort, nubBy, permutations)
type Partition a = ([a],[a])
-- Your notion of equivalence (sort to ignore the order)
equiv :: (Ord a) => Partition a -> Partition a -> Bool
equiv p q = canon p == canon q
where
canon (xs,ys) = sort [sort xs, sort ys]
-- All ordered partitions
partitions :: [a] -> [Partition a]
partitions xs = map (splitAt l) (permutations xs)
where
l = length xs `div` 2
-- All partitions filtered out by the equivalence
equivPartitions :: (Ord a) => [a] -> [Partition a]
equivPartitions = nubBy equiv . partitions
测试
>>> equivPartitions [1,2,3,4]
[([1,2],[3,4]),([3,2],[1,4]),([3,1],[2,4])]
使用QuickCheck测试此实现与Daniel的等效性之后,我发现了一个重要的区别。显然,我的约束需要(Ord a)
,而他的则没有约束,这暗示了差异。特别是,如果你给他[0,0,0,0]
,你会得到一份包含三份([0,0],[0,0])
副本的清单,而我的只会给一份副本。没有具体说明哪一个是正确的;当将两个输出列表视为有序序列(通常认为是那种类型)时,丹尼尔是很自然的,当将它们视为集合或包时,我很自然(这就是这个问题似乎是如何对待它们的。) / p>
通过操作位置而不是列表中的值,可以从需要Ord
的实现转换为不需要的实现。我提出了这种转变 - 我认为这个想法源于本杰明·皮尔斯在双向编程方面的工作。
import Data.Traversable
import Control.Monad.Trans.State
data Labelled a = Labelled { label :: Integer, value :: a }
instance Eq (Labelled a) where
a == b = compare a b == EQ
instance Ord (Labelled a) where
compare a b = compare (label a) (label b)
labels :: (Traversable t) => t a -> t (Labelled a)
labels t = evalState (traverse trav t) 0
where
trav x = state (\i -> i `seq` (Labelled i x, i + 1))
onIndices :: (Traversable t, Functor u)
=> (forall a. Ord a => t a -> u a)
-> forall b. t b -> u b
onIndices f = fmap value . f . labels
在onIndices
上使用equivPartitions
根本不会加速它,但它会使它具有与Daniel的结果相同的语义(最多equiv
个结果)约束,以及我更天真,更明显的表达方式 - 我只是认为这是一种摆脱约束的有趣方式。
答案 3 :(得分:1)
我自己的通用版本,后来添加了很多,灵感来自Will的回答:
import Data.Map (adjust, fromList, toList)
import Data.List (groupBy, sort)
divide xs n evenly = divide' xs (zip [0..] (replicate n [])) where
evenPSize = div (length xs) n
divide' [] result = [result]
divide' (x:xs) result = do
index <- indexes
divide' xs (toList $ adjust (x :) index (fromList result)) where
notEmptyBins = filter (not . null . snd) $ result
partlyFullBins | evenly == "evenly" = map fst . filter ((<evenPSize) . length . snd) $ notEmptyBins
| otherwise = map fst notEmptyBins
indexes = partlyFullBins
++ if any (null . snd) result
then map fst . take 1 . filter (null . snd) $ result
else if null partlyFullBins
then map fst. head . groupBy (\a b -> length (snd a) == length (snd b)) . sort $ result
else []