Эффективное решение для поиска индексов списка больше, чем элементы во втором списке - PullRequest
0 голосов
/ 23 марта 2020

Этот вопрос связан с этим: Первый Python индекс списка больше, чем x?

У меня есть (отсортированный) список с плавающей точкой, и я хочу найти первый индекс который превышает каждое значение второго списка

например,

 l=[0.2,0.3,0.7,0.9]
 m=[0.25,0.6]

, если бы m был float, я бы использовал это:

 bisect.bisect_left(l,m)

Но для случая, где m список потерпел неудачу, и я могу подумать только об использовании списка:

[bisect.bisect_left(l,i) for i in m]

, который дает:

 [1, 2]

, который работает, но я хочу ускорить его для больших списков в моем реальном примере, избегая понимания списка, поскольку мои тесты показали, что это была операция «узкого места» (я ранее говорил, что подозревал, что это слишком медленно). Есть ли способ эффективно сделать это, используя векторизованную функцию, например, numpy или улучшенный алгоритм (поскольку требуется только один обход списка)?

Ответы [ 3 ]

4 голосов
/ 23 марта 2020

Что ж, есть хороший шанс, что bisect_left - это операция O(logN) (бинарный поиск), поэтому ваша общая операция будет O(KlogN) where N relates to size of l, and K relates to size of m.

Если бы также был отсортирован второй список m, Вы можете сделать эту операцию O(N) просто запустив индекс по обоим спискам одновременно.

Но, если ваш комментарий "I подозреваемый это медленный", ваш первый шаг должен всегда для тестирования самого простого решения с наибольшим ожидаемым набором данных. Если это выполнимо, остановитесь прямо здесь! Только если она недостаточна, вы начинаете думать об оптимизации.

Например, рассмотрите следующую программу:

import random
import bisect
haystack = []
for _ in range(1000000):
    haystack.append(random.random())
haystack.sort()
needles = []
for _ in range(10000):
    needles.append(random.random())
result = [bisect.bisect_left(haystack, needle) for needle in needles]
print(result)

Это создаст стог сена в 1 000 000 элементов и список игл из 10 000 элементов, а затем использует ваше понимание списка bisect для выполнения работы. Выполнение этого на моем (не особенно грубом) рабочем столе с time показывает:

real    0m0.738s  # < 3/4 of a second elapsed
user    0m0.578s
sys     0m0.109s

И это включает время, необходимое для создания списков, сортировки большого и распечатки результаты.


Использование timeit для избавления от всего этого времени установки может быть выполнено с помощью:

import timeit
import random
import bisect
haystack = []
for _ in range(1000000):
    haystack.append(random.random())
haystack.sort()
needles = []
for _ in range(10000):
    needles.append(random.random())
print(timeit.timeit('[bisect.bisect_left(haystack, needle) for needle in needles]', setup = 'from __main__ import bisect, haystack, needles', number = 1000))

Вывод этого 12.27 для тысячи итераций Это означает, что вы можете делать это примерно 75 раз в секунду, не потея.

1 голос
/ 23 марта 2020

Вы должны запомнить последнее найденное значение, чтобы использовать его в качестве отправной точки для следующего двоичного поиска, поэтому вместо понимания списка вы должны использовать для l oop:

result = [bisect.bisect_left(l,m[0]),]
for i in m[1:] :
    result.append( bisect.bisect_left(l,i,result[-1]))

Это должно работать быстрее, чем простое понимание.

0 голосов
/ 26 марта 2020

Итак, я обнаружил, что есть функция numpy для выполнения этой задачи, np.searchsorted . что намного быстрее, чем использование списочных представлений.

1. Понимание стандартного списка:

это была моя первая попытка найти решение

python3 -m timeit -s "import numpy as np" -s "import bisect" -s "h=np.sort(np.random.uniform(size=10000))" -s "n=np.sort(np.random.uniform(size=1000))" "r=[bisect.bisect_left(h,i) for i in n]"

200 петель, лучшее из 5: 1,61 мсэ c на л oop

2. Сокращенный поиск для l oop

Это решение было любезно предоставлено @ lenik

python3 -m timeit -s "import numpy as np" -s "import bisect" -s "h=np.sort(np.random.uniform(size=10000))" -s "n=np.sort(np.random.uniform(size=1000))" "r=[bisect.bisect_left(h,n[0])]" "for i in n[1:]:" "    r.append(bisect.bisect_left(h,i,r[-1]))"

200 петель, лучшее из 5: 1,6 мсэ c за л oop

Едва ли отличается от понимания списка, которое меня несколько удивило ...

3. Numpy searchsorted

python3 -m timeit -s "import numpy as np" -s "import bisect" -s "h=np.sort(np.random.uniform(size=10000))" -s "n=np.sort(np.random.uniform(size=1000))" "r=np.searchsorted(h,n)"

10000 петель, лучшее из 5: 33,6 использования c за л oop

Руки быстрее всех.

...