4

How would I calculate the average of a list of numbers using map and reduce.

Ideally I want to call reduce on a list and get an average back. You may optionally map and filter that list first.

A skeleton LISP attempt:

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

A skeleton JS attempt:

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

If you post an answer with actual code in a language please explain it as if I'm a beginner in that langauge (which I probably will be).

I want to avoid the trivial answer of

function average (array) {
   return sum(array) / array.length;
}

This uses division at the end rather then a reduce statement. I consider this "cheating".

[[Edit]]

Solved my own problem. If anyone has an elegant solution using syntactic sugar from LISP or Haskell I would be interested.

Raynos
  • 166,823
  • 56
  • 351
  • 396
  • Better suited for CodeGolf.SE perhaps? – ircmaxell May 12 '11 at 20:15
  • A simple `reduce` aka `fold` is not `mapreduce` (as in, the Google framework). Not even if you `map` the input beforehand. –  May 12 '11 at 20:18
  • @delnan sorry I got my terminology confused. – Raynos May 12 '11 at 20:19
  • The first step should be working out the maths behind the solution. Can you think about an iterative algorithm that gives you what you want? Turning that into a `fold` shouldn't be too hard. – abesto May 12 '11 at 20:23
  • @abesto the solution seems so simple now. I wasn't thinking thank you. – Raynos May 12 '11 at 20:32

5 Answers5

3

As @abesto mentioned it requires an iterative algorithm.

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

return counter

A javascript implementation would be

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);  
}
Raynos
  • 166,823
  • 56
  • 351
  • 396
2

calculate the average of a list of numbers using map and reduce

There's no map needed. Just a unfold to generate the list, and a fold to reduce it to a mean value:

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)

An efficient implementation

This structure (fold . unfold) allows for the fusion optimization to occur. A particularly efficient implementation will fuse the unfold (list generation) with the fold (the list reduction). Here, in Haskell, GHC combines the two phases (unfold == enumFromN) and the fold via stream fusion:

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))

Which the compiler converts from a composition of two functions, into a recursive loop:

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)

which reduces to this goto in assembly (the C backend to the compiler):

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

More efficient, but more confusing implementations result from LLVM (about 2x faster).


References: Computing the mean of a list efficiently in Haskell

Community
  • 1
  • 1
Don Stewart
  • 137,316
  • 36
  • 365
  • 468
1

Here's a version in 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

It keeps track of a running average and weight, and calculates the new average as the ((previous average * weight) + new value).

ataylor
  • 64,891
  • 24
  • 161
  • 189
  • could you explain `1+` and `weight`. I know nothing of LISP. Also `* (car r) (cdr r)` Is `r` a tuple? – Raynos May 12 '11 at 21:25
  • `1+` is a function that adds 1 to the argument. `weight` is just a local variable name. Yes, `r` is a tuple; I've cleaned it up a bit by creating some meaningful names. – ataylor May 12 '11 at 21:26
  • it's reasonably obvouis what `:initial-value` does but could you explain the semantics of that syntax? – Raynos May 12 '11 at 21:33
  • It's an optional keyword argument to `reduce`. Common Lisp functions can take named arguments, where the name is a lisp symbol. The leading colon is just the lisp syntax for symbols. With an initial value, the first call of the function is with `(initial-value, element0)` instead of `(element0, element1)`. – ataylor May 12 '11 at 21:36
  • Now that I understand the code I prefer the original version because it has that classic LISP terseness :) – Raynos May 12 '11 at 21:40
0

A description of an apporach in Haskell that allows a compositional approach to folds: http://conal.net/blog/posts/another-lovely-example-of-type-class-morphisms/

sclv
  • 38,665
  • 7
  • 99
  • 204
0

In Mathematica

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)
Dr. belisarius
  • 60,527
  • 15
  • 115
  • 190