将已排序的项目搜索到已排序的序列中
searching sorted items into a sorted sequence
我想在已排序的值数组中找到一系列项目。
我知道使用 numpy 我可以做到:
l = np.searchsorted(values, items)
这具有 O(len(items)*log(len(values))) 的复杂性。
但是,我的项目也已排序,所以我可以在 O(len(items)+len(values)) 中完成:
l = np.zeros(items.size, dtype=np.int32)
k, K = 0, len(values)
for i in range(len(items)):
while k < K and values[k] < items[i]:
k += 1
l[i] = k
问题是这个纯 python 的版本比 searchsorted 慢得多,因为 python 循环,即使对于大的 len(items) 和 len(values) (~10^6 ).
知道如何使用 numpy "vectorize" 这个循环吗?
一些示例数据:
# some example data
np.random.seed(0)
n_values = 1000000
n_items = 100000
values = np.random.rand(n_values)
items = np.random.rand(n_items)
values.sort()
items.sort()
您的原始代码片段以及@PeterE 建议的实现:
def original(values, items):
l = np.empty(items.size, dtype=np.int32)
k, K = 0, len(values)
for i, item in enumerate(items):
while k < K and values[k] < item:
k += 1
l[i] = k
return l
def peter_e(values, items):
l = np.empty(items.size, dtype=np.int32)
last_idx = 0
for i, item in enumerate(items):
last_idx += values[last_idx:].searchsorted(item)
l[i] = last_idx
return l
针对天真的测试正确性np.searchsorted
:
ss = values.searchsorted(items)
print(all(original(values, items) == ss))
# True
print(all(peter_e(values, items) == ss))
# True
时间安排:
In [1]: %timeit original(values, items)
10 loops, best of 3: 115 ms per loop
In [2]: %timeit peter_e(values, items)
10 loops, best of 3: 79.8 ms per loop
In [3]: %timeit values.searchsorted(items)
100 loops, best of 3: 4.09 ms per loop
因此,对于这种大小的输入,天真地使用 np.searchsorted
可以轻而易举地击败您的原始代码以及 PeterE 的建议。
更新
为避免任何可能导致时间偏差的缓存效应,我们可以为基准测试的每次迭代生成一组新的随机输入数组:
In [1]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
original(values, items)
.....:
10 loops, best of 3: 115 ms per loop
In [2]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
peter_e(values, items)
.....:
10 loops, best of 3: 79.9 ms per loop
In [3]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
values.searchsorted(items)
.....:
100 loops, best of 3: 4.08 ms per loop
更新 2
对于 values
和 items
都已排序的情况,编写一个击败 np.searchsorted
的 Cython 函数并不难。
search_doubly_sorted.pyx
:
import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def search_doubly_sorted(values, items):
cdef:
double[:] _values = values.astype(np.double)
double[:] _items = items.astype(np.double)
long n_items = items.shape[0]
long n_values = values.shape[0]
long[:] out = np.empty(n_items, dtype=np.int64)
long ii, jj, last_idx
last_idx = 0
for ii in range(n_items):
for jj in range(last_idx, n_values):
if _items[ii] <= _values[jj]:
break
last_idx = jj
out[ii] = last_idx
return out.base
正确性测试:
In [1]: from search_doubly_sorted import search_doubly_sorted
In [2]: print(all(search_doubly_sorted(values, items) == values.searchsorted(items)))
# True
基准:
In [3]: %timeit values.searchsorted(items)
100 loops, best of 3: 4.07 ms per loop
In [4]: %timeit search_doubly_sorted(values, items)
1000 loops, best of 3: 1.44 ms per loop
不过,性能提升相当有限。除非这是您代码中的严重瓶颈,否则您应该坚持使用 np.searchsorted
.
我想在已排序的值数组中找到一系列项目。 我知道使用 numpy 我可以做到:
l = np.searchsorted(values, items)
这具有 O(len(items)*log(len(values))) 的复杂性。 但是,我的项目也已排序,所以我可以在 O(len(items)+len(values)) 中完成:
l = np.zeros(items.size, dtype=np.int32)
k, K = 0, len(values)
for i in range(len(items)):
while k < K and values[k] < items[i]:
k += 1
l[i] = k
问题是这个纯 python 的版本比 searchsorted 慢得多,因为 python 循环,即使对于大的 len(items) 和 len(values) (~10^6 ).
知道如何使用 numpy "vectorize" 这个循环吗?
一些示例数据:
# some example data
np.random.seed(0)
n_values = 1000000
n_items = 100000
values = np.random.rand(n_values)
items = np.random.rand(n_items)
values.sort()
items.sort()
您的原始代码片段以及@PeterE 建议的实现:
def original(values, items):
l = np.empty(items.size, dtype=np.int32)
k, K = 0, len(values)
for i, item in enumerate(items):
while k < K and values[k] < item:
k += 1
l[i] = k
return l
def peter_e(values, items):
l = np.empty(items.size, dtype=np.int32)
last_idx = 0
for i, item in enumerate(items):
last_idx += values[last_idx:].searchsorted(item)
l[i] = last_idx
return l
针对天真的测试正确性np.searchsorted
:
ss = values.searchsorted(items)
print(all(original(values, items) == ss))
# True
print(all(peter_e(values, items) == ss))
# True
时间安排:
In [1]: %timeit original(values, items)
10 loops, best of 3: 115 ms per loop
In [2]: %timeit peter_e(values, items)
10 loops, best of 3: 79.8 ms per loop
In [3]: %timeit values.searchsorted(items)
100 loops, best of 3: 4.09 ms per loop
因此,对于这种大小的输入,天真地使用 np.searchsorted
可以轻而易举地击败您的原始代码以及 PeterE 的建议。
更新
为避免任何可能导致时间偏差的缓存效应,我们可以为基准测试的每次迭代生成一组新的随机输入数组:
In [1]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
original(values, items)
.....:
10 loops, best of 3: 115 ms per loop
In [2]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
peter_e(values, items)
.....:
10 loops, best of 3: 79.9 ms per loop
In [3]: %%timeit values = np.random.randn(n_values); items = np.random.randn(n_items); values.sort(); items.sort();
values.searchsorted(items)
.....:
100 loops, best of 3: 4.08 ms per loop
更新 2
对于 values
和 items
都已排序的情况,编写一个击败 np.searchsorted
的 Cython 函数并不难。
search_doubly_sorted.pyx
:
import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def search_doubly_sorted(values, items):
cdef:
double[:] _values = values.astype(np.double)
double[:] _items = items.astype(np.double)
long n_items = items.shape[0]
long n_values = values.shape[0]
long[:] out = np.empty(n_items, dtype=np.int64)
long ii, jj, last_idx
last_idx = 0
for ii in range(n_items):
for jj in range(last_idx, n_values):
if _items[ii] <= _values[jj]:
break
last_idx = jj
out[ii] = last_idx
return out.base
正确性测试:
In [1]: from search_doubly_sorted import search_doubly_sorted
In [2]: print(all(search_doubly_sorted(values, items) == values.searchsorted(items)))
# True
基准:
In [3]: %timeit values.searchsorted(items)
100 loops, best of 3: 4.07 ms per loop
In [4]: %timeit search_doubly_sorted(values, items)
1000 loops, best of 3: 1.44 ms per loop
不过,性能提升相当有限。除非这是您代码中的严重瓶颈,否则您应该坚持使用 np.searchsorted
.