If you sort your values, you can then make an iterator on the sorted list, forward it to the lower limit, count until the first value is reached that is larger than the upper limit and discard all further values.
The sorting will add \$\mathcal{O}(n\log n)\$ time complexity, but if you have a lot of values larger than (all) your upper bounds, you could get this back.
An implementation using itertools could be:
from itertools import dropwhile, takewhile def graipher(scores, lower, upper): scores = sorted(scores) for l, u in zip(lower, upper): s = iter(scores) yield sum(1 for _ in takewhile(lambda x: x <= u, dropwhile(lambda x: x < l, s)))
Since the scores are now already sorted, you could even use bisect to find the right indices to insert the upper and lower limits. The difference between the two indices will give you the number of values in range:
from bisect import bisect_left, bisect_right def graipher2(scores, lower, upper): scores = sorted(scores) for l, u in zip(lower, upper): yield bisect_right(scores, u) - bisect_left(scores, l)
Both functions are generators. You can just call list() on them to consume them into a list, giving the same result as your code:
if __name__ == "__main__": scores= [5, 8, 7] lowerLimits = [3, 7] upperLimits = [9, 7] print(check(scores, lowerLimits, upperLimits)) print(list(graipher(scores, lowerLimits, upperLimits))) print(list(graipher2(scores, lowerLimits, upperLimits)))
Finally, Python has an official style-guide, PEP8, which recommends using lower_case for variables and functions.
When running your function and my two functions on an input of the maximum defined size for scores and a single pair of limits, I get the following timings:
check: 249 ms ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) graipher: 77.3 ms ± 950 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) graipher2: 53.9 ms ± 772 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
When using a scores of length 10 and the maximum defined size for the lengths of the limits, I get:
check: 2.8 s ± 112 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) graipher: 246 ms ± 2.77 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) graipher2: 73.1 ms ± 612 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
And finally, when using the maximum defined size for both scores and the limits, only graipher2 finishes in a reasonable time (I stopped the other ones after a few minutes):
graipher2: 247 ms ± 4.94 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
So, to summarize, sort your scores and use bisection.