如何泛化实现生成指定长度的递增整数列表函数?
待泛化的示例
以下函数生成长度为2的列表[a,b],元素取自1到top的整数,且满足a < b。
ascendingPairs top = [ [x,y] | x <- [1..top], y <- [x+1..top] ]
运行示例:
> mapM_ (putStrLn . show) $ ascendingPairs 4 [1,2] [1,3] [1,4] [2,3] [2,4] [3,4]
期望的泛化需求
ascendingPairs仅针对生成长度为2的列表的场景定义。若输出列表的长度未知,需将长度作为参数传入,该如何实现?
几种低效方案
一种笨拙的方式是为每个长度编写分支:
partial :: Int -> Int -> [[Int]] partial 1 top = [ [x] | x <- [1..top] ] partial 2 top = [ [x,y] | x <- [1..top], y <- [x+1 .. top] ] partial 3 top = [ [x,y,z] | x <- [1..top], y <- [x+1 .. top], z <- [y+1 .. top] ] partial 4 top = ... partial 5 top = ... ...
这种方式需要编写无穷多的分支,显然不现实。
另一种覆盖所有场景的方式(需用到list monad):
slow :: Int -> Int -> [[Int]] slow top size = filter monotonicAscending $ mapM (\x -> [1..top]) [1..size] monotonicAscending :: [Int] -> Bool monotonicAscending (a:b:rest) = a < b && monotonicAscending (b:rest) monotonicAscending _ = True
slow节省了人力,但会消耗大量机器时间,因为它会生成大量不符合条件的列表,随后被filter monotonicAscending过滤。计算length $ slow 41 7至少需要数分钟,甚至数小时(已中断)。相比之下,反而更倾向于partial方案。
一种疑似冗余的可行方案
最终找到一种实现方式,但感觉过于繁琐:
monoAscending :: Int -> Int -> [[Int]] monoAscending top size = map reverse $ incrementNTimes top (size-1) $ [ [a] | a <- [1..top] ] incrementNTimes :: Int -> Int -> [[Int]] -> [[Int]] incrementNTimes top 0 lists = lists incrementNTimes top n lists = let x :: [[Int]] x = concatMap (increments top) lists in incrementNTimes top (n-1) x -- | All the ways of prepending a bigger element to the input list. -- This assumes the input list is in descending order. increments :: Int -> [Int] -> [[Int]] increments top (a:as) = [ b:a:as | b <- [a+1 .. top]]
该实现可正常运行:
> mapM_ (putStrLn . show) $ monoAscending 4 3 [1,2,3] [1,2,4] [1,3,4] [2,3,4]
但是否存在更优的实现方式?
内容的提问来源于stack exchange,提问作者Jeffrey Benjamin Brown
相关产品推荐
相关产品推荐

