How to find range in which a number falls, from given list of numbers?

Interpolation

I propose using Interpolation.

list = Prime ~Array~ 3000;
intf = Interpolation[
         {list, Range@Length@list}\[Transpose],
         InterpolationOrder -> 0
       ];

Then, for point x:

x = 12225.4;

Which[
 x < First@list , {-∞, First@list},
 x > Last@list  , {Last@list, ∞},
 True           , list[[#-1 ;; #]]& @ intf @ x
]
{12211, 12227}

This could all be done inside Interpolation as well:

intf2 =
  Interpolation[
    Join[
      {{First@list, {-∞, 2}}},
      Thread[{Rest@list, Partition[list, 2, 1]}],
      {{Last@list + 1, {Last@list, ∞}}}
    ],
    InterpolationOrder -> 0
  ];

intf2[12225.4]
{12211, 12227}

Ordering

The method above was written from the perspective of repeated searching within the same list, and as noted in the comments it is assumed that the input list is sorted and free of duplicates.

If these assumptions do not hold other methods become appealing. After a review of others answers seeking inspiration, including those by kglr, celtschk and Leonid, I find Leonid's use of UnitStep to have great promise but his function is hobbled by the comparatively slow function Position. We can replace it with a use of Ordering.

  • This function requires a sorted list as input, but including the overhead of Sort I still find it faster than other methods I tried such as a separate application of Ordering in an earlier revision of this answer.

  • I use an explicit Subtract for performance.

Code:

seekOrdered[x_, list_] /; x < First @ list := {-∞, First @ list}

seekOrdered[x_, list_] /; x >= Last @ list := {Last @ list, ∞}

seekOrdered[x_, list_] := 
  list[[# ;; # + 1]] & @@ Ordering[UnitStep @ Subtract[x, list], -1]

Here are comparative timings including Leonid's getInterval, celtschk's function, and a variation of kglr's interval2 using Replace rather than ReplaceList to return a single interval (in the case of ambiguous matches) for somewhat better performance. (Credit to Ali Hashmi for noting this.)

The various functions I am comparing take slightly different interpretations of the end point behavior requested therefore output does not precisely match. It should be possible to change the behavior of my function with a bit of tinkering should that be required for a particular application.

The other functions as I will be timing them:

getInterval[ints_List, num_] := 
  Position[UnitStep[ints - num], 1, 1, 
    1] /. {{{1}} -> {-Infinity, First@ints}, {} -> {Last@ints, Infinity}, {{n_}} :> 
     ints[[n - 1 ;; n]]};

celtsF[x_, list_List] := 
 Module[{pos = Last@Ordering@Ordering[Append[list, x]]}, 
  Which[pos == 1, {-Infinity, First@list}, 
   pos == Length[list] + 1, {Last@list, Infinity}, True, list[[{pos - 1, pos}]]]]

interval2fast[x_, list_List] := 
 Replace[#, {___, a_, x, b_, ___} :> {a, b}] &@
  Join[{-Infinity}, Sort[Join[list, {x}]], {Infinity}]

Benchmark 1:

list = Prime ~Array~ 3000;

xs = RandomInteger[{-100, 30000}, 5000];

interval2fast[#, list] & /@ xs;  // RepeatedTiming // First
getInterval[list, #] & /@ xs;    // RepeatedTiming // First
celtsF[#, list] & /@ xs;         // RepeatedTiming // First
seekOrdered[#, list] & /@ xs;    // RepeatedTiming // First

1.25

0.5890

0.224

0.114

With a packed input list:

list = Developer`ToPackedArray @ list;

(* other code the same *)

1.16

0.5370

0.129

0.0689

With Reals rather than Integers for the search elements:

xs = RandomReal[{-100, 30000}, 5000];

(* other code the same *)

1.54

0.5674

0.855

0.0981

With Reals rather than Integers for the list:

list = Sort @ RandomReal[27000, 3000];

(* other code the same *)

1.88

0.552

0.129

0.0895

Of course for this repeated application Interpolation is faster still:

intf2 /@ xs; // RepeatedTiming // First // Quiet

0.0112


You can make use of BinCounts. I think this is a very simple to understand solution because BinCounts does almost exactly what you need already.

f[x_, list_List] :=
 Module[{bins},
  bins = Join[{-Infinity}, Sort[list], {Infinity}];
  First@Pick[Partition[bins, 2, 1], BinCounts[{x}, {bins}], 1]
 ]

But it won't give you two intervals if the number is part of both. Of course it's very easy to put in an extra check and include this feature if you need it, but I just wanted to show the concept now.


Assuming the list list is already ordered, the following should answer your question:

f[x_,list_List]:=
  Module[{pos=Last@Ordering@Ordering[Append[list,x]]},
    Which[pos==1,{-Infinity,First@list},
          pos==Length[list]+1,{Last@list,Infinity},
          True,list[[{pos-1,pos}]]]]