9
$\begingroup$

I have implemented this function which smooths the values in a list by, if it encounters a zero, replacing this entry by the average of its two neighbours (apart from the first and last values of the list, which are left alone). The following works:

smoother[vTemp__] := 
  Module[{vTempSmoothed = ConstantArray[-1, Length[vTemp]], i}, 
    For[i = 1, i <= Length[vTemp], i++, 
      If[And[i > 1, i < Length[vTemp]], 
        If[vTemp[[i]] == 0, 
          vTempSmoothed[[i]] = Mean[{vTempSmoothed[[i - 1]], vTemp[[i + 1]]}], 
          vTempSmoothed[[i]] = vTemp[[i]]], 
        vTempSmoothed[[i]] = vTemp[[i]]]]; vTempSmoothed]

Testing the following on the list below returns the required result:

vTempTest = {1, 3, 4, 6, 8, 0, 11, 12, 0, 13};
smoother[vTempTest]

However, wanting to improve my knowledge of functional programming, I would like to know how to replace the above For loop with a more functionally-desirable alternative (for example Map, or Apply).

Does any one have an idea how I could do this?

$\endgroup$

3 Answers 3

6
$\begingroup$

Here are two possibilities. First, use MovingMap:

ClearAll[av];
av[{l_, 0, r_}] := (l + r)/2;
av[{_, m_, _}] := m;

and then

smoothMM[list_] := Join[{First@list}, MovingMap[av, list, 3], {Last@list}]

or, you can use in-place assignments:

smooth2[list_] :=
  Module[{copy = list, pos = Flatten[Position[list[[2 ;; -2]], 0]] + 1 },
    copy[[pos]] = (copy[[pos - 1]] + copy[[pos + 1]])/2;
    copy
  ]

The second one will be more efficient.

There are obviously infinitely many other ways to solve this.

$\endgroup$
7
$\begingroup$

This duplicates the behavior of yours (no effect on zeroes at ends):

smoothee=ReplacePart[#, i_ /; i > 1 && i < Length@# && #[[i]] == 0 :> 
                                   Mean[{#[[i - 1]], #[[i + 1]]}]] &;

smoothee[{0, 1, 3, 4, 6, 8, 0, 11, 12, 0, 13, 0}]

(* {0, 1, 3, 4, 6, 8, 19/2, 11, 12, 25/2, 13, 0} *)

Here's a goofy (but quite fast) method:

goofy = (# + Join[{0}, BitXor[1, Unitize@#[[2 ;; -2]]], {0}] MeanFilter[#, 1]*3/2) &;

And a seriously fast way - note I've broken out the pieces, so you can see what's happening. You'd combine this into one function for real use. I compared to one of the faster answers:

vTempTest = RandomInteger[{0, 1000}, 500000];


Timing[

 x = vTempTest;
 zers = DeleteCases[Pick[Range@Length@x, x, 0], 1 | Length@x];
 means = Mean[x[[{# - 1, # + 1}]]] & /@ zers
 x[[zers]] = means;

 ]

f = fiter[vTempTest]; // Timing

x == f

(*

{0.015600, Null}

{6.380441, Null}

True

*)

Squeezing the last together into a function, and taking advantage of listability of functions like mean nets an extra speed bump:

fast1 = Module[{z = DeleteCases[Pick[Range@Length@#, #, 0], 1 | Length@#], c = #},
                c[[z]] = Mean[{c[[Subtract[z, 1]]], c[[z + 1]]}];c] &;

vTempTest = RandomInteger[{0, 500}, 1000000];

Timing[
 x = vTempTest;
 zers = DeleteCases[Pick[Range@Length@x, x, 0], 1 | Length@x];
 means = Mean[x[[{# - 1, # + 1}]]] & /@ zers;
 x[[zers]] = means;
 ]

f = fiter[vTempTest]; // Timing

ls = smooth2[vTempTest]; // Timing

f1 = fast1[vTempTest]; // Timing

x == f == ls == f1

(*

{0.093601,Null}

{11.356873,Null}

{0.327602,Null}

{0.078001, Null}

True

*)

NB - Timings on an old netbook...

$\endgroup$
2
  • $\begingroup$ That's great. Thanks for that. Does anyone know of any others that could also do the same job? I am interested to see a variety of functional alternatives! Cheers, Ben $\endgroup$
    – ben18785
    Commented Feb 22, 2015 at 0:52
  • 1
    $\begingroup$ @ben18785: I'll cook up some more if time permits, I'm sure others will have interesting solutions. NB: There is a bug in yours (unless it's intended) - if two zeroes are in sequence, you average using the already calculated average for the first zero... $\endgroup$
    – ciao
    Commented Feb 22, 2015 at 0:54
3
$\begingroup$

Yet another approach.

data = {1, 3, 4, 6, 8, 0, 11, 12, 0, 13};
filterF[{a_, b_, c_} /; b == 0] := (a + c)/2
filterF[{a_, b_, c_}] := b
fiter[data_] := Join[{data[[1]]}, filterF /@ Partition[data, 3, 1], {data[[-1]]}]

fiter @ data
{1, 3, 4, 6, 8, 19/2, 11, 12, 25/2, 13}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.