三位数的所有组合是什么?让我们手动写出一些。
000, 001, 002 ... 009, 010, 011 ... 099, 100, 101 ... 998, 999
我们最终只是进行了简单的“计数”!我们枚举了0到999之间的所有数字。对于任意数量位数的数字,这个方法很容易地推广开来:上限为
10^n
(不包括),其中
n
是位数。
数字是有意设计成这样的。如果存在三个数字的组合不是有效数字,或者存在低于1000的数字无法通过组合三个数字来表示,那将会非常奇怪!
这启示我一个简单的方案,只涉及算术,不需要对Haskell有深入的理解:
- 生成0到
10^n
之间的数字列表
- 将每个数字转换为数字列表
步骤2是有趣的部分。要提取三位数字的(十进制)数字,请
按照以下方式操作:
- 将数与100取商和余数。商是该数的第一位数字。
- 用步骤1的余数对10取商和余数。商是第二个数字。
- 步骤2的余数就是第三个数字。这与对1取商相同。
对于一个n位数,我们需要取商n次,从10^(n-1)
开始,以1
结束。每次,我们使用上一步的余数作为下一步的输入。这表明将数字转换为数字列表的函数应该实现为一个fold:我们将余数通过操作线程,并在进行的过程中构建一个列表。(如果您不是在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中,生成列表、转换列表,然后将其折叠回去的模式是一种惯用的做法。它甚至有自己高大上的数学名字,称为。
GHC也知道这种模式,可以将其编译成紧凑的循环,优化掉您正在使用的列表的存在。
让我们来测试一下!
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
。
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的版本,请参见我的其他答案。
n-1
种选择的组合,生成n
种选择的组合。您需要输出一个列表,而不是一个固定长度的元组。 - chireplicateM n [1..100]
可以完成任务。不过,要意识到为什么它能够实现并不是一个简单的练习,所以最好在更熟悉Haskell后再进行尝试。 - chisequence $ replicate 3 [0..9]
将生成一个列表的笛卡尔积,而不是元组,但很容易推广。 - karakfa000
,001
,002
...009
,010
,011
...999
...我是否遗漏了什么,或者这不是数学概念中所称的计数? - Mark Seemann