【问题标题】:Fast way to check whether number in list is in given range [duplicate]检查列表中的数字是否在给定范围内的快速方法[重复]
【发布时间】:2018-10-29 15:01:03
【问题描述】:

我有以下方式的字典列表:

list1 = [{'some_id': 1, 'lower_range': 3, 'upper_range': 7},
         {'some_id': 2, 'lower_range': 8, 'upper_range': 12},
         {'some_id': 3, 'lower_range': 13, 'upper_range': 16}]

第二个列表包含一些整数:

list2 = [{'value': 4, 'data': 'A'},
         {'value': 8, 'data': 'B'},
         {'value': 9, 'data': 'C'},
         {'value': 15, 'data': 'D'}]

我现在想在新列表中加入 'some_id''data' 以使 'value' 介于 'lower_range''upper_range' 之间。即,我希望输出为

list3 = [{'some_id': 1, 'data': 'A'},
         {'some_id': 2, 'data': 'B'},
         {'some_id': 2, 'data': 'C'},
         {'some_id': 3, 'data': 'D'}]

一种方法是

list3 = []
for i in list1:
    for j in list2:
        if (j['value'] >= i['lower_range'] and
            j['value'] <= i['upper_range']):
            list3.append({'some_id': i['some_id'], 'data': j['data']})

但是,这似乎非常低效。有更快的方法吗?

【问题讨论】:

  • list1 中的范围重叠时会发生什么?
  • 最好它会抛出一个错误,因为它不应该是可能的
  • list2 是否正确?目前这是一个有字典的列表。
  • 每个人都只有一场比赛吗?还是可以进行零/多个匹配?
  • @Reut Sharabani:应该不可能有多个匹配项。而且很可能为零也不可能。

标签: python python-3.x


【解决方案1】:

有一个特殊的前提是范围不重叠。 所以我们可以通过搜索满足条件的最大lower_bound的元素来找到候选者。

二分查找可以将复杂度从O(n*n) 降低到O(n log n)。 在python3中,我们可以使用bisect。

list1 = [{'some_id': 1, 'lower_range': 3, 'upper_range': 7},
         {'some_id': 2, 'lower_range': 8, 'upper_range': 12},
         {'some_id': 3, 'lower_range': 13, 'upper_range': 16}]

list2 = [{'value': 4, 'data': 'A'},
         {'value': 8, 'data': 'B'},
         {'value': 9, 'data': 'C'},
         {'value': 15, 'data': 'D'}]

list3 = []

list1.sort(key = lambda r: r['lower_range'])
lower_ranges = [r['lower_range'] for r in list1]

from bisect import bisect_right

for record in list2:
    position = bisect_right(lower_ranges, record['value']) - 1
    if (position < 0): continue
    candidate = list1[position]
    if (record['value'] <= candidate['upper_range']):
        list3.append({'some_id': candidate['some_id'], 'data': record['data']})

print(list3)

输出(手动缩进)

[{'some_id': 1, 'data': 'A'},
 {'some_id': 2, 'data': 'B'},
 {'some_id': 2, 'data': 'C'},
 {'some_id': 3, 'data': 'D'}]

【讨论】:

  • 这是最快的解决方案,比 Reut Sharabani 的答案略快。
【解决方案2】:

这有点冗长,但由于排序应该更有效(O(nlogn) O(n^2))(您也可以使用list.sort就地排序):

#!/usr/bin/env python
from operator import itemgetter

list1 = [{'some_id': 1, 'lower_range': 3, 'upper_range': 7},
        {'some_id': 2, 'lower_range': 8, 'upper_range': 12},
        {'some_id': 3, 'lower_range': 13, 'upper_range': 16}]

list2 = [{'value': 4, 'data': 'A'},
        {'value': 8, 'data': 'B'},
        {'value': 9, 'data': 'C'},
        {'value': 15, 'data': 'D'}]

# sort before merging so we iterate less (O(nlogn))
list1 = sorted(list1, key=itemgetter('lower_range'))
list2 = sorted(list2, key=itemgetter('value'))


it1 = iter(list1)
it2 = iter(list2)

# merge lists that we know are sorted (simple merging algorithm - O(n))
try:
    curr_range = next(it1)
    curr_val = next(it2)
    list3 = []
    while True:
        rng = range(curr_range['lower_range'], curr_range['upper_range'] + 1)
        value = curr_val['value']
        if value in rng:
            # got a match, add it and check if there are more values
            list3.append({'some_id': curr_range['some_id'],
                          'data': curr_val['data']})
            curr_val = next(it2)
            continue
        if value < curr_range['lower_range']:
            # no match, skip to next value
            curr_val = next(it2)
            continue
        if value >= curr_range['upper_range']:
            # range too low for value, try next one
            curr_range = next(it1)
            continue
except StopIteration:
    pass
print(list3)

给予:

[{'data': 'A', 'some_id': 1},
 {'data': 'B', 'some_id': 2},
 {'data': 'C', 'some_id': 2},
 {'data': 'D', 'some_id': 3}]

【讨论】:

  • 你是对的!在 OP 的代码中错过了这一点。
  • 如果你写like this,你可以缩短代码。
  • 这段代码比我在 OP 中做的快 50 倍左右
【解决方案3】:

您可以创建一个将值映射到像{3: 1, 4: 1, 5: 1, ..., 8: 2, 9: 2, ...} 这样的 id 的 dict,这样您就可以在恒定的 O(1) 时间内找到每个 dict 的 id:

# create a dict that maps values to ids
value_to_id_dict = {}
for dic in list1:
    id_ = dic['some_id']
    for value in range(dic['lower_range'], dic['upper_range']+1):
        value_to_id_dict[value] = id_

# look up each dict's id in the dict we just created
list3 = []
for dic in list2:
    new_dic = {'data': dic['data'],
               'some_id': value_to_id_dict[dic['value']]}
    list3.append(new_dic)

# result:
# [{'data': 'A', 'some_id': 1},
#  {'data': 'B', 'some_id': 2},
#  {'data': 'C', 'some_id': 2},
#  {'data': 'D', 'some_id': 3}]

【讨论】:

  • 需要注意的是,这将为 {'lower_range': 0, 'upper_range': 1000000} 创建约 100000 个条目 ...
  • 没有看到 +1 :P 添加了一个近似值所以我们不挑剔:D
  • 这段代码比我在 OP 中做的快 25 倍左右
猜你喜欢
  • 2017-03-28
  • 2017-05-16
  • 1970-01-01
  • 1970-01-01
  • 2021-04-01
  • 2014-05-05
  • 2014-12-05
  • 2017-06-08
  • 2021-03-15
相关资源
最近更新 更多