【问题标题】:Start a dictionary for loop at a specific key value在特定键值处启动字典 for 循环
【发布时间】:2015-09-15 08:25:18
【问题描述】:

代码如下:

编辑**** 请不要再“使用无序的字典回复是不可能的”。我几乎已经知道了。我发表这篇文章是因为它可能是可能的,或者有人有一个可行的想法。

#position equals some set of two dimensional coords
for name in self.regions["regions"]:  # I want to start the iteration with 'last_region'
    # I don't want to run these next two lines over every dictionary key each time since the likelihood is that the new
    # position is still within the last region that was matched.
    rect = (self.regions["regions"][name]["pos1"], self.regions["regions"][name]["pos2"])
    if all(self.point_inside(rect, position)):
        # record the name of this region in variable- 'last_region' so I can start with it on the next search...
        # other code I want to run when I get a match
        return
return # if code gets here, the points were not inside any of the named regions

希望代码中的 cmets 能很好地解释我的情况。假设我最后在区域“delta”内(即,键名是 delta,值将是定义其边界的坐标集)并且我还有 500 个区域。我第一次发现自己在区域 delta 中时,代码可能直到(假设)第 389 次迭代才发现这一点……所以它在发现之前进行了 388 次all(self.point_inside(rect, position)) 计算。由于下次运行时我可能仍处于 delta 中(但每次代码运行时我都必须验证),如果键“delta”是第一个被 for 循环检查的键,将会很有帮助。

这个特定的代码每秒可以为许多不同的用户运行多次。所以速度至关重要。设计是这样的,用户不会在一个区域中,并且所有 500 条记录可能需要循环通过并且将在没有匹配项的情况下退出循环,但我想通过加速整个程序来加速它那些目前在其中一个地区的人。

我不希望以任何特定顺序对字典进行排序等额外开销。我只希望它从成功匹配的最后一个开始查找all(self.point_inside(rect, position))

也许这会有所帮助。以下是我正在使用的字典(仅显示第一条记录),因此您可以看到我在上面编码的结构...是的,尽管名称为“rect”代码,它实际上检查立方体区域中的点。

{"regions": {"shop": {"flgs": {"breakprot": true, "placeprot": true}, "dim": 0, "placeplayers": {"4f953255-6775-4dc6- a612-fb4230588eff": "SurestTexas00"}, "breakplayers": {"4f953255-6775-4dc6-a612-fb4230588eff": "SurestTexas00"}, "protected": true, "banplayers": {}, "pos1": [ 5120025, 60, 5120208], "pos2": [5120062, 73, 5120257], "ownerUuid": "4f953255-6775-4dc6-a612-fb4230588eff", "accessplayers": {"4f953255-6774-2056-a612-b ": "SurestTexas00"}},更多,更多,更多...}

【问题讨论】:

  • 字典是任意排序的。如果您想要某种缓存或优化行为,我认为您需要超越内置类型和函数。
  • 我知道它们是任意订购的……这就是我提出问题的原因。我知道我可以直接通过 self.regions["regions"]["""whatever last region is"""] 直接访问可能的可能性,然后如果不匹配就检查整个字典,但它会如果它可以从可能的第一个开始,则更简单(因为它们是任意的)。我怀疑任何优化或缓存都不会那么有用,因为多个用户同时访问相同的数据......除了让这一切变得更加复杂之外。
  • 你的 thing 能否有一个属性来指示它所在的最后一个区域 - self.where_i_was_lastself.region_i_was_in_last_time_i_looked??
  • # record the name of this region in variable- 'last_region' so I can start with it on the next search... 行是(对于该用户)我将记录他们所在区域的位置...由于每个人都说不可能从特定键开始,我不知道如何做类似的事情:```搜索(lastkey):找到它:Do_foo_code Dangit:For Loop All:搜索(All):Do_foo_code```
  • dangit 因为无法将其格式化为代码... :(

标签: python performance python-2.7 for-loop dictionary


【解决方案1】:

您可以尝试在dict 的自定义子类中实现一些缓存机制。

您可以在__init__ 中设置self._cache = None,添加类似set_cache(self, key) 的方法来设置缓存,最后在调用默认__iter__ 之前将__iter__ 覆盖为yield self._cache

但是,如果您考虑this stackoverflow answerthis one,这可能有点麻烦。

对于您的问题中所写的内容,我会尝试在您的代码中实现此缓存逻辑。

def _match_region(self, name, position):
    rect = (self.regions["regions"][name]["pos1"], self.regions["regions"][name]["pos2"])
    return all(self.point_inside(rect, position))


if self.last_region and self._match_region(self.last_region, position):
    self.code_to_run_when_match(position)
    return

for name in self.regions["regions"]:
    if self._match_region(name, position):
        self.last_region = name
        self.code_to_run_when_match(position)
        return
return # if code gets here, the points were not inside any of the named regions

【讨论】:

  • if self.last_region and self._match_region(self.last_region, position): 语句中的第一个条件if self.last_region 的用途是什么?我的代码会将其定义为 None 除非它找到以前的匹配项。这只是确保它有一个值吗?
  • 是的,没错。您想在开始和没有匹配时(如果发生这种情况)将其初始化/设置为 None
【解决方案2】:

没错,字典是无序类型。因此,OrderedDict 对您想做的事情没有多大帮助。

您可以将最后一个区域存储到您的类中。然后,在下一次调用时,在检查整个字典之前检查最后一个区域是否仍然有效?

【讨论】:

  • 这就是我想要做的。我将已经知道最后一场比赛......问题在于弄清楚如何让它首先检查。
【解决方案3】:

您可以直接使用迭代器来代替 for 循环。这是一个示例函数,它使用迭代器执行类似于您想要的操作:

def iterate(what, iterator):
    iterator = iterator or what.iteritems()
    try:
        while True:
            k,v = iterator.next()
            print "Trying k = ", k
            if v > 100:
                return iterator
    except StopIteration:
        return None

您将存储此函数的结果,而不是将区域名称存储在last_region 中,这就像一个指向您离开的地方的“指针”。然后,您可以像这样使用该函数(显示为在 Python 交互式解释器中运行,包括输出):

>>> x = {'a':12, 'b': 42, 'c':182, 'd': 9, 'e':12}
>>> last_region = None
>>> last_region = iterate(x, last_region)
Trying k = a
Trying k = c
>>> last_region = iterate(x, last_region)
Trying k = b
Trying k = e
Trying k = d

因此,您可以轻松地从中断处继续,但还有一个需要注意的额外警告:

>>> last_region = iterate(x, last_region)
Trying k =  a
Trying k =  c
>>> x['z'] = 45
>>> last_region = iterate(x, last_region)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in iterate
RuntimeError: dictionary changed size during iteration

如您所见,如果您添加新密钥,它会引发错误。因此,如果您使用此方法,则需要确保在向字典中添加新区域时设置last_region = None

【讨论】:

  • 感谢您提出富有成效的建议! :) 这看起来可行.. 只是一个额外的问题.. 上面哪些是关键字,哪些只是变量?我喜欢这个概念,我只需要先完全理解它,而不仅仅是复制/过去的代码。所以每个用户实例都可以记录一个不同的离开点,然后把它备份起来? ...并且一旦添加或删除区域,运行代码以清除每个用户指向无的指针?
  • 是的,您可以让多个迭代器指向字典并从您想要的任何一个中提取。此外,根据您的具体要求,您可能希望在调用next() 之前保存迭代器的值,而不是之后(这应该是一个简单的更改)。
  • 我假设 iter 是一个任意变量名?那可以说“lastrecord”吗? pycharm 告诉我“iter”隐藏了内置名称“iter”..
  • 在我的例子中,iter 是一个变量名。对困惑感到抱歉。我已编辑将其更改为 iterator
  • 不确定这有多容易,因为我的数据是嵌套的。每个区域实际上包含一个字典本身,其中是坐标数据,由项目“pos1”和“pos2”表示。典型记录:“shop”:{“flgs”:{“breakprot”:true,“placeprot”:true},“dim”:0,“placeplayers”:{“4f953255-6775-4dc6-a612-fb4230588eff”:“ SurestTexas00"}, "breakplayers": {"4f953255-6775-4dc6-a612-fb4230588eff": "SurestTexas00"}, "protected": true, "banplayers": {}, "pos1": [5120025, 60, 5120208] , "pos2": [5120062, 73, 5120257], "ownerUuid": "4f953255-6775-4dc6-a612-fb4230588eff"}
【解决方案4】:

TigerhawkT3 是对的。从某种意义上说,在给定字典中没有保证顺序或键的情况下,字典是无序的。如果你遍历同一个字典,你甚至可以有不同的键顺序。如果您想要订购,您需要使用OrderedDict 或只是简单的列表。您可以将您的 dict 转换为 list 并按照它表示您需要的顺序对其进行排序。

【讨论】:

  • 我们可以停止他们没有订购的事实吗?我已经知道了。我只想从预定义的键开始...我什至愿意手动搜索第一个键,然后如果没有匹配项,则对其余键执行 for 循环.. 只要它可以干净地完成。另外,我在最初的问题中表示,我不想要排序字典的开销......此外,缓存和排序将没有什么价值,因为所有用户都在使用同一个字典。
  • @SurestTexas 我不确定您是否完全理解“未订购”的含义。您肯定会获得预定义键的位置,但没有“休息”。休息假设有一些线性顺序,有一个点以及之后和之前的一切。好吧,事实并非如此,您可以使用iteritems() 迭代dict,这会线性化,但不能保证键的顺序。您是否实际测量过 OrderedDict 的开销?您可以复制和排序字典,它不会影响其他用户。
  • 我知道安德烈是什么意思;停止居高临下。为了我的目的,我只需要“预定义的位置”。如果不匹配,那么我将使用 for 循环搜索所有记录。休息时,我的意思是我要搜索整个内容,顺序无关紧要。
【解决方案5】:

如果不知道您的对象是什么以及示例中的 selfuser 实例还是 environment 实例,则很难提出解决方案。但是如果示例中的self环境,那么如果用户实例是可散列的,那么它的类可以有一个类属性,即所有当前用户及其最后已知位置的字典。

类似的东西

class Thing(object):
    __user_regions = {}
    def where_ami(self, user):
        try:
            region = self.__user_regions[user]
            print 'AHA!! I know where you are!!'
        except KeyError:
            # find region
            print 'Hmmmm. let me think about that'
            region = 'foo'
            self.__user_regions[user] = region

class User(object):
    def __init__(self, position):
        self.pos = position

thing = Thing()
thing2 = Thing()
u = User((1,2))
v = User((3,4))

现在您可以尝试从类属性中检索用户的区域。如果有多个Thing,它们将共享该类属性。

>>> 
>>> thing._Thing__user_regions
{}
>>> thing2._Thing__user_regions
{}
>>> 
>>> thing.where_ami(u)
Hmmmm. let me think about that
>>> 
>>> thing._Thing__user_regions
{<__main__.User object at 0x0433E2B0>: 'foo'}
>>> thing2._Thing__user_regions
{<__main__.User object at 0x0433E2B0>: 'foo'}
>>> 
>>> thing2.where_ami(v)
Hmmmm. let me think about that
>>> 
>>> thing._Thing__user_regions
{<__main__.User object at 0x0433EA90>: 'foo', <__main__.User object at 0x0433E2B0>: 'foo'}
>>> thing2._Thing__user_regions
{<__main__.User object at 0x0433EA90>: 'foo', <__main__.User object at 0x0433E2B0>: 'foo'}
>>> 
>>> thing.where_ami(u)
AHA!! I know where you are!!
>>> 

【讨论】:

    【解决方案6】:

    您说您“不希望以任何特定顺序对字典进行排序的额外开销”。 什么开销?大概OrderedDict 在内部使用了一些额外的数据结构来跟踪键的顺序。但除非您知道这会消耗您太多内存,否则OrderedDict 是您的解决方案。这意味着分析您的代码并确保 OrderedDict 是您的瓶颈的来源。

    如果您想要最干净的代码,只需使用OrderedDict。它有一个move_to_back 方法,它接受一个键并将其放在字典的前面或末尾。例如:

    from collections import OrderedDict
    
    animals = OrderedDict([('cat', 1), ('dog', 2), ('turtle', 3), ('lizard', 4)])
    
    def check_if_turtle(animals):
        for animal in animals:
            print('Checking %s...' % animal)
            if animal == 'turtle':
                animals.move_to_end('turtle', last=False)
                return True
        else:
            return False
    

    我们的check_if_turtle 函数通过OrderedDict 查找turtle 键。如果没有找到,它会返回False。如果确实找到它,它会返回True,但不会在将turtle 键移到OrderedDict 的开头之后。

    让我们试试吧。第一次运行:

    >>> check_if_turtle(animals)
    Checking cat...
    Checking dog...
    Checking turtle...
    True
    

    我们看到它检查了直到turtle 的所有键。现在,如果我们再次运行它:

    >>> check_if_turtle(animals)
    Checking turtle...
    True
    

    我们看到它首先检查了turtle 键。

    【讨论】:

    • 看起来不错,但我有两个后续问题:
    • 1) 当 20 个不同的用户将他们的地区放在列表的首位时会发生什么? - 在第 20 次迭代中找到它仍然比 100+ 好,我想 2)代码的其他部分中的常规字典方法仍然可以访问它,还是我必须在使用常规字典方法的地方重写整个程序....我问是因为这是一种奇怪的格式; ([('cat', 1), ('dog', 2), ('turtle', 3), ('lizard', 4)]){'cat': 1, 'dog': 2, 'turtle': 3, 'lizard': 4}
    • @SurestTexas 首先,回答 (2),是的,OrderedDict 可以在任何可以使用 dict 的地方替换,而无需重写代码。它是dict 的子类,因此具有所有dict 方法。通常人们会像我一样初始化OrderedDict,但实际上在这种情况下它并不重要:如果你愿意,你可以用dict 来初始化它。只需知道生成的OrderedDict 中的键顺序不一定与您在初始化程序中使用的dict 中编写它们的方式相同。
    • 至于(1),我不太清楚您所说的“不同用户”是什么意思。我想您是说在对该方法的不同调用之间,不同的用户可能正在寻找不同的区域?方法的签名是什么?您是否有理由不先检查可疑区域,然后遍历其他区域?
    • 我的意思是只有 1 个字典,并且 20 个用户中的每一个都可能每秒(或更多)与它进行交互,因为他们在网格中占据不同的位置。每个用户的可疑区域是不同的/播放器
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-11-03
    • 1970-01-01
    • 2022-11-17
    • 2021-05-13
    • 2021-12-17
    • 1970-01-01
    • 2015-04-27
    相关资源
    最近更新 更多