【问题标题】:How do you find the list of all numbers that are multiples of only powers of 2, 3, and 5? [duplicate]您如何找到仅是 2、3 和 5 的幂的倍数的所有数字的列表? [复制]
【发布时间】:2019-04-29 19:12:18
【问题描述】:

我正在尝试生成可以用 形式表示的所有倍数的列表,其中 a、b 和 c 是整数。我尝试了以下,

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

但它只列出了 5 的幂,从不继续 2 或 3。

编辑:抱歉,我似乎没有充分澄清这个问题。我想要的是一个有序的无限列表,虽然我可以对一个有限列表进行排序,但我觉得好像有一个更有效的解决方案。

【问题讨论】:

  • 您的解决方案满足您提出的要求。也许您可以更仔细地说明问题?例如听起来您希望列表按特定顺序排列
  • 有没有比my solution更好的非笨方法?
  • @melpomene 是的,有。您的解决方案会产生过多的序列,总是在其中添加三个倍数,同时选择一个。您可以有条件地只添加最小的一个,实际上将三个反向指针保留到正在生成的有序序列中。著名的规范代码可以在维基百科上的标签(我添加的)RosettaCode page"Haskell Features" page 上找到。
  • @melpomene /contd./ 然后有一个new, twice faster 代码,每个数字只产生一次。 (也出现在上面链接的 RosettaCode 和 WP 页面上)。
  • @WillNess 好的,如果您认为它值得,我会保留它。

标签: haskell hamming-numbers smooth-numbers


【解决方案1】:

只有 5 的幂的原因是 Haskell 试图评估所有可能的 c 为 a = 2^0 和 b = 3^0 并且只有当它完成时它才适用于 a = 2^0 和 b = 3^1。 所以这样你只能构造一个像这样的有限列表:
[ a * b * c | a &lt;- map (2^) [0..n], b &lt;- map (3^) [0..n], c &lt;- map (5^) [0..n] ]
对于给定的 n。

【讨论】:

  • 我很抱歉,我似乎没有足够澄清这个问题。我想要的是一个有序的无限列表,虽然我可以对一个有限列表进行排序,但我觉得好像有一个更有效的解决方案。
  • @robbie0630 数学家对此的解决方案是:为增加的n 制作一系列这些有限列表(通过加倍或重复平方或其他方式);同时跳过之前阶段已经找到的部分;您的无限序列也具有可容忍的理论复杂性。 :) 当然,在实践中它会很快卡住。而且,每个由此产生的有限序列仅在某个点上是正确的,之后在其中包含孔,因此成对比较它们也将有所帮助。再次,一个理论(非)解决方案。 :)
【解决方案2】:

我的第一个想法是分别从 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 消耗的元素越多,累加器就越大。
  • 从这个意义上说,它不是一个纯粹的“流”算法。即使我们忽略稳步增长的数字本身,我们也需要更多的内存并执行更多的计算,因为我们对序列的了解越深。

【讨论】:

  • 只是一个旁注:该 Set 的大小是 ~ n ^ (2/3),对于要生成的序列中的第 n 个数字。至少我是这么认为的。 (我想我曾经为自己证明过一次......)
【解决方案3】:

来自您的代码:

[ 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

【讨论】:

  • 抱歉混淆了;不知道我以前怎么会错过这些定义。应该仔细阅读答案...
  • 感谢您的回答;我不认为我以前见过这种方式。有趣的。 :) 我遵循了你的想法,最终得到了foldr merge' [] . iterate (map (5*)) . foldr merge' [] . iterate (map (3*)) . iterate (*2) $ 1merge' (m:ms) = (m :) . merge ms
  • 所有 2,3 & 5 的倍数使用递归加法而不合并或排序scanl (\b a -&gt; a+b) 2 $ cycle [1,1,1,1,2,1,1,2,2,1,1,2,2,1,1,2,1,1,1,1,2,2]
【解决方案4】:

但它只列出了 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&lt;-[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*log 2 + b*log 3 = v, :) 形式的对角线工作,以逐渐增加v,以便按顺序生成数字。对于飞机,a*log 2 + b*log 3 + c*log5 = v。 (说起来容易做起来难)。你能想出一个(简单的)方法来做到这一点吗? (因为我做不到)
  • 现在会很有趣。需要考虑一下。我会
  • 我什至对保证“足够好”的本地化订单的解决方案感兴趣。理论上我们可以例如根据通过diagonal k 获得的最大数字是5^k,小于通过diagonal (k+3) 获得的最小数字2^(3+k) 对流进行排序。虽然很糟糕......
【解决方案5】:

查看它的另一种方式是您想要只能被 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]

【讨论】:

  • 不幸的是,在产生序列中的前 n 个数字时,这是 n 的指数。 O(expt(n^(1/3))),to be exact.
  • @WillNess - 它可能没有经过时间优化,但它会按顺序返回小 n 的列表。在问题中提到的初始解决方案中,即使对于小的 n,它也会在无限时间内进行,并且当有限制时,返回值不是按顺序排列的。
  • 是的,没错。它正确的。 :)
【解决方案6】:

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

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 问题:如果我们坚持在获取下一个 y(或 x)之前尝试 z 的所有值,我们就会卡在预期输出的一个子集上。我们需要一种更“公平”的方式来选择x,y,z 的值,这样我们就不会陷入这种困境:这种技术被称为“dovetailing”。

其他人已经展示了一些相吻合的技巧。在这里,我只会提到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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-24
    • 2018-08-25
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 2021-11-19
    相关资源
    最近更新 更多