Я столкнулся с той же проблемой и нашел другое решение. Вместо этого вы можете обрабатывать данные из нескольких столбцов как отдельные записи, используя структурированный тип данных. Структурированный тип данных позволит использовать сортировку / сортировку данных (вместо lexsort, хотя на этой стадии сортировка выполняется быстрее), а затем использовать стандартную сортировку поиска. Вот пример:
import numpy as np
from itertools import repeat
# Setup our input data
# Every row is an entry, every column what we want to sort by
# Unlike lexsort, this takes columns in decreasing priority, not increasing
a = np.array([1,1,1,2,2,3,5,6,6])
b = np.array([10,20,30,5,10,100,10,30,40])
data = np.transpose([a,b])
# Sort the data
data = data[np.lexsort(data.T[::-1])]
# Convert to a structured data-type
dt = np.dtype(zip(repeat(''), repeat(data.dtype, data.shape[1]))) # the structured dtype
data = np.ascontiguousarray(data).view(dt).squeeze(-1) # the dtype change leaves a trailing 1 dimension, ascontinguousarray is required for the dtype change
# You can also first convert to the structured data-type with the two lines above then use data.sort()/data.argsort()/np.sort(data)
# Search the data
values = np.array([(2,7),(5,150)], dtype=dt) # note: when using structured data types the rows must be a tuple
pos = np.searchsorted(data, values)
# pos is (4,7) in this example, exactly what you would want
Это работает для любого количества столбцов, использует встроенные функции numpy, столбцы остаются в «логическом» порядке (с уменьшением приоритета), и это должно быть довольно быстро.
A сравнил по времени два метода на основе numpy.
# 1 - рекурсивный метод из @ j0ker5 (тот, что ниже, расширяет его пример с предложением рекурсии и работает с любым количеством строк, отсортированных по лексической сортировке)
# 2 - это структурированный массив от меня
Они оба используют одни и те же входные данные, в основном как searchsorted
, за исключением того, что a
и v
соответствуют lexsort
.
import numpy as np
def lexsearch1(a, v, side='left', sorter=None):
def _recurse(a, v):
if a.shape[1] == 0: return 0
if a.shape[0] == 1: return a.squeeze(0).searchsorted(v.squeeze(0), side)
bl = np.searchsorted(a[-1,:], v[-1], side='left')
br = np.searchsorted(a[-1,:], v[-1], side='right')
return bl + _recurse(a[:-1,bl:br], v[:-1])
a,v = np.asarray(a), np.asarray(v)
if v.ndim == 1: v = v[:,np.newaxis]
assert a.ndim == 2 and v.ndim == 2 and a.shape[0] == v.shape[0] and a.shape[0] > 1
if sorter is not None: a = a[:,sorter]
bl = np.searchsorted(a[-1,:], v[-1,:], side='left')
br = np.searchsorted(a[-1,:], v[-1,:], side='right')
for i in xrange(len(bl)): bl[i] += _recurse(a[:-1,bl[i]:br[i]], v[:-1,i])
return bl
def lexsearch2(a, v, side='left', sorter=None):
from itertools import repeat
a,v = np.asarray(a), np.asarray(v)
if v.ndim == 1: v = v[:,np.newaxis]
assert a.ndim == 2 and v.ndim == 2 and a.shape[0] == v.shape[0] and a.shape[0] > 1
a_dt = np.dtype(zip(repeat(''), repeat(a.dtype, a.shape[0])))
v_dt = np.dtype(zip(a_dt.names, repeat(v.dtype, a.shape[0])))
a = np.asfortranarray(a[::-1,:]).view(a_dt).squeeze(0)
v = np.asfortranarray(v[::-1,:]).view(v_dt).squeeze(0)
return a.searchsorted(v, side, sorter).ravel()
a = np.random.randint(100, size=(2,10000)) # Values to sort, rows in increasing priority
v = np.random.randint(100, size=(2,10000)) # Values to search for, rows in increasing priority
sorted_idx = np.lexsort(a)
a_sorted = a[:,sorted_idx]
И результаты синхронизации (в iPython):
# 2 rows
%timeit lexsearch1(a_sorted, v)
10 loops, best of 3: 33.4 ms per loop
%timeit lexsearch2(a_sorted, v)
100 loops, best of 3: 14 ms per loop
# 10 rows
%timeit lexsearch1(a_sorted, v)
10 loops, best of 3: 103 ms per loop
%timeit lexsearch2(a_sorted, v)
100 loops, best of 3: 14.7 ms per loop
В целом подход к структурированным массивам быстрее и его можно сделать еще быстрее, если вы разработаете его для работы с перевернутыми и транспонированными версиями a
и v
. Он становится еще быстрее, так как количество строк / ключей увеличивается, а при переходе от 2 к 10 строкам едва замедляется.
Я не заметил какой-либо значительной разницы во времени между использованием a_sorted
или a и sorter=sorted_idx
, поэтому я оставил их для ясности.
Я считаю, что с помощью Cython можно создать действительно быстрый метод, но это так же быстро, как и с чистым Python и numpy.