Reputation: 172
I'm trying to find the fastest way to return the index of a given element in a sorted array. np.searchsorted()
seems to be specifically designed for the job. It uses binary search, which should be faster than the generic np.where()
, which I'm assuming needs to loop through the entire array in O(N). However to my surprise, np.searchsorted()
takes the same amount of time as np.where()
, even for large arrays. Here's a MWE:
t_where, t_searchsorted = 0, 0
a = sorted(np.random.uniform(0, 1, 10000)) # sorted array
for i in range(1000):
val = np.random.choice(a) # randomly select value from array
t = time.time()
idx_1 = np.where(a == val)[0][0]
t_where += (time.time()-t)
t = time.time()
idx_2 = np.searchsorted(a, val)
t_searchsorted += (time.time()-t)
print(t_where) # 0.804 s
print(t_searchsorted) # 0.793 s
EDIT: As @Warren Weckesser explained in his answer below, the issue was that sorted()
would convert my numpy array into a python list, and np.searchsorted()
was converting it back into a numpy array in O(N). For those who might be wondering, the equivalent of np.searchsorted()
for python lists is bisect.bisect_left()
. Here's a quick benchmark (reproducing the experiment above):
If a
is a (sorted) python list, and val
is in the list:
idx = a.index(val) # takes 100.0 ms
idx = bisect.bisect_left(a, val) # takes 1.4 ms
If a
is a (sorted) numpy array:
idx = np.where(a, val) # takes 12.1 ms
idx = np.searchsorted(a, val) # takes 4.5 ms
Upvotes: 2
Views: 2004
Reputation: 114791
You used the Python builtin function sorted
to sort the random values, so a
is a Python list, not a NumPy array. Internally, searchsorted
will create a NumPy array from that list, which means copying the list, so the actually complexity is O(n) instead of O(log(n)). The same is true for using np.where
: the comparison of the Python list to a numpy.float64
value will end up converting the list to a NumPy array first.
If you change the creation of a
to something like
a = np.random.uniform(0, 1, 10000)
a.sort()
you'll get times that are much faster, with relative values that reflect the difference between using where
and searchsorted
.
Here's the modified version of your script, followed by its output.
import time
import numpy as np
t_where, t_searchsorted = 0, 0
a = np.random.uniform(0, 1, 10000)
a.sort()
for i in range(1000):
val = np.random.choice(a) # randomly select value from array
t = time.time()
idx_1 = np.where(a == val)[0][0]
t_where += (time.time()-t)
t = time.time()
idx_2 = np.searchsorted(a, val)
t_searchsorted += (time.time()-t)
print(t_where)
print(t_searchsorted)
Output:
0.00879216194152832
0.0038056373596191406
Upvotes: 3