【问题标题】:Efficiently mapping unhashable objects to their index in a list有效地将不可散列的对象映射到它们在列表中的索引
【发布时间】:2017-12-05 20:10:06
【问题描述】:

一个 Python 列表

f = [x0, x1, x2]

可以看作是从[0, 1, ..., len(f) - 1] 到其元素集的映射的有效表示。 “高效”是指 f[i] 在 O(1) 时间内返回与 i 关联的元素。

逆映射可以定义如下:

class Inverse:
    def __init__(self, f):
        self.f = f

    def __getitem__(self, x):
        return self.f.index(x)

这可行,但是Inverse(f)[x] takes O(n) time on average

或者,可以使用dict

f_inv = {x: i for i, x in enumerate(f)}

这具有 O(1) 的平均时间复杂度,但它要求列表中的对象为 hashable

有没有一种方法可以定义一个逆映射,该映射提供基于等式的查找,平均时间为 O(1),unhashable 对象?

编辑:示例输入和预期输出:

>>> f = [x0, x1, x2]
>>> f_inv = Inverse(f)  # this is to be defined
>>> f_inv[x0]  # in O(1) time
0
>>> f_inv[x2]  # in O(1) time
2

【问题讨论】:

  • 让它们可散列?它们是什么价值观?
  • 使可变对象可哈希化似乎是一种非常有效的自爆方式:asmeurer.com/blog/posts/…
  • 取决于你的数据是什么以及你想用它做什么。也许它无缘无故地可变/不可散列。
  • 这是一个通用图(在数学意义上)库。这些值是节点标签,它们实际上可以是任何东西。
  • 您想通过对象标识 (is) 还是通过相等 (==) 执行此查找?

标签: python list dictionary hashable


【解决方案1】:

您可以创建关联字典,将对象 ID 映射回列表索引。

明显的缺点是您必须在索引中搜索标识对象,而不是搜索仅相等的 eobject。

从好的方面来说,通过使用collections.abc 创建一个自定义的 MutableSequence 类,您可以用最少的代码编写一个类,将您的数据保存为序列和反向字典。

from collections.abc import MutableSequence
from threading import RLock


class MD(dict):
    # No need for a full MutableMapping subclass, as the use is limited
    def __getitem__(self, key):
        return super().__getitem__(id(key))


class Reversible(MutableSequence):
    def __init__(self, args):
        self.seq = list()
        self.reverse = MD()
        self.lock = RLock()
        for element in args:
            self.append(element)

    def __getitem__(self, index):
        return self.seq[index]

    def __setitem__(self, index, value):
        with self.lock:
            del self.reverse[id(self.seq[index])]
            self.seq[index] = value
            self.reverse[id(value)] = index

    def __delitem__(self, index):
        if index < 0:
            index += len(self)
        with self.lock:
            # Increase all mapped indexes
            for obj in self.seq[index:]:
                self.reverse[obj] -= 1
            del self.reverse[id(self.seq[index])]
            del self.seq[index]

    def __len__(self):
        return len(self.seq)

    def insert(self, index, value):
        if index < 0:
            index += len(self)
        with self.lock:
            # Increase all mapped indexes
            for obj in self.seq[index:]:
                self.reverse[obj] += 1
            self.seq.insert(index, value)
            self.reverse[id(value)] = index

瞧:只需使用此对象代替您的列表,并使用公共属性“reverse”来获取身份对象的索引。 感知你可以通过尝试使用不同的策略来增加“MD”类的“智能”,比如使用对象本身,如果它们是可散列的,并且只使用 id,或者基于其他对象属性的其他自定义键,当需要。这样,您可以减少对同一对象进行搜索的需要。

所以,对于列表上的普通操作,这个类保持恢复的字典同步。但是,不支持切片索引。 如需更多信息,请查看https://docs.python.org/3/library/collections.abc.html的文档

【讨论】:

  • 谢谢,这与我开始玩弄的想法相同。我认为您对self.reverse 的访问不应该使用id()self.reverse[id(self.seq[index])] 应该是 self.reverse[self.seq[index]]
【解决方案2】:

不幸的是,您在这里遇到了算法限制。快速查找结构(如哈希表或二叉树)非常有效,因为它们将对象放在特定的桶中或根据它们的值对它们进行排序。这要求它们在您将它们存储在此结构中的整个过程中都是可散列的或可比较的一致,否则查找很可能会失败。

如果您需要的对象是可变的(通常是它们不可散列的原因),那么任何时候您跟踪的对象发生变化,您都需要更新数据结构。最安全的方法是创建不可变对象。如果您需要更改对象,则创建一个新对象,从字典中删除旧对象,并将新对象作为具有相同值的键插入。

这里的操作相对于字典的大小仍然是O(1),你只需要考虑每次更改复制对象的成本是否值得。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-03-14
    • 2017-03-06
    • 1970-01-01
    • 1970-01-01
    • 2013-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多