【问题标题】:Average value using fold使用折叠的平均值
【发布时间】:2011-05-12 20:14:12
【问题描述】:

如何使用 map 和 reduce 计算数字列表的平均值。

理想情况下,我想在列表上调用 reduce 并获得平均回报。您可以选择先映射和过滤该列表。

一个骨架 LISP 尝试:

(defun average (list)
  (reduce ... list))

一个骨架 JS 尝试:

function average (array) {
  return array.reduce(function() {
    ..
  }, 0);
}

如果您使用某种语言的实际代码发布答案,请解释为好像我是该语言的初学者(我可能会)。

我要避免

的琐碎回答
function average (array) {
   return sum(array) / array.length;
}

这在末尾使用除法而不是 reduce 语句。我认为这是“作弊”。

[[编辑]]

解决了我自己的问题。如果有人使用 LISP 或 Haskell 的语法糖有一个优雅的解决方案,我会很感兴趣。

【问题讨论】:

  • 或许更适合 CodeGolf.SE?
  • 一个简单的reduce 又名fold 不是mapreduce(就像在Google 框架中一样)。即使您事先map 输入也不行。
  • @delnan 抱歉,我的术语混淆了。
  • 第一步应该是解决解决方案背后的数学问题。你能想出一个迭代算法来给你想要的东西吗?把它变成fold 应该不会太难。
  • @abesto 解决方案现在看起来很简单。没想到谢谢。

标签: language-agnostic functional-programming average


【解决方案1】:

正如@abesto 所说,它需要一个迭代算法。

Let counter be 0 
For each [value, index] in list   
  let sum be (counter * index) + value   
  let counter be sum / (index + 1)

return counter

一个 javascript 实现将是

var average = function(list) { 
    // returns counter
    return list.reduce(function(memo, val, key) {
         // memo is counter
         // memo * key + val is sum. sum / index is counter, counter is returned
         return ((memo * key) + val) / (key + 1)
    // let counter be 0
    }, 0);  
}

【讨论】:

    【解决方案2】:

    使用 map 和 reduce 计算数字列表的平均值

    不需要map。只需展开以生成列表,然后折叠以将其减少到平均值:

    mean n m = uncurry (/) . foldr g (0, 0) . unfoldr f $ n
          where 
            f b | b > m     = Nothing
                | otherwise = Just (b, b+1)
    
            g x (s,n) = (s+x, n+1)
    

    高效的实现

    此结构 (fold . unfold) 允许进行融合优化。一个特别有效的实现将融合展开(列表生成)和折叠(列表减少)。在这里,在 Haskell 中,GHC 结合了两个阶段(展开 == enumFromN)和通过流融合的折叠:

    import Data.Vector.Unboxed 
    
    data Pair = Pair !Int !Double
    
    mean :: Vector Double -> Double
    mean xs = s / fromIntegral n
      where
        Pair n s       = foldl' k (Pair 0 0) xs
        k (Pair n s) x = Pair (n+1) (s+x)
    
    main = print (mean $ enumFromN 1 (10^7))
    

    编译器将两个函数的组合转换为递归循环:

    main_loop a d e n =
        case ># a 0 of 
          False -> (# I# n, D# e #);
          True -> main_loop (-# a 1) (+## d 1.0) (+## e d) (+# n 1)
    

    在汇编中简化为 goto(编译器的 C 后端):

    Main_mainzuzdszdwfoldlMzqzuloop_info:
            leaq    32(%r12), %rax
            cmpq    %rax, 144(%r13)
            movq    %r12, %rdx
            movq    %rax, %r12
            jb      .L198
            testq   %r14, %r14
            jle     .L202
    .L199:
            movapd  %xmm5, %xmm0
            leaq    -1(%r14), %r14
            movsd   .LC0(%rip), %xmm5
            addq    $1, %rsi
            addsd   %xmm0, %xmm6
            movq    %rdx, %r12
            addsd   %xmm0, %xmm5
            jmp     Main_mainzuzdszdwfoldlMzqzuloop_info
    

    LLVM 带来更高效但更令人困惑的实现(大约快 2 倍)。


    参考文献Computing the mean of a list efficiently in Haskell

    【讨论】:

      【解决方案3】:

      这是 common lisp 的一个版本:

      (defun running-avg (r v)
        (let* ((avg (car r))
               (weight (cdr r))
               (new-weight (1+ weight)))
          (cons (/ (+ (* avg weight) v) new-weight) new-weight)))
      
      (car (reduce 'running-avg '(3 6 5 7 9) :initial-value '(0 . 0)))
      ;; evaluates to 6
      

      它会跟踪运行平均值和体重,并将新平均值计算为((previous average * weight) + new value)

      【讨论】:

      • 您能解释一下1+weight。我对 LISP 一无所知。还有* (car r) (cdr r) r 是一个元组吗?
      • 1+ 是一个将参数加 1 的函数。 weight 只是一个局部变量名。是的,r 是一个元组;我通过创建一些有意义的名称对其进行了一些清理。
      • :initial-value 所做的事情相当明显,但您能解释一下该语法的语义吗?
      • 这是reduce 的可选关键字参数。 Common Lisp 函数可以采用命名参数,其中名称是一个 lisp 符号。前导冒号只是符号的 lisp 语法。有了初始值,函数的第一次调用是(initial-value, element0)而不是(element0, element1)
      • 现在我理解了代码,我更喜欢原始版本,因为它具有经典的 LISP 简洁性:)
      【解决方案4】:

      对 Haskell 中允许使用组合方法进行折叠的方法的描述: http://conal.net/blog/posts/another-lovely-example-of-type-class-morphisms/

      【讨论】:

      • 如果我参加“Haskell 初学者课程”,那篇文章会很有意义。
      • 链接的“漂亮折叠”帖子更易于访问。
      【解决方案5】:

      在数学中

      mean[l_List]:=
          Fold[{First@#1+1,(#2 +#1[[2]](First@#1-1))/First@#1}&,{1,1},l][[2]]  
      
      In[23]:= mean[{a,b,c}]
      Out[23]= 1/3 (a+b+c)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-10-31
        • 2022-08-20
        • 2020-08-14
        • 2022-06-15
        • 2021-04-04
        • 2016-02-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多