在Haskell中将列表分成两部分的所有可能性

时间:2013-03-02 19:26:39

标签: list haskell split

在Haskell中创建将一个(偶数)列表分成两个的所有可能性的最直接/最有效的方法是什么?我玩弄拆分列表的所有排列,但这将添加许多额外的 - 所有的实例,其中每一半包含相同的元素,只是以不同的顺序。例如,

[1,2,3,4] should produce something like:

[ [1,2],  [3,4] ]
[ [1,3],  [2,4] ]
[ [1,4],  [2,3] ]

编辑:感谢您的评论 - 元素的顺序和结果的类型对我来说不如概念 - 来自一个组的所有两个组的表达式,其中元素顺序不重要。

4 个答案:

答案 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 []