Python / Numpy - быстро найти индекс в массиве, ближайшем к некоторому значению - PullRequest
7 голосов
/ 20 мая 2011

У меня есть массив значений t, который всегда в возрастающем порядке (но не всегда равномерно разнесен).У меня есть еще одно значение, х.Мне нужно найти индекс в t, чтобы t [index] был ближе всего к x.Функция должна возвращать ноль для x t.max ().

Я написал две функции для этого.Первый, f1, НАМНОГО быстрее в этом простом тесте синхронизации.Но мне нравится, что вторая - всего одна строка.Это вычисление будет выполнено для большого массива, потенциально много раз в секунду.

Может кто-нибудь придумать какую-нибудь другую функцию со сравнимой по времени синхронизацией с первой, но с более чистым кодом?Как насчет чего-то быстрее первого (скорость важнее всего)?

Спасибо!

Код:

import numpy as np
import timeit

t = np.arange(10,100000)         # Not always uniform, but in increasing order
x = np.random.uniform(10,100000) # Some value to find within t

def f1(t, x):
   ind = np.searchsorted(t, x)   # Get index to preserve order
   ind = min(len(t)-1, ind)      # In case x > max(t)
   ind = max(1, ind)             # In case x < min(t)
   if x < (t[ind-1] + t[ind]) / 2.0:   # Closer to the smaller number
      ind = ind-1
   return ind

def f2(t, x):
   return np.abs(t-x).argmin()

print t,           '\n', x,           '\n'
print f1(t, x),    '\n', f2(t, x),    '\n'
print t[f1(t, x)], '\n', t[f2(t, x)], '\n'

runs = 1000
time = timeit.Timer('f1(t, x)', 'from __main__ import f1, t, x')
print round(time.timeit(runs), 6)

time = timeit.Timer('f2(t, x)', 'from __main__ import f2, t, x')
print round(time.timeit(runs), 6)

Ответы [ 3 ]

7 голосов
/ 20 мая 2011

Это кажется намного быстрее (для меня Python 3.2-win32, numpy 1.6.0):

from bisect import bisect_left
def f3(t, x):
    i = bisect_left(t, x)
    if t[i] - x > 0.5:
        i-=1
    return i

Выход:

[   10    11    12 ..., 99997 99998 99999]
37854.22200356027
37844
37844
37844
37854
37854
37854
f1 0.332725
f2 1.387974
f3 0.085864
1 голос
/ 20 мая 2011

np.searchsorted - бинарный поиск (каждый раз разбивать массив пополам). Таким образом, вы должны реализовать его так, чтобы оно возвращало последнее значение, меньшее x, а не ноль.

Посмотрите на этот алгоритм (из здесь ):

def binary_search(a, x):
    lo=0
    hi = len(a)
    while lo < hi:
        mid = (lo+hi)//2
        midval = a[mid]
        if midval < x:
            lo = mid+1
        elif midval > x: 
            hi = mid
        else:
            return mid
    return lo-1 if lo > 0 else 0

только что заменил последнюю строку (было return -1). Также изменены аргументы.

Поскольку циклы написаны на Python, он может быть медленнее, чем первый ... (не тестируется)

1 голос
/ 20 мая 2011

Использовать поисковый запрос:

t = np.arange(10,100000)         # Not always uniform, but in increasing order
x = np.random.uniform(10,100000)

print t.searchsorted(x)

Edit:

Ах да, я вижу, что вы делаете в f1. Может быть, ниже f3 легче читать, чем f1.

def f3(t, x):
    ind = t.searchsorted(x)
    if ind == len(t):
        return ind - 1 # x > max(t)
    elif ind == 0:
        return 0
    before = ind-1
    if x-t[before] < t[ind]-x:
        ind -= 1
    return ind
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...