Если мы можем предположить, что ряды в scores
появляются в отсортированном порядке, то мы можем использовать bisect.bisect_right
, чтобы найти нужный индекс в O(log(n))
времени. Если len(scores)
велика, это будет быстрее, чем цикл по scores
для каждого значения в ranks
:
import bisect
import operator
scores = [ (500, 1), (450, 3), (400, 6), (300, 7), (225, 9) ]
values, cutoffs = zip(*scores)
ranks = [1, 5, 7, 8]
print([values[bisect.bisect_right(cutoffs, rank)-1] for rank in ranks])
дает
[500, 450, 300, 300]
Для очень больших len(scores)
или len(ranks)
вы получите лучше с использованием NumPy. Эквивалент NumPy bisect.bisect
- np.searchsorted
.Обратите внимание, однако, что np.searchsorted
может принимать целый список (или массив) ranks
в качестве аргумента и возвращать массив индексов, тогда как bisect.bisect
должен вызываться один раз для каждого rank
. Таким образом, проблема может быть решена только с одним вызовом np.searchsorted
:
import numpy as np
scores = np.array([ (500, 1), (450, 3), (400, 6), (300, 7), (225, 9) ])
values, cutoffs = scores[:,0], scores[:,1]
ranks = [1, 5, 7, 8]
print(values[np.searchsorted(cutoffs, ranks, side='right')-1])
# [500, 450, 300, 300]
Ниже приведены некоторые тесты, когда len(scores)
умеренно большой. С учетом этой установки:
import bisect
import numpy as np
scores = [ (500, 1), (450, 3), (400, 6), (300, 7), (225, 9) ]
scores = np.array(scores * 2000).cumsum(axis=0)
values, cutoffs = scores[:,0], scores[:,1]
ranks = cutoffs[::100]
scores_list = list(map(tuple, scores))
ranks_list = list(ranks)
def using_numpy(scores, ranks):
# ranks does not have to be in sorted order
scores = np.asarray(scores)
values, cutoffs = scores[:,0], scores[:,1]
return values[np.searchsorted(cutoffs, ranks, side='right')-1]
def using_bisect(scores, ranks):
# ranks does not have to be in sorted order
values, cutoffs = zip(*scores)
return [values[bisect.bisect_right(cutoffs, rank)-1] for rank in ranks]
def using_reverse(scores, ranks):
# ranks must be sorted for this method to work
ranks.sort()
values = []
i = len(scores) - 1
for rank in reversed(ranks):
while scores[i][1] > rank:
i -= 1
values.append(scores[i][0])
values = values[::-1]
return values
это проверяет, что результаты одинаковы:
for func in (using_reverse, using_bisect):
assert np.allclose(func(scores_list, ranks_list), using_numpy(scores, ranks))
Это показывает using_numpy
гораздо быстрее, чем using_reverse
, если вы передаете массив NumPy в using_numpy
и списков using_reverse
:
In [241]: %timeit using_numpy(scores, ranks)
100000 loops, best of 3: 8.58 µs per loop
In [239]: %timeit using_reverse(scores_list, ranks_list)
1000 loops, best of 3: 827 µs per loop
In [250]: %timeit using_bisect(scores_list, ranks_list)
1000 loops, best of 3: 835 µs per loop
Если вы указали время, необходимое для конвертации списки scores_list
в массив NumPy , то using_numpy
становится медленнее, чем using_reverse
:
In [242]: %timeit using_numpy(scores_list, ranks_list)
100 loops, best of 3: 3.77 ms per loop
Однако, как правило, когда вы используете NumPy, вы строите массивы раз и затем выполнить много вычислений. Таким образом, в то время как преобразование в массивы NumPy составляет дорого, это разовая стоимость, скажем < 4 мс (для примера выше). После , который вы получите, чтобы наслаждаться быстрыми вычислениями на основе NumPy (что в приведенном выше случае почти в 100 раз быстрее). Если ваша программа настолько короткая, что заканчивается за время, которое составляет порядка 4 мс, тогда преобразование в массивы NumPy может быть непомерно дорогостоящим. Однако, если ваша программа занимает значительно больше времени , чем 4 мс (что обычно имеет место, когда производительность имеет значение), то преобразование в массивы NumPy было бы небольшой ценой.
Можете ли вы дать приблизительную оценку для M и N? Это важно, как «M >> N» они есть. –
@StefanPochmann на данный момент, N меньше 10, M может быть 10^3 ~ 10^4. – roger