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

问题描述 投票:1回答:2

所以我花了大约一个小时左右尝试编写一个有效的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
2个回答
2
投票

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

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

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

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

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
投票

所以我稍微修改了@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+)]
© www.soinside.com 2019 - 2024. All rights reserved.