Haskell 如何找到所有仅为2、3和5的幂的倍数的数字的列表?
我试图生成一个所有倍数的列表,可以用形式表示,其中a、b和c是整数。我试了以下方法Haskell 如何找到所有仅为2、3和5的幂的倍数的数字的列表?,haskell,hamming-numbers,smooth-numbers,Haskell,Hamming Numbers,Smooth Numbers,我试图生成一个所有倍数的列表,可以用形式表示,其中a、b和c是整数。我试了以下方法 [ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ] [a*b*c | a之所以只有5的幂,是因为Haskell试图对a=2^0和b=3^0的每一个可能的c进行求值,并且只有当求值完成时,才会求出a=2^0和b=3^1。 因此,这样您只能像这样构造一个有限列表: [a*b*c|a 但它只列出
[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ]
[a*b*c | a之所以只有5的幂,是因为Haskell试图对a=2^0和b=3^0的每一个可能的c进行求值,并且只有当求值完成时,才会求出a=2^0和b=3^1。
因此,这样您只能像这样构造一个有限列表:
[a*b*c|a
但它只列出了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]]
要生成这样的平面,只需沿其对角线工作
triagonal k = [(k-x,b,c) | x <- [0..k], (b,c) <- diagonal x]
triagnalk=[(k-x,b,c)| x另一种看待它的方式是你想要那些只能被2,3或5整除的数字。所以检查从1开始的每个数字是否满足这个条件。如果是,它就是列表的一部分
someList = [x| x<- [1..], isIncluded x]
PowRequires是一个函数,它接受数字和基数,并返回不能进一步除以基数的数字
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]
我的第一个想法是分别从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'
这是可行的,但我不喜欢它的某些方面:
- 对于我们发出的每个元素(
n:…
),我们将最多三个新元素添加到累加器(ns'S.union`.[2,3,5]
)(“最多三个”,因为其中一些元素可能是重复的,将被过滤掉。)
- 这意味着
numbers
承载着一个稳定增长的数据结构;我们从numbers
中消耗的元素越多,累加器的增长就越大
- 从这个意义上说,它不是一个纯粹的“流”算法。即使我们忽略了稳步增长的数字本身,我们需要更多的内存,并在序列中执行更多的计算
从您的代码:
[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ]
为了方便起见,让xs=map(2^[0..];让ys=map(3^[0..];让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
merge[]ys=ys
合并xs[]=xs
合并(x:xs)(y:ys)=如果x m:合并ms ns)。翻转fmap xs。(*)[]ys
在foldr((\(m:ms)ns->m:merge ms ns)中,翻转fmap xys(*)[]zs
正如其他人所评论的,您的核心无法工作,因为它类似于以下命令伪代码:
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
都被固定在值0
上
这是一个典型的问题:如果我们坚持在下一个y
(或x
)之前尝试z
的所有值,我们就会在预期输出的子集上陷入困境。我们需要一种更“公平”的方法来选择x,y,z
的值,这样我们就不会陷入困境:这种技术被称为“吻合”
其他人已经展示了一些燕尾技术。在这里,我只提到这个包,它实现了一个易于使用的燕尾单子。生成的代码与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
import Control.Monad.Omega
powersOf235::[整数]
powerso235=runOmega$do
x您的解决方案满足您所述的要求。也许您可以更仔细地说明问题?例如,听起来您希望列表按某种特定顺序排列。是否有一种非愚蠢的方式比它更好?@melpomene是的,有。您的解决方案在选择时会过度生成序列,总是在序列中添加三倍一个。您可以有条件地只添加最小的一个,实际上是在正在生成的有序序列中保留三个反向指针。著名的规范代码可以在标记(我添加的)和Wikipedia上找到。@melpomene/contd./然后有一个代码,每个数字只生成一次。(也出现在上面链接的RosettaCode和WP页面上)好的,如果你认为这是值得的,我就不提了。很抱歉,我似乎没有充分澄清这个问题。我想要的是一个有序的无限列表,虽然我可以对有限列表进行排序,但我觉得可能有一个更有效的解。@robbie0630一个数学学家的解决方案是:把这些f递增的n
(通过加倍、重复平方或其他方式)的初始列表;跳过之前阶段中已经找到的部分;还有你的无限序列,理论上也相当复杂。:)当然,在实践中,它会很快被卡住。但是,这样产生的每个有限序列在某个点上都是正确的,之后会有洞,所以将它们成对比较也会有帮助。同样,一个理论(非)解决方案。:)只是一个旁注:集合的大小i
let xys = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xs . (*)) [] ys
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
for x in 0..infinity:
for y in 0..infinity:
for z in 0..infinity:
print (2^x * 3^y * 5^x)
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