【问题标题】:Using Haskell's map function to calculate the sum of a list使用 Haskell 的 map 函数计算列表的总和
【发布时间】:2011-09-04 17:53:40
【问题描述】:

Haskell

addm::[Int]->Int
addm (x:xs) = sum(x:xs)

我能够使用sum 函数获得列表的总和,但是否可以使用map 函数获得列表的总和?还有map函数有什么用?

【问题讨论】:

  • 您可以将addm 简化为addm = sum
  • 注意:除非您执行 Waldheinz 建议的操作,否则您的 addm 函数对于空列表未定义。

标签: list haskell recursion fold


【解决方案1】:

你不能真正使用map 来总结一个列表,因为 map 将每个列表元素独立于其他元素。例如,您可以使用 map 来增加列表中的每个值,例如

map (+1) [1,2,3,4] -- gives [2,3,4,5]

实现 addm 的另一种方法是使用 foldl

addm' = foldl (+) 0

【讨论】:

    【解决方案2】:

    这就是,summap 的形式被认为不可能的定义:

    sum' xs  =  let { ys = 0 : map (\(a,b) -> a + b) (zip xs ys) } in last ys
    

    这实际上显示了 scanl 可以如何根据map ziplast)来实现,以上等价于@ 987654330@:

    scanl' f z xs  =  let { ys = z : map (uncurry f) (zip ys xs) } in ys
    

    我希望map可以计算很多东西,通过zip安排各种信息流。

    编辑: 当然,上面只是伪装的zipWithzipWith 有点像map2):

    sum' xs  =  let { ys = 0 : zipWith (+) ys xs } in last ys
    

    这似乎表明scanlfoldl 更通用。

    【讨论】:

    • 好吧,这是可能的,但我不认为这是惯用的 Haskell。但是 +1 因为从技术上讲你是对的。 :-)
    • 我认为你在这里作弊,威尔。基本上,我读到的问题是询问您是否可以使用Functor [] 实例来计算列表的长度;你的答案是你可以使用Applicative [] 实例来这样做(基本上是ZipList)。我意识到我通过阅读Functor 来“丰富”这个问题(可以这么说),但是通过这个论点,你比我更丰富它,因为ApplicativeFunctor 更强大。跨度>
    • @LuisCasillas 这个答案的重点是我猜列表不是集合;它们的结构(项目的位置;第一个/最后一个元素)完全可见; map 不仅仅是 fmap。 IOW instance Functor [] 将列表作为集合讨论,因为只有 fmap 可用,没有别的。集合只有成员,没有位置,所以没有顺序。我们也可以很好地为列表集合定义Applicative,笛卡尔积是唯一可能的实现——正是因为没有位置的概念。但有了它,内积变成了另一种可能(即zip,即ZipList)。
    • 列表似乎是indexed families,或sequences。如果您接受这一点,您就不能反对使用zip。如果你反对,这意味着你将列表视为非排序的、无序的集合。
    • 另一件事是,OP 并没有说 map 是唯一允许的功能。所以是的,这个答案使用mapziplast,实际上实现了sum = foldl (+) 0,但它仍然以不平凡的方式使用map 来实现这一点。
    【解决方案3】:

    无法使用map 将列表缩减为总和。该递归模式是fold

    sum :: [Int] -> Int
    sum = foldr (+) 0
    

    顺便说一句,请注意您也可以将map 定义为折叠:

    map :: (a -> b) -> ([a] -> [b])
    map f = fold (\x xs -> f x : xs) []
    

    这是因为foldr 是列表上的规范递归函数。


    参考文献A tutorial on the universality and expressiveness of fold, Graham Hutton, J. 函数式编程 9 (4): 355–372,1999 年 7 月。

    【讨论】:

      【解决方案4】:

      在一些见解之后,我必须添加另一个答案:您无法使用map 获得列表的总和,但您可以使用其一元版本mapM 获得总和。您需要做的就是在Sum 半群(参见LYAHFGG)上使用Writer monad(参见LYAHFGG)。

      我写了一个专门的版本,可能更容易理解:

      data Adder a = Adder a Int
      
      instance Monad Adder where
        return x = Adder x 0
        (Adder x s) >>= f = let Adder x' s' = f x
                            in Adder x' (s + s') 
      
      toAdder x = Adder x x
      
      sum' xs = let Adder _ s = mapM toAdder xs in s  
      
      main = print $ sum' [1..100]
      --5050
      

      Adder 只是某种类型的包装器,它也保持“运行总和”。我们可以让Adder 成为一个monad,它在这里做了一些工作:当操作>>=(又名“bind”)被执行时,它返回新结果和该结果的运行总和的值plus原始运行总和toAdder 函数接受一个 Int 并创建一个 Adder 以将该参数保存为包装值和运行总和(实际上我们对值不感兴趣,而只对总和部分感兴趣)。然后在sum' mapM 可以发挥它的魔力:虽然它对于嵌入在 monad 中的值类似于 map,但它执行像 toAdderchains 这样的“monadic”函数调用(它使用sequence 来执行此操作)。至此,我们通过了 monad 的“后门”,了解了标准 map 所缺少的列表元素之间的交互。

      【讨论】:

        【解决方案5】:

        将列表中的每个元素“映射”到输出中的元素:

        let f(x) = x*x
        map f [1,2,3]
        

        这将返回一个正方形列表。

        要对列表中的所有元素求和,请使用折叠:

        foldl (+) 0 [1,2,3]
        

        +是你要应用的函数,0是初始值(0代表求和,1代表乘积等)

        【讨论】:

          【解决方案6】:

          正如其他答案所指出的,“正常”方式是使用 fold 函数之一。但是,可以用命令式语言编写类似于while 循环的内容:

          sum' [] = 0
          sum' xs = head $ until single loop xs where 
             single [_] = True
             single _ = False
             loop (x1 : x2 : xs) = (x1 + x2) : xs 
          

          它将列表的前两个元素相加,直到得到一个单元素列表,然后返回该值(使用head)。

          【讨论】:

            【解决方案7】:

            我意识到这个问题已经得到解答,但我想补充一下这个想法......

            listLen2 :: [a] -> Int
            listLen2 = sum . map (const 1)
            

            我相信它会为列表中的每个项目返回常量 1,并返回总和! 可能不是最好的编码实践,但这是我的教授给我们学生的一个例子,似乎与这个问题很相关。

            【讨论】:

              【解决方案8】:

              map 永远不能成为求和容器元素的主要工具,就像螺丝刀永远不能成为看电影的主要工具一样。但是您可以使用螺丝刀来固定电影放映机。如果你真的想,你可以写

              import Data.Monoid
              import Data.Foldable
              
              mySum :: (Foldable f, Functor f, Num a)
                    => f a -> a
              mySum = getSum . fold . fmap Sum
              

              当然,这很愚蠢。您可以获得更通用、可能更高效的版本:

              mySum' :: (Foldable f, Num a) => f a -> a
              mySum' = getSum . foldMap Sum
              

              或者更好,只需使用sum,因为它实际上是为工作而设计的。

              【讨论】:

                猜你喜欢
                • 2020-02-03
                • 2021-12-18
                • 2012-10-03
                • 2016-08-01
                • 1970-01-01
                • 2020-01-30
                • 1970-01-01
                • 2020-01-02
                • 1970-01-01
                相关资源
                最近更新 更多