您如何找到所有数字的列表,这些数字是2、3和5的幂的倍数?

时间:2018-11-27 23:42:05

标签: haskell hamming-numbers smooth-numbers

我正在尝试生成一个可以用2^a*3^b*5^c形式表示的所有倍数的列表,其中a,b和c是整数。我尝试了以下方法,

[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ] 

但是它仅列出5的幂,而从不继续2或3。

编辑:抱歉,我没有足够清楚地阐明这个问题。我想要的是一个有序的无限列表,尽管我可以对一个有限列表进行排序,但我觉得好像有一种更有效的解决方案。

6 个答案:

答案 0 :(得分:4)

之所以只有5的幂,是因为Haskell尝试评估a = 2 ^ 0和b = 3 ^ 0的每个可能的c,并且只有在完成时才对a = 2 ^ 0和b = 3 ^ 1。 因此,您只能以这种方式构造一个有限列表:
     document.addEventListener('DOMContentLoaded', function() { var elems = document.querySelectorAll('.carousel'); var instances = M.Carousel.init(elems, options); }); // Or with jQuery $(document).ready(function(){ $('.carousel').carousel(); });
给定n。

答案 1 :(得分:3)

我的第一个想法是分别从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. 将累加器初始化为{1}
  2. 从累加器中查找并删除最小的元素;称为n
  3. 发出n
  4. {2n, 3n, 5n}添加到累加器。
  5. 如果需要更多元素,请转到#2。

累加器是一个集合,因为它很容易让我找到并提取最小的元素(基本上,我将其用作优先级队列)。它还处理因同时计算2 * 33 * 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中消耗的元素越多,累加器就越大。
  • 从这个意义上讲,它不是纯粹的“流”算法。即使我们自己忽略稳定增长的数字,我们也需要更多的内存,并且需要更深入的研究才能执行更多的计算。

答案 2 :(得分:2)

  

但是它仅列出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..]]

答案 3 :(得分:2)

根据您的代码:

[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ] 

由于map (5^) [0..]是一个无限列表,因此在ab的第一次迭代中,它将遍历该无限列表,该列表不会停止。这就是为什么它停留在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

答案 4 :(得分:1)

另一种查看方式是您希望数字只能被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]

答案 5 :(得分:1)

正如其他人已经评论过的那样,您的核心无法工作,因为它类似于以下命令式伪代码:

for x in 0..infinity:
   for y in 0..infinity:
      for z in 0..infinity:
         print (2^x * 3^y * 5^x)

最里面的for花费无限的时间执行,因此其他两个循环将永远不会超过它们的第一次迭代。因此,xy都停留在值0上。

这是一个经典的dovetailing问题:如果我们坚持在尝试下一个z(或y)之前尝试使用x的所有值,则会陷入困境预期输出的子集。我们需要一种更“公平”的方法来选择x,y,z的值,以免陷入这种困境:这种技术被称为“燕尾加工”。

其他人显示了一些燕尾榫技术。在这里,我仅提及control-monad-omega软件包,该软件包实现了易于使用的燕尾式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