我正在尝试生成可以用表格表示的所有倍数的列表,其中 a、b 和 c 是整数。我尝试了以下,
[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ]
但它只列出了 5 的幂,从不继续 2 或 3。
编辑:抱歉,我似乎没有充分澄清这个问题。我想要的是一个有序的无限列表,虽然我可以对一个有限列表进行排序,但我觉得好像有一个更有效的解决方案。
我正在尝试生成可以用表格表示的所有倍数的列表,其中 a、b 和 c 是整数。我尝试了以下,
[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ]
但它只列出了 5 的幂,从不继续 2 或 3。
编辑:抱歉,我似乎没有充分澄清这个问题。我想要的是一个有序的无限列表,虽然我可以对一个有限列表进行排序,但我觉得好像有一个更有效的解决方案。
只有 5 的幂的原因是 Haskell 试图评估每个可能的 c 为 a = 2^0 和 b = 3^0 并且只有当它完成时它才会为 a = 2^0 和 b = 3^1 . 所以这种方式你只能像这样构造一个有限列表:
[ a * b * c | a <- map (2^) [0..n], b <- map (3^) [0..n], c <- map (5^) [0..n] ]
对于给定的 n。
我的第一个想法是分别从 2、3 和 5 的幂列表开始:
p2 = iterate (2 *) 1
p3 = iterate (3 *) 1
p5 = iterate (5 *) 1
合并两个排序的流也很容易:
fuse [] ys = ys
fuse xs [] = xs
fuse xs@(x : xs') ys@(y : ys')
| x <= y = x : fuse xs' ys
| otherwise = y : fuse xs ys'
但后来我被卡住了,因为fuse p2 (fuse p3 p5)
没有做任何有用的事情。它只产生 2、3 或 5 的倍数,从不混合因子。
我想不出一个纯粹的生成解决方案,所以我以集合累加器的形式添加了一些过滤。该算法(这是非常必要的)是:
{1}
.n
。n
。{2n, 3n, 5n}
到累加器。累加器是一个集合,因为这很容易让我找到并提取最小的元素(我基本上将它用作优先级队列)。它还处理因计算和计算而产生的重复2 * 3
项3 * 2
。
Haskell 实现:
import qualified Data.Set as S
numbers :: [Integer]
numbers = go (S.singleton 1)
where
go acc = case S.deleteFindMin acc of
(n, ns) -> n : go (ns `S.union` S.fromDistinctAscList (map (n *) [2, 3, 5]))
这行得通,但有些事情我不喜欢它:
n : ...
),我们将最多三个新元素添加到累加器 ( ns `S.union` ... [2, 3, 5]
) 中。(“最多三个”,因为其中一些可能是重复的,将被过滤掉。)numbers
携带稳定增长的数据结构;我们从中消耗的元素越多numbers
,累加器就越大。但它只列出了 5 的幂,从不继续 2 或 3。
仅寻址该位。要计算数字2^a*3^0b*5^c
,您尝试生成三元组(a,b,c)
,但在生成表单时遇到了困难(0,0,c)
。这就是为什么你的数字都是形式的2^0*3^0*5^c
,即只有 5 的幂。
如果从配对开始,会更容易。要生成所有对(a,b)
,您可以沿着表格的对角线工作,
a+b = k
对于每个积极的k
. 每个对角线都很容易定义,
diagonal k = [(k-x,x) | x <- [0..k]]
因此,要生成所有对,您只需为 生成所有对角线k<-[1..]
。虽然你想要三倍(a,b,c)
,但它是相似的,只是沿着平面工作,
a+b+c = k
要生成这样的平面,只需沿着它们的对角线工作,
triagonal k = [(k-x,b,c) | x <- [0..k], (b,c) <- diagonal x]
你去吧。现在只需生成所有“三角”以获得所有可能的三元组,
triples = [triagonal k | k <- [0..]]
从您的代码:
[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ]
由于map (5^) [0..]
是一个无限列表,在第一次迭代a
andb
时,它会遍历所述无限列表,它不会停止。这就是为什么它被困在5的幂。
这是除算术之外的解决方案。请注意map (2^) [0..]
,map (3^) [0..]
、 和map (5^) [0..]
都是按升序排序的列表。这意味着通常的合并操作适用:
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys
为方便起见,let xs = map (2^) [0..]; let ys = map (3^) [0..]; let zs = map (5^) [0..]
.
要获得 2 和 3 的倍数,请考虑以下所述数字的组织:
1, 2, 4, 8, 16, ...
3, 6, 12, 24, 48, ...
9, 18, 36, 72, 144, ...
...
由此看来,您可能希望以下工作:
let xys = foldr (merge . flip fmap xs . (*)) [] ys
但这不起作用,因为从上面的组织中,merge
不知道哪一行包含生成的 head 元素,无限地让它不被评估。我们知道上面一行包含所说的 head 元素,所以通过下面的小调整,它终于可以工作了:
let xys = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xs . (*)) [] ys
对 做同样的事情zs
,这是所需的列表:
let xyzs = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xys . (*)) [] zs
完整代码总结:
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys
xyzs = let
xs = map (2^) [0..]
ys = map (3^) [0..]
zs = map (5^) [0..]
xys = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xs . (*)) [] ys
in foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xys . (*)) [] zs
另一种看待它的方式是你想要只能被 2,3 或 5 整除的数字。所以检查从 1 开始的每个数字是否满足这个条件。如果是,它是列表的一部分。
someList = [x| x<- [1..], isIncluded x]
其中 isIncluded 是决定 x 是否满足上述条件的函数。要做到这一点,isIncluded 首先将数字除以 2,直到它不能再除以 2。然后对 3 和 5 的新除数也是如此。最后是 1,然后我们知道这个数字只能被 2 整除,3 或 5,仅此而已。
这可能不是最快的方法,但仍然是最简单的方法。
isIncluded :: Int -> Bool
isIncluded n = if (powRemainder n 2 == 1) then True
else let q = powRemainder n 2
in if (powRemainder q 3 == 1) then True
else let p = powRemainder q 3
in if (powRemainder p 5 == 1) then True else False;
powRemainder 是接受数字和基数并返回不能进一步除以基数的数字的函数。
powRemainder :: Int -> Int -> Int
powRemainder 1 b = 1
powRemainder n b = if (n `mod` b) == 0 then powRemainder (n `div` b) b else n
当我运行take 20 someList
它时,它会返回[1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36]
。
正如其他人已经评论的那样,您的核心不起作用,因为它类似于以下命令式伪代码:
for x in 0..infinity:
for y in 0..infinity:
for z in 0..infinity:
print (2^x * 3^y * 5^x)
最里面for
的循环需要无限的时间来执行,所以其他两个循环永远不会超过它们的第一次迭代。因此,x
和y
都坚持 value 0
。
这是一个典型的吻合问题:如果我们坚持在取下一个(或)z
之前尝试所有的值,我们就会卡在预期输出的一个子集上。我们需要一种更“公平”的方式来选择 的值,以免我们陷入这种方式:这种技术被称为“dovetailing”。y
x
x,y,z
其他人则展示了一些相吻合的技术。在这里,我只会提到这个control-monad-omega
包,它实现了一个易于使用的 dovetailing monad。生成的代码与 OP 中发布的代码非常相似。
import Control.Monad.Omega
powersOf235 :: [Integer]
powersOf235 = runOmega $ do
x <- each [0..]
y <- each [0..]
z <- each [0..]
return $ 2^x * 3^y * 5^z