4
$\begingroup$

I have a sorted list with no duplicates:

L = {1, 2, 5, 7, 8}

Given an integer n, I want to find the index of n if it is to be inserted into L.

I have been using

Length[Select[L, # <= n &]]

But that is incredibly unoptimal as I should be able to do this in $\log n$ by bisecting the set. How should I approach this?

$\endgroup$
4
$\begingroup$
ClearAll[pos0, pos1, pos2, pos3]
pos0[l_, n_] := Total@UnitStep[n - l]
pos1[l_, n_] := LengthWhile[l, # < n &]
pos2[l_, n_] := Ordering[Ordering[Join[{n}, l]]][[1]]
pos3[l_, n_] := If[n <= Length[l]/3, pos1[l, n], pos2[l, n]]


L = {1, 2, 5, 7, 8};
{pos0[L,3], pos1[L, 3], pos2[L, 3], pos3[L, 3]}

{2, 2, 2, 2}

$\endgroup$
3
$\begingroup$

You could use a variation of my LeftNeighbor function, although it returns the left neighbor strictly less than the number instead of less equal as in your solution:

LeftNeighbor[s_]:=LeftNeighborFunction[s,Nearest[s->"Index"]]
LeftNeighbor[s_,list_List]:=LeftNeighbor[s][list]
LeftNeighbor[s_, elem_]:=First@LeftNeighbor[s][{elem}]

LeftNeighborFunction[s_,nf_][list_List]:=With[{n=nf[list][[All,1]]},n-UnitStep[s[[n]]-list]]
LeftNeighborFunction[s_,nf_][elem_]:=First @ LeftNeighborFunction[s,nf][{elem}]

MakeBoxes[i:LeftNeighborFunction[s_,nf_], StandardForm] ^:= Module[
    {
    len=Length[s],
    g = FirstCase[ToBoxes[nf], _GraphicsBox, GraphicsBox[Point[{0,0}]],Infinity]
    },
    BoxForm`ArrangeSummaryBox[
        LeftNeighborFunction,
        i,
        RawBoxes@g,
        {
        BoxForm`MakeSummaryItem[{"Data points: ",Length[s]},StandardForm],
        BoxForm`MakeSummaryItem[{"Range: ",MinMax[s]},StandardForm]
        },
        {},
        StandardForm,
        "Interpretable"->True
    ]
]

Then:

L={1,2,5,7,8};
lnf = LeftNeighbor[L];

lnf[5]
lnf[Range[0, 9]]

2

{0, 0, 1, 2, 2, 2, 3, 3, 4, 5}

It should be possible to modify the code to return the left neighbor that is less equal than the requested number if desired.

A remark on performance

The construction of the LeftNeighborFunction can be slow, but if you want to find the index of many numbers into the same list L, then you will only have to construct the LeftNeighborFunction once. Here is a comparison with a large list:

L = Sort @ RandomReal[100, 10^6];

The construction of the LeftNeightbor is a bit slow:

lnf = LeftNeighbor[L]; //RepeatedTiming

{0.052, Null}

But finding the index of many numbers is very fast:

sample = RandomReal[100, 10^3];
r1 = lnf[sample]; //RepeatedTiming

{0.00020, Null}

Compare this with one of kglr's solution:

r2 = pos0[L, #]& /@ sample; //RepeatedTiming

{3.05, Null}

Check :

r1 === r2

True

$\endgroup$
2
$\begingroup$

It seems to me that the issue of how to handle inappropriate inputs is as important finding the position of an integer that is in the list being considered. Therefore, I would write something like this:

positionOfN[data : {__Integer}, n_Integer] := 
  First[First[Position[data, n], Return[$Failed]]]
positionOfN[_, _] = $Failed;

Testing

data = {1, 2, 5, 7, 8};
positionOfN[data, 5]

3

positionOfN[data, -5]

$Failed

positionOfN[data, 5.]

$Failed

positionOfN[{"foo", "bar"}, 5]

$Failed

Note

If you don't want $Failed to be returned as the failure value, substitute whatever value you like for the two occurrences of $Failed.

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