【问题标题】:Generalizing a combinatoric function?泛化组合函数?
【发布时间】:2015-03-09 04:58:48
【问题描述】:

我一直在解决一些关于 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。因此,我想知道:是否可以使用combinatorics 定义combinations?如果不是,那么combinatorics 的更好抽象是什么,它成功地包含了这两个函数?

【问题讨论】:

  • 如果您不介意,您能解释一下(filter (/= x) list) 的工作原理吗?我看到 xlist (根据上一行),您正在尝试从 list 中过滤不等于 list 的元素?
  • permutationscombinations 的良好实现可能不需要 EqOrd 实例,除非您尝试做一些更复杂的事情以便 permuations [1,1] = [[1,1]]。跨度>
  • @thefourtheye 不,这里x 只是列表的一个元素(123...)。这实际上是一种从列表中删除元素的肮脏和错误的方法,即filter (/= 2) [1,2,3,4] == [1,3,4]。我相信有更好的选择。请注意,实现了列表的 monad 实例,以便 x &lt;- list 导致 x 不确定地成为 list 的每个元素,就好像该行将程序拆分为不同的宇宙一样。
  • @Viclib 那么,x &lt;- list 将迭代 list 并每次都给 x 一个来自 list 的新值?
  • @thefourtheye 是的,这就是 list monad 所做的。在 ghci 上试试这个:do { x &lt;- [1,2]; y &lt;- [3,4]; return (x,y) }。结果是:[(1,3),(1,4),(2,3),(2,4)].

标签: algorithm haskell functional-programming


【解决方案1】:

这不是您问题的直接答案(抱歉),但我认为您的代码不正确。 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 的定义很有帮助,它没有 Eq 约束。

【讨论】:

  • 没问题!但是我在原始线程下方评论了该列表不能包含重复的元素。不过,您是对的:我应该使用一组来更清楚地说明这一点。感谢您的关注!无论如何,如果有人好奇,为了在我的代码上解决这个问题,而不是过滤,只需按其索引删除元素。例如,可以为集合编写一个 monad 实例,它不仅提供元素,还提供列表的“其余部分”。
  • 我不认为这样的单子存在
【解决方案2】:

首先让我们改进原来的功能。您假设所有元素在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

然后定义permutationscombinations,通过指定合适的重复次数以及StateT [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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-13
    • 2017-07-27
    • 1970-01-01
    • 2015-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多