【问题标题】:Project Euler 3 - Haskell欧拉计划 3 - Haskell
【发布时间】:2014-10-09 12:31:13
【问题描述】:

我正在解决 Haskell 中的 Project Euler 问题。我已经为下面的问题 3 找到了一个解决方案,我已经在小数上对其进行了测试,并且它可以工作,但是由于通过首先推导所有素数的蛮力实现,它对于较大的数字呈指数级增长。

-- Project Euler 3

module Main 
    where

import System.IO
import Data.List

main = do
    hSetBuffering stdin LineBuffering
    putStrLn "This program returns the prime factors of a given integer"
    putStrLn "Please enter a number"
    nums <- getPrimes
    putStrLn "The prime factors are: "
    print (sort nums)


getPrimes = do
    userNum <- getLine
    let n = read userNum :: Int
    let xs = [2..n]
    return $ getFactors n (primeGen xs)

--primeGen :: (Integral a) => [a] -> [a]
primeGen [] = []
primeGen (x:xs) = 
    if x >= 2
    then x:primeGen (filter (\n->n`mod` x/=0) xs)
    else 1:[2]

--getFactors
getFactors :: (Integral a) => a -> [a] -> [a]
getFactors n xs = [ x | x <- xs, n `mod` x == 0]

我查看了here 的解决方案,可以看到factor 中的第一个守卫如何优化它。我不明白的是:

primes = 2 : filter ((==1) . length . primeFactors) [3,5..]

特别是filter的第一个参数。

((==1) . length . primeFactors)

由于 primeFactors 本身就是一个函数,我不明白它在这种情况下是如何使用的。有人可以解释一下这里发生了什么吗?

【问题讨论】:

  • 你只是难以理解primes = 2 : filter ((==1) . length . primeFactors) [3, 5 ..]这个表达式吗?
  • 不,我理解表达式本身的作用,它是我不理解的 filter 的第一个参数 ((==1) .length . primeFactors)

标签: haskell primes pointfree function-composition


【解决方案1】:

如果您要在命令行中打开 ghci 并输入

Prelude> :t filter

你会得到一个输出

filter :: (a -> Bool) -> [a] -> [a]

这意味着filter 需要两个参数。

  • (a -&gt; Bool) 是一个接受单个输入并返回 Bool 的函数。
  • [a] 是任何类型的列表,只要它与第一个参数的类型相同。

filter 将遍历其第二个参数列表中的每个元素,并将其应用于作为其第一个参数的函数。如果第一个参数返回True,则将其添加到结果列表中。

同样,ghci,如果您要输入

Prelude> :t (((==1) . length . primeFactors))

你应该得到

(((==1) . length . primeFactors)) :: a -> Bool

(==1) 是一个部分应用的函数。

Prelude> :t (==)
(==) :: Eq a => a -> a -> Bool
Prelude> :t (==1)
(==1) :: (Eq a, Num a) => a -> Bool

它只需要一个参数而不是两个。

意思是一起,它将接受一个参数,并返回一个布尔值。

它的工作方式如下。

  • primeFactors 将采用单个参数,并计算结果,即 [Int]
  • length 将获取这个列表,并计算列表的长度,并返回一个Int
  • (==1)会 查看length 返回的值是否等于1

如果列表的长度为1,则表示它是质数。

【讨论】:

  • 知道了。让我失望的是,我期待在括号内对 primeFactors 进行论证。然后在我记得过滤器循环通过 [3,5..] 之后。递归调用返回一个因子列表,部分函数应用于这些因子,仅包含长度为 1 的列表,即素数。希望这对每个人都有意义,我现在明白了。谢谢。
  • 很高兴能帮助您理解。
【解决方案2】:

(.) :: (b -&gt; c) -&gt; (a -&gt; b) -&gt; a -&gt; c是合成函数,所以

f . g = \x -> f (g x)

我们可以用这个运算符将两个以上的函数链接在一起

f . g . h  ===  \x -> f (g (h x))

这就是表达式 ((==1) . length . primeFactors) 中发生的情况。

【讨论】:

    【解决方案3】:

    表达式

    filter ((==1) . length . primeFactors) [3,5..]
    

    正在使用函数(==1) . length . primeFactors 过滤列表[3, 5..]。这种表示法通常称为无点,不是因为它没有. 点,而是因为它没有任何显式参数(在某些数学上下文中称为“点”)。

    . 实际上是一个函数,特别是它执行函数组合。如果你有两个函数fg,然后f . g = \x -&gt; f (g x),就是这样!这个操作符的优先级让你可以很流畅地把很多函数链接在一起,所以如果你有f . g . h,这和\x -&gt; f (g (h x))是一样的。当您有许多函数要链接在一起时,组合运算符非常有用。

    所以在这种情况下,您可以将函数 (==1)lengthprimeFactors 组合在一起。 (==1) 是一个通过所谓的运算符部分的函数,这意味着您向运算符的一侧提供参数,它会产生一个函数,该函数接受一个参数并将其应用于另一侧.其他示例及其等效的 lambda 形式是

    (+1)           =>  \x -> x + 1
    (==1)          =>  \x -> x == 1
    (++"world")    =>  \x -> x ++ "world"
    ("hello"++)    =>  \x -> "hello" ++ x
    

    如果你愿意,你可以使用 lambda 重写这个表达式:

    (==1) . length . primeFactors => (\x0 -> x0 == 1) . length . primeFactors
                                  => (\x1 -> (\x0 -> x0 == 1) (length (primeFactors x1)))
    

    或者使用$ 运算符更简洁:

    (\x1 -> (\x0 -> x0 == 1) $ length $ primeFactors x1)
    

    但这仍然比简单的“罗嗦”很多

    (==1) . length . primeFactors
    

    要记住的一点是. 的类型签名:

    (.) :: (b -> c) -> (a -> b) -> a -> c
    

    但我觉得多加一些括号会更好看:

    (.) :: (b -> c) -> (a -> b) -> (a -> c)
    

    这更清楚地表明该函数接受另外两个函数并返回第三个函数。密切注意此函数中类型变量的顺序。 . 的第一个参数是函数 (b -&gt; c),第二个参数是函数 (a -&gt; b)。您可以将其视为从右到左,而不是我们在大多数 OOP 语言中习惯的从左到右的行为(例如 myObj.someProperty.getSomeList().length())。我们可以通过定义一个具有相反参数顺序的新运算符来获得此功能。如果我们使用 F# 约定,我们的操作符称为|&gt;

    (|>) :: (a -> b) -> (b -> c) -> (a -> c)
    (|>) = flip (.)
    

    那么我们可以这样写

    filter (primeFactors |> length |> (==1)) [3, 5..]
    

    您可以将|&gt; 视为一个箭头,将一个函数的结果“输入”到下一个函数中。

    【讨论】:

    • 感谢您的回答,我只是在接受上述其他之一后才看到它。两者都说得很清楚,而且都很有帮助,所以 +1。
    • @Dave0504 无论哪一个说得最清楚都是可以接受的,我很高兴能帮上忙 :)
    • 在 F# 中,(|&gt;) x f = f x。它是左关联的。 flip (.) 在 F# 中 is called ( &gt;&gt; ).
    【解决方案4】:

    这只是意味着,只保留只有一个质因数的奇数。

    在其他伪代码中:filter(x -&gt; length(primeFactors(x)) == 1) for any x in [3,5,..]

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多