Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

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.

share|improve this question
add comment

3 Answers

up vote 5 down vote accepted

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]

enter image description here

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

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

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}

See: How can the behavior of InterpolationOrder->0 be controlled?


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}, {}}
share|improve this answer
    
What a nice analysis. +1 on using interpolation! –  rasher Feb 2 at 12:58
add comment

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.

share|improve this answer
    
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 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. –  rasher Feb 3 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 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. –  rasher Feb 3 at 3:53
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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