[英]Efficient solution to find list indices greater than elements in a second list
這個問題與此相關: 第一個 Python 列表索引大於 x?
我有一個(排序的)浮點數列表,我想找到超過第二個列表的每個值的第一個索引
例如
l=[0.2,0.3,0.7,0.9]
m=[0.25,0.6]
如果 m 是一個浮點數,我會使用這個:
bisect.bisect_left(l,m)
但是對於 m 是列表的情況,這失敗了,我只能考慮使用列表理解:
[bisect.bisect_left(l,i) for i in m]
這使:
[1, 2]
這是有效的,但我想在我的真實示例中通過避免列表理解來加快大型列表的速度,因為我的測試表明這是“瓶頸”操作(我之前說過我懷疑它太慢了)。 有沒有一種方法可以使用例如 numpy 中的矢量化函數或改進的算法(因為只需要遍歷一次列表)來有效地做到這一點?
好吧, bisect_left
很有可能是O(logN)
操作(二元搜索),因此您的整體操作將是O(KlogN) where N relates to size of l, and K relates to size of m
。
如果第二個列表m
排序了,您可以簡單地通過同時運行兩個列表的索引來使其成為O(N)
操作。
但是,對於您的“我懷疑這很慢”的評論,您的第一步應該始終是使用最大的預期數據集測試最簡單的解決方案。 如果這可行,就停在那里! 只有當它有缺陷時,你才會開始考慮優化。
例如,考慮以下程序:
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 次而不會出汗。
您必須記住找到的最后一個值以將其用作下一次二分搜索的起點,因此您必須使用 for 循環而不是列表理解:
result = [bisect.bisect_left(l,m[0]),]
for i in m[1:] :
result.append( bisect.bisect_left(l,i,result[-1]))
這應該比簡單的理解更快。
所以我發現有一個 numpy 函數來執行這個任務, np.searchsorted 。 這比使用列表推導式要快得多。
result=np.searchsorted(searchlist,newindices)
這些是各種解決方案的時間安排:
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 毫秒
2. 縮短了 for 循環中的搜索
這是@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 毫秒
與列表理解幾乎沒有什么不同,我對此感到有些驚訝......
3. Numpy 搜索排序
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 微秒
比本示例中基於列表理解的解決方案快大約 50 倍,因此是最快的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.