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$
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.

| improve this answer | |
$\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...

| improve this answer | |
$\endgroup$
  • $\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 Feb 22 '15 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 Feb 22 '15 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}
| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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