如何在Haskell中使这个主要检查功能更简洁?

时间:2018-03-04 17:52:58

标签: haskell math primes

所以我花了大约一个小时左右尝试编写一个有效的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方式写这个呢?

2 个答案:

答案 0 :(得分:2)

我认为最重要的是平等比较。在Haskell中,我们通常更喜欢模式匹配,您可以直接应用于前三种情况:

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

(这个例子中的不同之处在于,但通常模式匹配会使代码更安全,更简洁,有时也会显着提高性能。)

接下来,您正在使用列表推导和concatMap。这两种结构在很大程度上都是相同的,并且相当于monadic绑定。但是 a)在一个地方只使用一个这些语法通常是有益的, b) monad实际上强于必要: applicative 也可以,并且可以在这里写得很好。

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

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

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

...但总的来说,更好的是完全避免平方根:

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

原因是,平方根非常昂贵并且具有浮点不准确性,因此通常更好地对方程的另一侧进行平方。不幸的是,这实际上在这里不可靠,因为平方可能会导致Int溢出,而Integer会使性能可能更糟而不是使用float-sqrt,因为它已完成对于列表中的每个元素而不是仅仅一次。所以,平方根在这里实际上是明智的。

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

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]

答案 1 :(得分:1)

所以我稍微修改了@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+)]