你的尝试
你写道:
print(minimum([x | x <- [1..], y <- [1..10], rem x y == 0]))
由于以下几个原因,这将不起作用:
- 它会将
x 添加到列表理解中的列表中,因为[1..10] 中有any 元素y 将x 分开。此外,对于任何这样的y,我们将添加一次x。所以给定x 是6,它会加三四次,因为6 可以被1、2、3 和6 整除;
- 结果将是一个无限列表,因此无法计算该列表的最小值。这是因为 Haskell 不知道列表已经排序,因此最小值只是第一个元素。您可以使用
head 获取第一个元素;
-
minimum 只会产生一个元素,但你想要前 10 个。
蛮力方法
首先我们可以使用列表推导来生成所有这些数字(对于任意列表as 和bs):
divide_all as bs = [a | a <- as, all ((0 ==) . mod a) bs]
所以这里的列表推导遍历as 并将每个元素分配给a。接下来我们有一个过滤器all ((0 ==) . mod a) bs,它是all (\b -> mod a b == 0) bs 的紧凑形式。因此,它检查bs、mod a b == 0 中的所有成员b 是否(所以a 可以被b 整除)。如果满足过滤器,那么我们将a(在列表理解的头部)添加到结果中。请注意,此类列表是延迟构建的,因此 as 具有无限数量的元素这一事实不是问题。
现在我们可以使用take :: Int -> [a] -> [a] 获取这些数字中的前 10 个,从而打印这些数字:
mapM_ print (take 10 $ divide_all [1..] [1..10])
哪个打印:
Prelude> mapM_ print (take 10 $ divide_all [1..] [1..10])
2520
5040
7560
10080
12600
15120
17640
20160
22680
25200
最小公倍数
上述方法效率不高:对于a的每个元素,我们需要检查它是否可以与b的每个元素整除。我的机器用了 2.16 秒来计算这个列表的第 1000 个元素,并用了 10.21 秒找到第 5000 个元素。
我们可以加快最后一个,通过计算b中所有元素的最小公倍数(lcm),并检查一个数是否可以被lcm整除:
divide_all as bs = [a | a <- as, mod a lcmb == 0] -- optimized version
where lcmb = foldr1 lcm bs
所以现在我们只需要执行一项检查。现在计算第 1000 个元素需要 0.95 秒,计算第 5000 个元素需要 4.54 秒。
万一as = [1..]
如果已知as 是[1..],我们可以显着提升这段代码,因为我们知道a 的元素都是lcmb 的倍数。所以我们可以去掉as参数,然后使用:
divide_all bs = [lcmb*a | a <- [1..]] -- optimized version
where lcmb = foldr1 lcm bs
现在计算第 1000 个元素需要 0.01 秒,计算第 5000 个元素需要 0.03 秒。但当然,这仅在假设条件下才有效。