推广组合函数?

时间:2015-03-08 07:25:18

标签: algorithm haskell functional-programming

我一直在解决Haskell上的几个组合问题,所以我写下了这两个函数:

permutations :: (Eq a) => [a] -> [[a]]
permutations [] = [[]]
permutations list = do
    x  <- list
    xs <- permutations (filter (/= x) list)
    return (x : xs)

combinations :: (Eq a, Ord a) => Int -> [a] -> [[a]]
combinations 0 _ = [[]]
combinations n list = do
    x  <- list
    xs <- combinations (n-1) (filter (> x) list)
    return (x : xs)

其工作原理如下:

*Main> permutations [1,2,3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
*Main> combinations 2 [1,2,3,4]
[[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]

那些令人不舒服的相似,所以我 来抽象它。我写了以下抽象:

combinatoric next [] = [[]]
combinatoric next list = do
    x  <- list
    xs <- combinatoric next (next x list)
    return (x : xs)

它接收一个控制如何过滤列表元素的函数。它可用于轻松定义排列:

permutations :: (Eq a) => [a] -> [[a]]
permutations = combinatoric (\ x ls -> filter (/= x) ls)

但我无法以这种方式定义combinations,因为它带有状态(n)。我可以使用额外的状态参数来扩展combinatoric,但这会变得过于笨重而且我记得这种方法不是必需的in a somewhat similar situation。因此,我想知道:是否可以使用combinations来定义combinatorics?如果不是,那么成功包含两个函数的combinatorics的更好的抽象是什么?

2 个答案:

答案 0 :(得分:5)

这不是您问题的直接答案(对不起),但我认为您的代码不正确。 EqOrd约束让我失望 - 它们不应该是必要的 - 所以我写了几个QuickCheck属性。

prop_numberOfPermutations xs = length (permutations xs) === factorial (length xs)
    where _ = (xs :: [Int])  -- force xs to be instantiated to [Int]

prop_numberOfCombinations (Positive n) (NonEmpty xs) = n <= length xs ==>
        length (combinations n xs) === choose (length xs) n
    where _ = (xs :: [Int])


factorial :: Int -> Int
factorial x = foldr (*) 1 [1..x]

choose :: Int -> Int -> Int
choose n 0 = 1
choose 0 r = 0
choose n r = choose (n-1) (r-1) * n `div` r

第一个属性检查the number of permutations of a list of length n is n!。第二个检查the number of r-combinations of a list of length n is C(n, r)。当我根据您的定义运行它们时,这两个属性都会失败:

ghci> quickCheck prop_numberOfPermutations
*** Failed! Falsifiable (after 5 tests and 4 shrinks):    
[0,0,0]
3 /= 6
ghci> quickCheck prop_numberOfCombinations 
*** Failed! Falsifiable (after 4 tests and 1 shrink):     
Positive {getPositive = 2}
NonEmpty {getNonEmpty = [3,3]}
0 /= 1

当输入列表包含重复元素时,您的函数看起来会失败。为不正确的实现编写抽象并不是一个好主意 - 在你走路之前不要尝试并运行!您可能会发现阅读the source code标准库的permutations定义很有帮助,{{3}}没有Eq约束。

答案 1 :(得分:1)

首先让我们改进原始功能。您假设所有元素都与permutations的相等不同,并且它们是不同的并且具有combinations的顺序。这些约束不是必需的,并且如另一个答案中所述,代码可能产生错误的结果。在robustness principle之后,让我们接受无约束的列表。为此,我们需要一个帮助函数来生成列表的所有可能的拆分:

split :: [a] -> [([a], a, [a])]
split = loop []
  where
    loop _  []      = []
    loop rs (x:xs)  = (rs, x, xs) : loop (x:rs) xs

请注意,该实现会导致此函数返回的前缀被反转,但这并不是我们所需要的。

这允许我们编写通用permutationscombinations

permutations :: [a] -> [[a]]
permutations [] = [[]]
permutations list = do
    (pre, x, post) <- split list
    -- reversing 'pre' isn't really necessary, but makes the output
    -- order natural
    xs <- permutations (reverse pre ++ post)
    return (x : xs)

combinations :: Int -> [a] -> [[a]]
combinations 0 _ = [[]]
combinations n list = do
    (_, x, post) <- split list
    xs <- combinations (n-1) post
    return (x : xs)

现在他们有什么共同点:

  • 在每一步中,他们选择一个要输出的元素,
  • 更新要从中挑选的元素列表和
  • 在满足某些条件后停止。

最后一点有点问题,因为permutations我们一旦可供选择的列表为空就结束,而combinations我们有一个计数器。这可能是难以概括的原因。我们可以通过实现permutations步骤的数量等于输入列表的长度来解决这个问题,因此我们可以用重复次数来表达条件。

对于这些问题,使用StateT s [] monad来表达它们通常非常方便,其中s是我们正在使用的状态。在我们的例子中,它将是可供选择的元素列表。然后可以用StateT [a] [] a表示组合函数的核心:从状态中选择一个元素并更新下一步的状态。由于有状态计算都发生在[] monad中,因此我们会自动分支所有可能性。有了它,我们可以定义一个通用函数:

import Control.Monad.State

combinatoric :: Int -> StateT [a] [] b -> [a] -> [[b]]
combinatoric n k = evalStateT $ replicateM n k

然后通过指定适当的重复次数和核心permutations函数来定义combinationsStateT [a] [] a

permutations' :: [a] -> [[a]]
permutations' xs = combinatoric (length xs) f xs
  where
    f = StateT $ map (\(pre, x, post) -> (x, reverse pre ++ post)) . split


combinations' :: Int -> [a] -> [[a]]
combinations' n xs = combinatoric n f xs
  where
    f = StateT $ map (\(_, x, post) -> (x, post)) . split