【问题标题】:Prime Number generator with recursion and list comprehension具有递归和列表理解的素数生成器
【发布时间】:2015-01-27 16:01:59
【问题描述】:

我是 Haskell 编程的新手,无法理解以下列表理解如何扩展。

primes = sieve [2..] 
sieve (p:xs) = p : sieve [x | x <-xs, x `mod` p /= 0]

谁能纠正我sieve 扩展的工作原理:

  • 当我们在sieve 中进行模式匹配时,p 将关联到来自[3..]2xs。
  • 接下来,在列表推导中x&lt;-3,但是在没有短路的情况下,我们如何仅使用3 调用筛子。

我不明白的另一件事是递归在这里是如何工作的。

我认为如果可以一次将上述步骤扩展为前几个数字,例如直到5,那将是很清楚的。

【问题讨论】:

标签: haskell recursion list-comprehension primes sieve


【解决方案1】:

这里是sieve 的操作描述。

计算sieve (x:xs)

  1. 发出前导元素x
  2. 从尾部 xs 开始,让 ys 成为列表 xs,并删除所有 x 的倍数。
  3. 要生成下一个元素,请在步骤 2 中定义的 ys 上递归调用 sieve

以下是前几项的计算方式:

sieve [2..]
  = sieve (2:[3..])              -- x = 2, xs = [3..]
  = 2 : sieve ys
      where ys = [3..] with all of the multiples of 2 removed
               = [3,5,7,9,...]
  = 2 : sieve [3,5,7,9,...]

然后:

sieve [3,5,7,9,...]              -- x = 3, xs = [5,7,9,11,...]
  = 3 : sieve ys
      where ys = [5,7,9,11,13,15,17,...] with all of the multiples of 3 removed
               = [5,7,  11,13,   17,...]
  = 3 : sieve [5,7,11,13,17,...]

然后:

sieve [5,7,11,13,17,...]         -- x = 5, xs = [7,11,13,17..]
  = 5 : sieve ys
      where ys = [7, 11,13, 17,19,...]  with all of the multiples of 5 removed
               = [7, 11,13, 17,19,...]  (the first one will be 25, then 35,...)
  = 5 : sieve [7,11,13,17,19,...]

等等

【讨论】:

    【解决方案2】:

    让我们做一些等式推理。

    primes = sieve [2..]
    sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
    

    [2..][2, 3, 4, 5, ...] 的语法糖,所以

    primes = sieve [2, 3, 4, 5, 6, ...]
    

    内联sieve 一次:

    primes = 2 : sieve [x | x <- [3, 4, 5, 6, 7, ...], x `mod` 2 /= 0]
    

    首先,x 获取通过 mod 2 过滤器的值 3

    primes = 2 : sieve (3 : [x | x <- [4, 5, 6, 7, ...], x `mod` 2 /= 0])
    

    再次内联sieve(我将x重命名为y以防止混淆)

    primes = 2 : 3 : sieve [y | y <- [x | x <- [4, 5, 6, 7, ...], x `mod` 2 /= 0], 
                                y `mod` 3 /= 0]
    

    现在 x = 4 未通过 mod 2 过滤器,但 x = 5 通过它。所以

    primes = 2 : 3 : sieve [y | y <- 5 : [x | x <- [6, 7, 8, ...], x `mod` 2 /= 0], 
                                y `mod` 3 /= 0]
    

    这个y = 5 也通过了mod 3 过滤器,所以现在我们有了

    primes = 2 : 3 : sieve (5 : [y | y <- [x | x <- [6, 7, 8, ...], x `mod` 2 /= 0], 
                                     y `mod` 3 /= 0])
    

    再扩展一次sievez 而不是y)让我们能够

    primes = 2 : 3 : 5 : sieve [z | z <- [y | y <- [x | x <- [6, 7, 8, ...], 
                                                        x `mod` 2 /= 0], 
                                              y `mod` 3 /= 0], 
                                    z `mod` 5 /= 0]
    

    并且扩张继续以同样的方式。

    【讨论】:

      【解决方案3】:

      使用辅助函数

      transform (p:xs) = [x | x <- xs, mod x p /= 0]
                       = filter (\x-> mod x p /= 0) xs  -- remove all multiples of p
                       = xs >>= noMult p                -- feed xs through a tester
      -- where
      noMult p x = [x | rem x p > 0]      -- keep x if not multiple of p
      

      我们可以将sieve函数重写为

             .=================================================+
             |                                                 |
             |  sieve input =                                  |
             |                  .===========================+  |
             |                  |                           |  |
       <~~~~~~~~~~ head input : | sieve (transform input )  |  |
             |                  |                           |  |
             |                  \___________________________|  |
             |                                                 |
             \_________________________________________________|
      

      在命令式伪代码中,我们可以写成

      sieve input =
          while (True) :
              yield (head input)           -- wait until it's yanked, and then
              input := transform input     -- advance and loop
      

      这种重复应用的模式被称为迭代

      iterate f x = loop x
        where
          loop x = x : loop (f x)   -- [x, f x, f (f x), f (f (f x)), ...]
      

      这样

      sieve xs = map head ( iterate transform xs )
      

      自然地,每个步骤中每个转换序列的头元素都是素数,因为我们已经删除了前面步骤中前面素数的所有倍数。

      Haskell 是懒惰的,因此不会在每一步都完全完成转换,远非如此 - 只会根据需要完成尽可能多的工作。这意味着只生成第一个元素,并在被询问时“发出通知”以进一步执行转换:

      <--  2 ---<    [2..]
      <--  3 ---<    [3..] >>= noMult 2 
      <--  5 ---<   ([4..] >>= noMult 2) >>= noMult 3 
      <--  7 ---<  (([6..] >>= noMult 2) >>= noMult 3) >>= noMult 5
      <-- 11 ---< ((([8..] >>= noMult 2) >>= noMult 3) >>= noMult 5) >>= noMult 7
                  ...............
      

      顺便说一下,这应该给我们一个想法:3不需要真的2测试; 4..8 不需要 真的3 测试,更不用说 57; 9..24 不应该真的5 测试;等等。我们想要的是以下内容:

      <-- 2
      <-- 3         --<  2(4),    [3..]
      <-- 5,7       --<  3(9),    [4..]  >>= noMult 2 
      <-- 11,...,23 --<  5(25),  ([9..]  >>= noMult 2) >>= noMult 3 
      <-- 29,...,47 --<  7(49), (([25..] >>= noMult 2) >>= noMult 3) >>= noMult 5
                         ......................
      

      即我们希望创建每个&gt;&gt;= noMult p 过滤器postponed,直到在输入中达到p*p

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-11-06
        • 2016-08-05
        相关资源
        最近更新 更多