【问题标题】:Good Style of Haskell Fibonacci FunctionHaskell Fibonacci 函数的良好风格
【发布时间】:2016-07-17 20:59:45
【问题描述】:

我正在学习 Haskell,并尝试实现一个函数来获取包含前 N 个斐波那契数的列表:

fibonacci :: Integer -> [Integer]
fibonacci 1 = [0]
fibonacci 2 = fibonacci 1 ++ [1]
fibonacci n = appendSumOfLastTwo (fibonacci (n - 1))

appendSumOfLastTwo :: (Num a) => [a] -> [a]
appendSumOfLastTwo xs = xs ++ [addLastTwo xs]

addLastTwo :: (Num a) => [a] -> a
addLastTwo xs = last xs + (xs !! ((length xs) - 2))

这可行,但不是很漂亮,因为它需要两个具有奇怪名称的辅助函数。在 Haskell 中,有这种单独使用的函数很常见吗?

为了摆脱这些函数,我尝试了匿名函数:

fibonacci :: Integer -> [Integer]
fibonacci 1 = [0]
fibonacci 2 = fibonacci 1 ++ [1]
fibonacci n = (\xs -> xs ++ [(\xs -> last xs + (xs !! ((length xs) - 2))) xs]) (fibonacci ( n - 1))

但这并没有更好,因为它几乎完全不可读。

你们觉得呢?我怎样才能最好地构建我的代码?

【问题讨论】:

  • 虽然我个人很喜欢这类问题(并且确实引发了一些很好的答案),但遗憾的是,这些问题并不适合 SO - 看你真的没有问题,你要求建议或代码审查,我认为 StackExchange 上的 codereview 网站可能确实对那些人来说是一个更好的地方
  • 哦,好吧。谢谢,下次我会在那里发布此类问题。

标签: haskell anonymous-function fibonacci


【解决方案1】:

没有比这更漂亮的了

fibs = 1:1:zipWith (+) fibs (tail fibs)

并用于任何 n

take n fibs

或者,如果你想从基础开始实现,最好先定义第 n 个斐波那契数

fib 1 = 1
fib 2 = 1                                      
fib n = fib (n-1) + fib (n-2)

这个系列很简单

map fib [1..n]

请注意,对于任何大的 n,性能都会很糟糕。

【讨论】:

  • 感谢您提供优雅的解决方案,但我宁愿寻找一种更简洁的方式来实现我的算法。
【解决方案2】:

也许最接近您的原始设计,但具有线性性能,是使用 iterate 和一对数字作为状态:

almostFibs :: [(Integer, Integer)]
almostFibs = iterate (\(x, y) -> (y, x + y)) (0, 1)

这为您提供了一对“先前”和“当前”值:

Prelude> take 10 almostFibs
[(0,1),(1,1),(1,2),(2,3),(3,5),(5,8),(8,13),(13,21),(21,34),(34,55)]

要真正获得fibs,您只需删除“previous”值:

fibs :: [Integer]
fibs = map snd almostFibs

【讨论】:

    【解决方案3】:

    也许使用let 关键字。它允许您绑定仅在表达式范围内的变量(包括函数):

    > let x = 3 in x + 2
    5
    

    这里x 在评估x + 2 之前绑定到三个,得到五个。

    你可以用你的例子做类似的事情:

    fibonacci :: Integer -> [Integer]
    fibonacci 1 = [0]
    fibonacci 2 = fibonacci 1 ++ [1]
    fibonacci n = let
      addLastTwo :: (Num a) => [a] -> a
      addLastTwo xs = last xs + (xs !! ((length xs) - 2))
    
      appendSumOfLastTwo :: (Num a) => [a] -> [a]
      appendSumOfLastTwo xs = xs ++ [addLastTwo xs]
     in appendSumOfLastTwo (fibonacci (n - 1))
    

    让我们看看我们是否可以做得更好。我们还可以使用where 作为语法糖来提高可读性。这个关键字的行为与let ... in ... 完全一样,除非您有很多要绑定的变量和一个相对较短的表达式,它可能更具可读性:

    fibonacci :: Integer -> [Integer]
    fibonacci 1 = [0]
    fibonacci 2 = fibonacci 1 ++ [1]
    fibonacci n = appendSumOfLastTwo (fibonacci (n - 1))
      where
        addLastTwo :: (Num a) => [a] -> a
        addLastTwo xs = last xs + (xs !! ((length xs) - 2))
    
        appendSumOfLastTwo :: (Num a) => [a] -> [a]
        appendSumOfLastTwo xs = xs ++ [addLastTwo xs]
    

    好的,这里肯定有改进的余地。关于如何处理我们的某些功能,我们仍然没有“用门户思考”。特别是,addLastTwo 可以通过模式匹配得到显着改进:

    addLastTwo :: (Num a) => [a] -> a
    addLastTwo (x:y:[]) = x + y
    addLastTwo (_:rest) = addLastTwo rest
    addLastTwo _ = error "List has less than two elements!"
    

    这将列表的迭代次数从三个减少到一个(last 一个,length 一个,!! 最多一个)。

    此外,追加到列表的头部比追加到列表的末尾要容易得多。如有必要,您可以随时reverse 列表。每当您写 list ++ [elem] 时,请考虑一下您是否真的是指 elem : list

    考虑到这一点,再加上更多的模式匹配,一个相对干净的算法版本应该是这样的:

    fibonacci :: Integer -> [Integer]
    fibonacci 1 = [0]
    fibonacci 2 = [0, 1]
    fibonacci n = reverse $ (x + y) : upToN
      where
        upToN@(x:y:_) = reverse $ fibonacci (n - 1)
    

    这里,@ 字符将变量绑定到模式。在上面的示例中,upToN 将绑定到列表 [x, y, ...],但xy 也将在范围内。

    我建议您也花一些时间了解@karakfa 的答案为何有效,以及为什么它会比您采用的方法更快。

    【讨论】:

    • 哇,非常感谢。这正是我所希望的。看到我最初的代码的演变让我学到了很多东西。
    • 哦,伙计,这两个倒数...最好有一个斐波那契函数,它只是以另一个顺序返回答案。如有必要,将其命名为 fibonacci' 并写为 fibonacci = reverse . fibonacci',这样每次调用 fibonacci 时您只会得到一个反向,而不是结果中每个元素的两个反向。
    【解决方案4】:

    问题不在于完全有效的辅助函数。拥有这样的功能当然很常见,尤其是在本地定义它们。它与列表索引有关,并且必须处理问题所在的列表末尾。如果你真的需要这些操作,那么列表就是错误的数据结构。

    检索列表中“最后”两个项目并添加它们的概念非常合理 - 您只需处理列表的前面,并在末尾反转它。所以“最后一个”变成了“第一个”,你只是模式匹配。

    fibonacci :: Integer -> [Integer]
    fibonacci = reverse . fib where
      fib n | n < 1 = [] 
      fib 1 = [0]
      fib 2 = [1,0] 
      fib n = case fib (n-1) of 
                r@(a:b:_) -> a+b:r
    

    【讨论】:

      【解决方案5】:

      我认为最惯用的方法是生成无限列表,然后按照其他人的建议获取第一个 n 元素。

      否则,生成第一个n 斐波那契数的反转 序列看起来也不错。这允许通过简单地添加一个新元素来添加“列表末尾的数字”。

      如果我们更喜欢按直接顺序生成列表,我们可以使用如下递归:

      fibonacci :: Int -> [Int]
      fibonacci n = fib 0 1 n
         where fib _ _ 0 = []
               fib a b n = a : fib b (a+b) (n-1)
      

      【讨论】:

        【解决方案6】:

        一个好方法是使用 Haskell 的惰性求值并生成一个无限列表。然后,您可以take 该列表中您想要多少个数字。

        fibonacci :: [Integer]
        fibonacci = 1 : scanl (+) 1 fibonacci
        

        一旦你有了这个函数,你就可以检索你需要多少元素n

        > take 7 fibonacci
        [1,1,2,3,5,8,13]
        

        【讨论】:

          猜你喜欢
          • 2010-09-12
          • 2011-01-27
          • 1970-01-01
          • 2022-10-04
          • 1970-01-01
          • 1970-01-01
          • 2015-02-07
          • 2010-10-18
          • 2016-04-25
          相关资源
          最近更新 更多