簡體   English   中英

使用Python中的列表和字典進行數值比較,優化循環效率

[英]Optimizing efficiency of loops with numeric comparison using list and dictionary in Python

我有一個數字為整數的列表: candidates = [1, 2 ,3, 4 , 5, 16, 20] 此列表可包含> 100萬個項目。

我有一個字典number_ranges ,其鍵為整數,列表為包含最小和最大范圍的對象的值。 這本字典現在包含大約500k鍵。

{
    {5: [{"start": 0, "end": 9}]},
    {16: [{"start": 15, "end": 20}, {"start": 16, "end": 18}]}
}

我現在循環遍歷列表:

for candidate in candidates:
    number = search_in_range(candidate, number_ranges)

在那里我檢查number_ranges范圍內是否有多個candidatesnumber_ranges ,如果是,我將返回將進一步使用的密鑰。

def search_in_range(candidate, number_ranges):
    for number_range_key in number_ranges:
        for number in number_ranges[number_range_key]:
            if int(number['start']) <= candidate <= int(number['end']):
                return {"key": number_range_key, "candidate": candidate}

當我運行它時,我發現從列表中處理1000個數字大約需要40秒。 這意味着如果我有100萬個數字,我需要超過11個小時來處理。

('2018-12-19 16:22:47', 'Read', 1000)
('2018-12-19 16:23:30', 'Read', 2000)
('2018-12-19 16:24:10', 'Read', 3000)
('2018-12-19 16:24:46', 'Read', 4000)
('2018-12-19 16:25:26', 'Read', 5000)
('2018-12-19 16:25:59', 'Read', 6000)
('2018-12-19 16:26:39', 'Read', 7000)
('2018-12-19 16:27:28', 'Read', 8000)
('2018-12-19 16:28:15', 'Read', 9000)
('2018-12-19 16:28:57', 'Read', 10000)

預期輸出返回來自在該范圍內匹配的number_ranges的密鑰和用於找到該密鑰的candidate編號,即在函數search_in_range return {"key": number_range_key, "candidate": candidate}

Python優化此算法的推薦方法是什么?

您的candidates列表已經過排序,反之亦然:在number_ranges循環字典並使用bisect對匹配的候選項進行二進制搜索。 這將使n字典, m候選者和k匹配候選者的平均復雜度從O(n*m)O(n*logm*k)

(注意:我將number_ranges的格式從一set dict改為每個只有一個元素,只是一個dict ,這更有意義。)

candidates = [1, 2, 3, 4, 5, 16, 20]
number_ranges = {
    5: [{"start": 0, "end": 9}],
    16: [{"start": 15, "end": 20}, {"start": 16, "end": 18}]
}

import bisect

for key, values in number_ranges.items():
    for value in values:
        start, end = value["start"], value["end"]
        lower = bisect.bisect_left(candidates, start)
        upper = bisect.bisect_right(candidates, end)
        for cand in range(lower, upper):
            res = {"key": key, "candidate": candidates[cand]}
            print(value, res)

輸出:

{'start': 0, 'end': 9} {'key': 5, 'candidate': 1}
{'start': 0, 'end': 9} {'key': 5, 'candidate': 2}
{'start': 0, 'end': 9} {'key': 5, 'candidate': 3}
{'start': 0, 'end': 9} {'key': 5, 'candidate': 4}
{'start': 0, 'end': 9} {'key': 5, 'candidate': 5}
{'start': 15, 'end': 20} {'key': 16, 'candidate': 16}
{'start': 15, 'end': 20} {'key': 16, 'candidate': 20}
{'start': 16, 'end': 18} {'key': 16, 'candidate': 16}

如果candidates沒有按實際排序,或者您希望結果按候選人而不是字典排序,則可以將其排序為預處理或后處理步驟。

通過一些重組,您的代碼成為典型的間隔樹問題。

看看這個包https://pypi.org/project/intervaltree/

與普通間隔樹的唯一區別在於,您有一些項目覆蓋多個區間,但是將它們分成單獨的區間很容易,例如{16.1:{“start”:15,“end”:20}, 16.2:{“start”:16,“end”:18}}

通過使用intervaltree包,創建了一個平衡的二叉搜索樹,它比使用嵌套的for循環更有效。 該解決方案是用於搜索每個候選者的O(logn),而for循環是O(n)。 如果有1MM +候選者,則intervaltree包將比接受的嵌套for循環回答快得多。

即使這個問題有一個公認的答案,我也會為了其他人的緣故添加這種情況,這種方法確實有助於創建反向查找。 這是一次頭痛,隨着候選人名單的增長,這將節省大量的實際時間。 字典查找是O(1),如果需要執行多次查找,還應考慮創建反向映射。

number_ranges = [
    {5: [{"start": 0, "end": 9}]},
    {16: [{"start": 15, "end": 20}, {"start": 16, "end": 18}]}
]

from collections import defaultdict

reversed_number_ranges = defaultdict(set) #returns an empty set, avoiding key errors.


for number in number_ranges:
    for k,v in number.items(): 
        ranges = set() #create a set of values which fall within range
        for range_dict in v:
            ranges.update(range(range_dict["start"], range_dict["end"] + 1)) #assuming "end" is included. remove the +1 for right exclusive.
        for i in ranges:
            reversed_number_ranges[i].add(k) #add the key for each location in a range.


candidates = [1, 2 ,3, 4 , 5, 16, 20]

for candidate in candidates:
    print(candidate, reversed_number_ranges[candidate])

輸出:

1 {5}
2 {5}
3 {5}
4 {5}
5 {5}
16 {16}
20 {16}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM