【问题标题】:How can I make this prime check function more succinct in Haskell?如何在 Haskell 中使这个素数检查功能更简洁?
【发布时间】:2018-03-04 17:52:57
【问题描述】:

所以我花了最后一个小时左右尝试编写一个有效的 haskell 函数来检查一个数字是否是素数。我想出了这个我很满意的算法:

prime :: Int -> Bool
prime x
    | x == 1 = False
    | x == 2 = True
    | x == 3 = True
    | x `mod` 2 == 0 = False
    | x `mod` 3 == 0 = False
    | otherwise = all (\y -> x `mod` y /= 0) $ dividends x
    where
        dividends z =
            takeWhile ((<=) . floor . sqrt . fromIntegral $ z)
                $ concatMap (\x -> [x-1, x+1]) [6 * x | x <- [1..]]

我还上传了一个笔记本,它检查算法的运行时间并将其与筛法进行比较,以防有人感兴趣:https://anaconda.org/freemo/primes/notebook

我的问题是,作为 haskell 的新手,我怎样才能使这个算法更加符合规范。我感觉可以消除我使用的匿名函数,并且可能还有其他方法可以使其更简洁,而不会对运行时间产生负面影响。我怎样才能用更惯用的 haskell 方式写这个?

【问题讨论】:

    标签: haskell math primes


    【解决方案1】:

    我认为这里最不习惯的事情是平等比较。在 Haskell 中,我们一般更喜欢模式匹配,可以直接应用于前三种情况:

    prime 1 = False
    prime 2 = True
    prime 3 = True
    prime x
        | ...
    

    (在这个例子中,区别在于外观,但模式匹配通常使代码更安全、更简洁,有时也显着提高性能。)

    接下来,您将同时使用列表解析和concatMap。这两种构造在很大程度上做同样的事情,并且等同于一元绑定。但是 a) 在一个地方只使用这些语法中的 一种 通常是有益的,b) monad 实际上比必要的更强大: applicative 也可以,在这里可以写得很好。

    import Control.Applicative
    ...
           (6*) <$> [1..] <**> [pred, succ]
    

    然后,我觉得这个((&lt;=) . floor . sqrt . fromIntegral $ z) 很尴尬。更自然的是翻转形式:

        takeWhile (>= floor (sqrt $ fromIntegral z)) ...
    

    ...但一般来说,最好完全避免平方根:

        takeWhile ((>=z) . (^2)) ...
    

    原因是,平方根非常昂贵并且浮点数不准确,因此通常最好将等式的另一边平方。不幸的是,这实际上在这里并不可靠,因为平方可能导致Int 溢出,而Integer 会使性能可能更差 比float-sqrt,因为它是为每个元素完成的列表而不是一次。所以,平方根在这里实际上是明智的。

    dividends 函数实际上是多余的,因为您只使用x 作为参数调用它。 (可能有人认为,仍然给它一个 name 是明智的,但无论如何你不需要给它一个 argument。)

    all 的谓词是我考虑无意义的东西。

    最终版本将是:

    prime :: Int -> Bool
    prime 1 = False
    prime 2 = True
    prime 3 = True
    prime x
        | x `mod` 2 == 0 = False
        | x `mod` 3 == 0 = False
        | otherwise = all ((/= 0) . (x`mod`))
                     . takeWhile (>= floor (sqrt $ fromIntegral x))
                     $ (6*) <$> [1..]
                            <**> [pred, succ]
    

    【讨论】:

    • 这感觉是正确的答案。有些想法对我来说是新的,所以我需要用它来确认。因此,除非我发现问题或出现更好的答案,否则我可能很快就会接受这个答案。
    • so Applicatives、 和 对我来说都很奇怪,我不明白它们是什么。如果我了解应用程序,那是否也涵盖其他两件事?
    • @JeffreyPhillipsFreeman: &lt;$&gt; 是列表中的 fmap :: (a -&gt; b) -&gt; f a -&gt; f b 中缀 map (f=[])。所以(6*) &lt;$&gt; [1..] = [6,12,18,…]&lt;**&gt;(&lt;*&gt;) :: f (a -&gt; b) -&gt; f a -&gt; f b 的翻转版本——在这里,将函数列表应用于操作数列表。此答案使用&lt;**&gt; 而不是(更常见的)&lt;*&gt;,因为它们给出不同的顺序:[pred,succ] &lt;*&gt; [10,20,30] == [9,19,29,11,21,31](全部为preds 然后全部为succs)而[10,20,30] &lt;**&gt; [pred,succ] == [9,11,19,21,29,31]predsucc每个)。只是… &gt;&gt;= \x -&gt; [pred x, succ x] 的较短版本。
    【解决方案2】:

    所以我稍微修改了@leftaroundabout 的答案并使用它。只是把它放在这里以防有人最终使用它。

    import Control.Applicative
    
    prime'' :: Int -> Bool
    prime'' 1 = False
    prime'' 2 = True
    prime'' 3 = True
    prime'' x
        | x `mod` 2 == 0 = False
        | x `mod` 3 == 0 = False
        | otherwise = all
                         -- check if x is not divisibile by the divisors
                         ((/=  0) . (x `mod`))
    
                         -- only take divisors less than or equal to sqrt(x)
                         . takeWhile (<=(floor . sqrt . fromIntegral $ x))
    
                         -- generate divisors as an infinite list 6n +/- 1
                         $ [6,12..] <**> [(-1+), (1+)]
    

    【讨论】:

      猜你喜欢
      • 2013-08-21
      • 2011-06-21
      • 1970-01-01
      • 2020-11-12
      • 2022-01-28
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多