# Functional equivalent of a For loop referring to consecutive elements in a list

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?

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.

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

• 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 – ben18785 Feb 22 '15 at 0:52
• @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... – ciao Feb 22 '15 at 0:54

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}