【问题标题】:Inteligent loop over dictionnary python字典python上的智能循环
【发布时间】:2020-01-22 11:01:40
【问题描述】:

我有一个看起来像的字典

position_dictionnary = {(0,0): <PositionObject1>, (0,1): <PositionObject2>, ...  (x,y): <PositionObjectn>}

我有一个函数需要返回给定位置周围特定范围内的 PositionObject 列表。现在我有这段代码对我来说很好用:

def getSurrounding(center_position, range, position_dictionnary):
    for x,y in position_dictionnary:
         if abs(center_position.x - x) + abs(center_position.y - y) < range:
             yield position_dictionnary[(x,y)]

但是当字典变大时,它变得太长了,所以我问是否有一种方法可以直接循环遍历字典的正确索引或另一种我看不到的让它运行​​得更快的方法。 (如果解决方案不是很好的实践证明,但速度更快,也可以)

【问题讨论】:

  • 二维列表会更好吗?我想如果 x 和 y 总是整数会起作用
  • 如果键非常稀疏,可能会占用太多内存。可能值得一看stackoverflow.com/questions/6179635/…
  • 根据坐标创建有序字典。在键中进行二分搜索,并以此范围为条件向两个方向扩展。
  • @AlbinPaul 您会在这些键中搜索什么?我也在考虑对元素进行排序,但不清楚您要搜索什么。
  • @JeffH 搜索起来会有点棘手。我正在考虑测量x - centerposition.x + y - centerposition.y 作为条件来搜索该值是否小于该范围,我们可以取另一半。如果它满足我们停止和扩展双方的范围。

标签: python dictionary optimization


【解决方案1】:

您可以通过对 x 和 y 坐标进行双重排序在亚线性时间内完成此操作。当然,这假设点字典是相当静态的,因为添加或移动事物显然有(很小的)成本。

下面涉及使用多个排序容器,因此对getSurrounding 的每个查询都将调用 4 次二等分搜索,仍然是 O(log(N))。额外的好处是,这应该适用于位置的float 值,但我还没有真正考虑过。

from sortedcollections import SortedDict, SortedList

pos_dict = {(0,0): 'a',
            (1,2): 'b',
            (1,3): 'c',
            (2,2): 'd',
            (2,6): 'e',
            (3,2): 'f',
            (4,4): 'g',
            (5,5): 'h'}

sd = SortedDict()

# put everything into a sorted dictionary
for x, y in pos_dict:
    temp = sd.get(x, SortedList())
    temp.add(y)
    sd[x] = temp

# we look for candidate x values in the sorted dictionary 
# and be clever with the Manhattan
# distance to find the y values

rng = 2
ctr = (2,2)
points_in_range = []

left_x = ctr[0] - rng
right_x = ctr[0] + rng

low_y = ctr[1] - rng
hi_y = ctr[1] + rng

x_vals = sd.keys()[sd.bisect(left_x) : sd.bisect(right_x)]

# get y values within the remaining Manhattan distance and construct tuples from (x, y)
for x in x_vals:
    temp = sd.get(x)
    low_y_lim = low_y + abs(x-ctr[0])
    hi_y_lim = hi_y - abs(x-ctr[0])
    y_vals = temp[temp.bisect(low_y_lim) : temp.bisect(hi_y_lim)]
    points_in_range.extend([(x, y) for y in y_vals])

print(points_in_range)

for p in points_in_range:
    print(f'{pos_dict.get(p)} is in range at location {p}')

产量:

[(1, 2), (1, 3), (2, 2), (3, 2)]
b is in range at location (1, 2)
c is in range at location (1, 3)
d is in range at location (2, 2)
f is in range at location (3, 2)

其他概念...

根据字典的大小和范围的值,您可能能够接近线性时间。如果 rng 的值很小,那么制作一组“在范围内”的点并将其与字典中的一组键相交非常容易,并且如果 rng 是“小”,则接近线性。

set_of_in_rng = {(x,y)      for x in range(ctr[0]-rng, ctr[0]+rng+1) 
                            for y in range(low_y+abs(x-2), hi_y-abs(x-2)+1)}

points_in_range = set_of_in_rng.intersection(set(pos_dict.keys()))

for p in points_in_range:
    print(f'{pos_dict.get(p)} is in range at location {p}')

产生相同的结果:

b is in range at location (1, 2)
f is in range at location (3, 2)
c is in range at location (1, 3)
d is in range at location (2, 2)

【讨论】:

    【解决方案2】:

    首先,不要掩盖内置关键字range。在同一范围内,您将无法访问内置对象。幸运的是,在这种情况下,您只在函数范围内隐藏了它。但是,如果您在全局范围内执行此操作,则会自找不必要的麻烦。


    现在,谈谈你的问题。不要遍历整个字典,而是使用range 作为主要条件,从字典中检索与您的条件匹配的键。它会明显更小。例如:

    def getSurrounding(center_position, rng, position_dictionary):
        for x in range(-rng, rng):
            for y in range(-rng, rng):
    
                # First, check if the dictionary returns the coord
                position = position_dictionary.get((x, y))
    
                # If the position was retrieved, yield it back.
                if position:
                    yield position
    

    使用此方法,假设您的字典大小为 50 x 50 坐标(总共 2500),并且您想检查距离中心位置 10 半径的范围,您只需迭代 20 x 20(总共 400)次而不是 2500 次。

    当然,在某个时刻,当您的rng 大于半径 25 时,您可能只想改用现有方法。

    【讨论】:

    • 这假设位置是严格的整数,这不是明确的,但可能是准确的此外,如果对象的字段是稀疏的并且搜索范围很大,这可能会变得非常低效。如果范围是 10,你将执行 400 gets 可能只找到一两个点,所以这里有一些基于参数的交易空间,我认为
    • @JeffH 完全正确 - 我添加了我的编辑以指出相同的内容。最终它会达到原始方法更有效的地步。
    • 您的方法是 O(n^2),而 OP 解决方案是 O(n) 时间复杂度,刚刚在 position_dictionnary 中使用 1000 个项目进行了测试,在最坏的情况下慢了 +1000 倍
    • @rusu_ro1 我知道,在某个阈值下,原始解决方案的收益将超过原始解决方案。也就是说,这里讨论的n 是不同的。 n=2500 的 O(n) 方法仍然比 n=20 的 O(n^2) 方法慢。正如我所说,这取决于用例,我只是假设range 将比原始集小得多。
    猜你喜欢
    • 2016-06-01
    • 1970-01-01
    • 2013-11-12
    • 2018-02-22
    • 2014-02-14
    • 2018-12-04
    • 2014-06-02
    • 2017-12-04
    • 1970-01-01
    相关资源
    最近更新 更多