Often when I'm working with numerical data I want to select those points within a given range.
To do this I generally do something like:
Select[samp, min < # < max &]
or if I'm feeling lazy:
Select[samp, Between[ {min, max} ]]
But today I was thinking about how inefficient this is and decided to try to do better. My first attempt was the following:
selectBetween[
data : {_?NumericQ, _, __},
{min_, max_}
] :=
Pick[data,
UnitStep[data - max] + UnitStep[min - data],
0
];
selectBetweenBy[test_, {min_, max_}][data_] :=
selectBetweenBy[data, test, {min, max}]
Which we can test for correctness:
samp = RandomReal[1, 100000];
selectBetween[samp, {.001, .01}] == Select[samp, .001 < # < .01 &]
True
And then for performance:
selectBetween[samp, {.001, .01}] // RepeatedTiming // First
0.000544
Select[samp, .001 < # < .01 &] // RepeatedTiming // First
0.076
Select[samp, Between[ {.001, .01}]] // RepeatedTiming // First
0.27
Already we're doing pretty well.
And if we want to extend to other data we can give it an operator and By
form:
selectBetween[{min_, max_}][data_] :=
selectBetween[data, {min, max}];
selectBetweenBy[
data_,
test_,
{min_, max_}
] :=
With[{
testData =
Replace[test,
{
n_Integer :> data[[All , n]],
_ :> Map[test, data]
}
]},
Pick[data,
UnitStep[testData - max] + UnitStep[min - testData],
0
]
]
But can we do better? I'm sure what I did there can be tweaked an optimized and given how often I have to do this and the scale of the data I am often working on it would be nice to have it more efficient.
As kglr notes this question: Finding all elements within a certain range in a sorted list has some answers that are very, very closely related (as they operate on arbitrary unsorted data). Many of them are probably more efficient than what I've posted.