我一直在解决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
的更好的抽象是什么?
答案 0 :(得分:5)
这不是您问题的直接答案(对不起),但我认为您的代码不正确。 Eq
和Ord
约束让我失望 - 它们不应该是必要的 - 所以我写了几个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
请注意,该实现会导致此函数返回的前缀被反转,但这并不是我们所需要的。
这允许我们编写通用permutations
和combinations
。
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
函数来定义combinations
和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