是否有更简洁、优雅、Haskell 的方式来编写此 LCM 函数?

Is there a more clean, elegant, Haskell way to write this LCM function?

我刚开始 Haskell 并敲定了这个简单的递归算法来为列表中的每个数字找到 LCM。它有效,但它很混乱,我希望就如何使它更优雅、更易读和 Haskell-y.

进行一些同行评审
lcms list 
  | length list > 1 = lcms (lcm (head list) (head (tail list)):(tail (tail list)))
  | otherwise = list

因此,它获取一个列表并对前两项进行 LCM,然后将其添加到减去这两个元素的列表中。基本上,我要使用的伪代码是这样的:

lcms [a,b,c] = lcm (a, (lcm (b, c))

任何人有什么建议吗?我渴望在 Haskell 上取得进步,写出人们可以真正阅读的东西。也欢迎效率提示!

谢谢大家!

弃牌:

import Data.List

lcms :: [Int] -> Int
lcms xs = foldl' lcm 1 xs

其中 lcm 计算两个数字的 lcm:

lcm :: Int -> Int -> Int

您几乎可以使用您建议的语法编写它,因此:

lcms (a:b:c) = lcms (lcm a b:c)
lcms list = list

我发现第二个子句有点奇怪,但并不可怕:它可以优雅地处理空列表,尽管 return 当你知道你最多 return 一个列表时可能 return一些 hasocists 认为您的类型有点不精确。您还可以考虑使用 Maybe,规范的 0 或 1 元素类型:

lcms (a:b:c)  = lcms (lcm a b:c)
lcms [answer] = Just answer
lcms []       = Nothing

另一个不错的选择是确定一个合理的基本案例。对于二元运算,运算单位通常是一个不错的选择,所以对于lcm,我会选择1,因此:

lcms (a:b:c)  = lcms (lcm a b:c)
lcms [answer] = answer
lcms []       = 1

一般来说,尽可能避免显式递归;另一个答案显示了如何采取该步骤。在这种情况下,有一个中间转换使基本情况在美学上稍微更令人愉悦:与其将累加器保持在列表的开头——这只是偶然起作用,因为你的累加器与列表元素的类型相同——一个可以使积累更明确或更少。因此,其中之一:

lcms (x:xs) = lcm x (lcm xs)
lcms []     = 1

-- OR

lcms = go 1 where
    go acc (x:xs) = go (lcm acc x) xs
    go acc []     = acc

这两种实现对应选择foldrfoldl来消除显式递归; foldl' 与第二个类似,但多了一个 seq:

lcms = go 1 where
    go acc (x:xs) = let acc' = lcm acc x in acc' `seq` go acc' xs
    go acc []     = acc