# For-loop not working as I expect

I want to do this:

• When 10 <= t < 130, give t -> 1;

• When 130 <= t<300, give t->3;

• When 300 <= t< 1000, give t -> 4;

• When 1000 <= t < 2000, give t -> 5;

My code is this:

data = {10, 130, 300, 1000, 2000};
fun[t_] := Part[data, t];
comf[s_] := For[t = 1, t <= 4, t++, If[fun[t] <= s < fun[t+1], Print[t]]];


I could get this:

comf[110]

1

comf[1300]

4


But when I evaluate this

comf[110] + 100


I didn’t get the right result, rather I got this:

100 + Null


I would like to know how I could fix this problem.

-

If this t increments by 1 (or constant amount) as suggested by code (I only raise this the question does 1,3,4,5) then you use UnitStep and decided what you want to do outside the domain.

data = {10, 130, 300, 1000, 2000};
func[t_] := Total[UnitStep[t - #] & /@ data]


Visualizing:

Plot[func[x], {x, 0, 3000}, Exclusions -> None]


UPDATE Mr. Wizard has explained in comment how this can be simplified. Mr Wizard's advice:

mrwmod[u_]:=Tr@UnitStep[u-data]

-
Quite an interesting approach. Incidentally you don't need Map; I would write: func[t_] := Tr @ UnitStep[t - data]. – Mr.Wizard Feb 2 '14 at 13:54
@Mr.Wizard thank you. I always learn something. Will edit. – ubpdqn Feb 3 '14 at 1:34

For your existing code you need to Return the value of t rather than Printing it:

data = {10, 130, 300, 1000, 2000};
fun[t_] := Part[data, t];
comf[s_] := For[t = 1, t <= 4, t++, If[fun[t] <= s < fun[t + 1], Return @ t]]


Now:

comf[110] + 100

101


However, this code is very much not in the recommended style of Mathematica programming. See Alternatives to procedural loops and iterating over lists in Mathematica for some examples.

## Alternatives

Perhaps the most direct simplification of the above code is to use Do in place of the manually incremented variable in For:

comf[s_] := Do[If[fun[t] <= s < fun[t + 1], Return @ t], {t, 4}]


You could also write it as a self-contained Piecewise function:

f1 =
Piecewise[
{{1, 10 <= # < 130},
{2, 130 <= # < 300},
{3, 300 <= # < 1000},
{4, 1000 <= # < 2000}}
] &;

f1 /@ {5, 17, 200, 300, 5000}

{0, 1, 2, 3, 0}


(The second argument of Piecewise can be used to determine what is returned when none of the conditions match; the default is zero.)

If you prefer to keep your values in data you might use something like LengthWhile:

f2[s_] := LengthWhile[data, # <= s &]

f2 /@ {5, 17, 200, 300, 5000}

{0, 1, 2, 3, 5}


A very efficient method is Interpolation with an InterpolationOrder of zero:

f3 = Interpolation[MapIndexed[{#, #2[[1]] - 1} &, data], InterpolationOrder -> 0];


Note however that this does not produce the same less-equal behavior as the others; it also warns you when the output is outside the interpolation range:

f3 /@ {5, 17, 200, 300, 5000}


InterpolatingFunction::dmval: Input value {5} lies outside the range of data in the interpolating function. Extrapolation will be used. >>

InterpolatingFunction::dmval: Input value {5000} lies outside the range of data in the interpolating function. Extrapolation will be used. >>

{0, 1, 2, 2, 4}


rasher posted a method using Interval and IntervalMemberQ which I like, but which I believe can be written more efficiently. Specifically the inefficiencies are:

• computing the Interval list every time the function is called
• not using the listability of IntervalMemberQ

We can address both points with this:

With[{intv = Interval /@ Partition[data, 2, 1]},
f4 = Pick[index, IntervalMemberQ[intv, #]] &;
]

index = {1, 3, 4, 5};

f4 /@ {5, 30, 400, 5000}

{{}, {1}, {4}, {}}

-
What a nice analysis. +1 on using interpolation! – ciao Feb 2 '14 at 12:58

Neat answers! My twisted take on something different, a generic function that accepts the data and indexes and returns an indexer:

data = {10, 130, 300, 1000, 2000};
index = {1, 3, 4, 5};

test = Function[arg,
Extract[index,
Position[
Interval /@
Partition[data, 2, 1], _?(IntervalMemberQ[#, arg] &)]]];

test[#] & /@ {5, 30, 400, 5000}


(* {{}, {1}, {4}, {}} *)

Note that indices are bracketed, out-of-range being empty. Can of course trivially be turned into a function that accepts the data/indices directly.

-
You have made a mistake that I see surprisingly frequently: you added a Function where it is not needed; test is already a Function; just use test /@ {5, 30, 400, 5000}. Also, and more importantly, the computation of the Interval list is performed for each call to test, which is inefficient. Nevertheless I like the idea of using IntervalMemberQ so +1. I shall attempt to write a more efficient version. – Mr.Wizard Feb 3 '14 at 3:03
Habit. And of course, this was just an example, my allusion to a function accepting the data/indices was bread-crumbs on the path toward a proper implementation. Shame interval testing is a bit sluggish: probably better to generate a Piecewise function as the result, but I like to use orphan functions occasionally. – ciao Feb 3 '14 at 3:11
Actually I think it will be reasonably fast in the listable form (see my updated answer). I intend to add timings for all methods later tonight. – Mr.Wizard Feb 3 '14 at 3:19
@Mr.Wizard: Color me surprised that you took my hunchback code and turned it into the handsome prince ;-). Your comments and answers always enlighten! I look forward to the timings. – ciao Feb 3 '14 at 3:53