我正在尝试生成n个数字的所有可能组合。例如,如果n = 3,我想要以下组合:
(0,0,0), (0,0,1), (0,0,2)... (0,0,9), (0,1,0)... (9,9,9).
这个post描述了如何在n = 3时执行此操作:
[(a,b,c) | m <- [0..9], a <- [0..m], b <- [0..m], c <- [0..m] ]
或者为了避免重复(即同一n-uple的多个副本):
let l = 9; in [(a,b,c) | m <- [0..3*l],
a <- [0..l], b <- [0..l], c <- [0..l],
a + b + c == m ]
然而,对于n > 3
,遵循相同的模式会非常愚蠢。说我想找到所有组合:(a, b, c, d, e, f, g, h, i, j)
等等。
有人能指出我在正确的方向吗?理想情况下,我宁愿不使用内置功能,因为我正在尝试学习Haskell,我宁愿花时间去理解一些代码而不仅仅是使用其他人编写的包。不需要元组,列表也可以。
答案 0 :(得分:5)
三位数的所有组合是什么?让我们手动写几个。
000, 001, 002 ... 009, 010, 011 ... 099, 100, 101 ... 998, 999
我们最终只是计算!我们列举了0到999之间的所有数字。对于任意数字的数字,这直截了当地说:上限是10^n
(不包括),其中n
是数字。
数字是故意这样设计的。如果有三个数字的可能组合不是有效数字,或者如果有一个三位数的数字不能通过组合三个数字来表达,那将是非常奇怪的!
这对我来说是一个简单的计划,它只涉及算术,并不需要深入了解Haskell *:
10^n
第2步是有趣的部分。要提取三位数字的数字(在基数10中),you do this:
对于 n -digit数字,我们采用商n
次,从10^(n-1)
开始到1
结束。每次,我们使用最后一步的余数作为下一步的输入。这表明我们将数字转换为数字列表的功能应该作为一个折叠实现:我们将通过操作线程化其余部分并构建一个列表。 (如果你不在10号基地,我会留给你弄清楚这个算法是如何变化的!)
现在让我们实现这个想法。我们想要计算给定数字的指定位数,必要时的零填充。 digits
的类型应该是什么?
digits :: Int -> Int -> [Int]
嗯,它接受一些数字和一个整数,并产生一个表示输入整数数字的整数列表。该列表将包含一位数整数,每个整数将是输入数字的一位数。
digits numberOfDigits theNumber = reverse $ fst $ foldr step ([], theNumber) powersOfTen
where step exponent (digits, remainder) =
let (digit, newRemainder) = remainder `divMod` exponent
in (digit : digits, newRemainder)
powersOfTen = [10^n | n <- [0..(numberOfDigits-1)]]
对我而言,这个代码看起来与我想要执行的算术的英文描述非常相似。我们通过从0向上取幂来生成十次幂表。然后我们将那张桌子折叠起来;在每一步,我们将商放在数字列表上,并将余数发送到下一步。我们最后必须reverse
输出列表,因为它是从右到左的方式构建的。
顺便说一句,生成列表,转换它,然后将其折叠起来的模式是Haskell中惯用的事情。它甚至得到了自己的高f&#39;数学名称, hylomorphism 。 GHC knows about this pattern too并且可以将其编译成紧密的循环,优化您正在使用的列表的存在。
让我们测试吧!
ghci> digits 3 123
[1, 2, 3]
ghci> digits 5 10101
[1, 0, 1, 0, 1]
ghci> digits 6 99
[0, 0, 0, 0, 9, 9]
它就像一个魅力! (好吧,如果numberOfDigits
对于theNumber
来说太小,那就行为不端了,但从不介意这一点。)现在我们只需要生成一个使用digits
的数字的计数列表。 / p>
combinationsOfDigits :: Int -> [[Int]]
combinationsOfDigits numberOfDigits = map (digits numberOfDigits) [0..(10^numberOfDigits)-1]
......我们已经完成了!
ghci> combinationsOfDigits 2
[[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],[0,9],[1,0],[1,1] ... [9,7],[9,8],[9,9]]
*对于 需要深入了解Haskell的版本,请参阅my other answer。
答案 1 :(得分:5)
我的other answer给出了一个算术算法来枚举所有数字组合。这是一个替代解决方案,它通过推广你的例子而产生。它也适用于非数字,因为它只使用列表的结构。
首先,让我们提醒自己如何使用列表理解来实现三位数组合。
threeDigitCombinations = [[x, y, z] | x <- [0..9], y <- [0..9], z <- [0..9]]
这里发生了什么?列表推导对应于嵌套循环。 z
从0到9计数,然后y
上升到1,z
从0再次开始计数。 x
是最慢的。如您所知,当您需要不同数量的数字时,列表推导的形状会发生变化(尽管是统一的)。我们将利用这种一致性。
twoDigitCombinations = [[x, y] | x <- [0..9], y <- [0..9]]
我们想要抽象列表推导中的变量数量(等效地,循环的嵌套)。让我们开始玩吧。首先,我要将这些列表推导重写为等效的 monad comprehensions 。
threeDigitCombinations = do
x <- [0..9]
y <- [0..9]
z <- [0..9]
return [x, y, z]
twoDigitCombinations = do
x <- [0..9]
y <- [0..9]
return [x, y]
有趣。看起来threeDigitCombinations
与twoDigitCombinations
大致相同的monadic动作,但有一个额外的声明。再次重写......
zeroDigitCombinations = [[]] -- equivalently, `return []`
oneDigitCombinations = do
z <- [0..9]
empty <- zeroDigitCombinations
return (z : empty)
twoDigitCombinations = do
y <- [0..9]
z <- oneDigitCombinations
return (y : z)
threeDigitCombinations = do
x <- [0..9]
yz <- twoDigitCombinations
return (x : yz)
现在应该清楚我们需要参数化了什么:
combinationsOfDigits 0 = return []
combinationsOfDigits n = do
x <- [0..9]
xs <- combinationsOfDigits (n - 1)
return (x : xs)
ghci> combinationsOfDigits' 2
[[0,0],[0,1],[0,2],[0,3],[0,4],[0,5],[0,6],[0,7],[0,8],[0,9],[1,0],[1,1] ... [9,8],[9,9]]
它有效,但我们还没有完成。我想告诉你,这是一个更一般的monadic模式的实例。首先,我要更改combinationsOfDigits
的实现,以便将常量列表折叠起来。
combinationsOfDigits n = foldUpList $ replicate n [0..9]
where foldUpList [] = return []
foldUpList (xs : xss) = do
x <- xs
ys <- foldUpList xss
return (x : ys)
查看foldUpList :: [[a]] -> [[a]]
的定义,我们可以看到它实际上并不需要使用列表本身:它只使用列表的monad -y部分。它可以适用于任何monad,事实上确实如此!它位于标准库中,名为sequence :: Monad m => [m a] -> m [a]
。如果您对此感到困惑,请将m
替换为[]
,您应该会看到这些类型的含义相同。
combinationsOfDigits n = sequence $ replicate n [0..9]
最后,注意到sequence . replicate n
是replicateM
的定义,我们将其归结为一个非常敏捷的单行。
combinationsOfDigits n = replicateM n [0..9]
总结一下,replicateM n
给出了输入列表的 n - 组合。这适用于任何列表,而不仅仅是数字列表。实际上,它适用于任何monad - 尽管&#34;组合&#34;当你的monad代表选择时,解释才有意义。
这段代码非常简洁!因此,我认为它的工作方式并不完全明显,这与我在其他答案中向您展示的算术版本不同。列表monad一直是我发现不太直观的monad之一,至少当你使用高阶monad组合而不是do
- 符号时。
另一方面,它比数字运算版本运行得快得多。在使用-O2
编译的我的(高规格)MacBook Pro上,此版本计算的5位数组合比压缩数字的版本快4倍。 (如果有人能解释这个我听的原因!)
答案 2 :(得分:1)
combos 1 list = map (\x -> [x]) list
combos n list = foldl (++) [] $ map (\x -> map (\y -> x:y) nxt) list
where nxt = combos (n-1) list
在你的情况下
combos 3 [0..9]