- 背景:**
我有一组键相同但值不同的日志。键保证是str
,值可以是str
或None
。
例如:
logs = [
{'id': '1234', 'error': None, 'fruit': 'orange'},
{'id': '12345', 'error': None, 'fruit': 'apple'}
]
有时候这些日志是重复的,相同的键和值对于字典来说是相同的。
{'id': '1234', 'error': None, 'fruit': 'orange'}
{'id': '1234', 'error': None, 'fruit': 'orange'}
我正在按如下方式处理这些问题:
already_seen = set()
for log in logs:
log_hash = compute_hash(log)
if log_hash in already_seen:
continue
already_seen.add(log_hash)
最初,我使用了以下哈希函数:
def compute_hash(log_dict: dict):
return hash(log_dict.values())
但是这个 * 对于不同的字典 * 给出了相同的哈希值,所以我把这个函数改为:
def compute_hash(log_dict: dict):
return hash(frozenset(log_dict.values()))
现在它工作正常(据我所注意到的)。
- 问题**:
1.为什么最初的方法不起作用?
- 当前方法是否正确?正确性:
- 具有相同值的dict的相同哈希值。
- 具有不同值的字典的不同散列值。
- 有没有更好或更简单的方法来解决这个问题?
示例:
dict_1 = {
"date": "2022-12-30 18:04:35.488",
"tenant": "hz",
"action": "REFRESH_KUBE_WORKLOAD",
"code": "SERVER_SENT_REQ_TO_AGENT",
"message_id": "ae62aa57-b155-452f-b7bd-740c18110aa7",
"error": None,
}
dict_2 = {
"date": "2022-12-30 18:04:35.479",
"tenant": "hz",
"action": "REFRESH_KUBE_WORKLOAD",
"code": "SERVER_FAIL",
"message_id": "5b0eb8d9-9c6a-4c6d-bd27-b03ac0b865e8",
"error": " Timeout",
}
print(hash(dict_1.values()) == hash(dict_2.values()))
3条答案
按热度按时间a9wyjsp71#
在Python 3.x中,
dict
上的.values
返回dict_values
的一个示例(名称没有在内置函数中定义,但是示例是这样标识自己的):此类旨在作为现有基础对象上的视图,并且不定义其自己的
__hash__
方法。object
是直接超类,因此对__hash__
的调用回退到object.__hash__
:事实上,
dict_values
不能合理地实现__hash__
,因为它是(间接)可变的-作为视图,它依赖于底层的dict:由于没有一种明显的通用方法来散列任何对象,同时还考虑其实际属性,因此默认方法 * 不 * 考虑属性,而是简单地基于对象标识。例如,在我的平台上,结果如下所示:
换句话说:
因此,如果原始代码中的
compute_hash
被临时对象重复调用,它不会给出有用的结果--结果不依赖于对象的内容,并且通常是相同的,因为循环中的临时(即直接GCd)对象通常会在同一个内存位置结束。另一方面,
frozenset
是为了长期存储一些不可变的数据,因此,定义一个__hash__
是很重要和有用的,它做到了:我们可以在GitHub上看到
frozenset.__hash__
的参考实现。(毕竟,它是一个不可变的对象,所以除了节省内存之外,没有理由重复计算),否则它会迭代哈希表,将条目的子计算异或在一起,并做一些额外的修正。所以我们可以确信这将是所讨论的任务的正确散列值。很难想象能更简单地解决这个问题,因为对不工作代码的唯一更改是单一类型强制,但是,如果所有日志条目都有相同的键集,那么将其硬编码到哈希中可能更有意义:
然而,现有的守则在技术上并不正确;毕竟,散列值只包含有限数量的位,而我们可以散列任意大的输入集,因此,完全不同的集可能被错误地注册为
already_seen
(尽管这是极不可能的)。更重要的是,如果我们的散列只关心log
的.values
,而不考虑顺序,那么当然它会将相同的散列值给予另一个log
,其中相同的一组值被分配给不同的键例如{'id': '1234', 'error': None, 'fruit': 'orange'}
对{'id': '1234', 'error': 'orange', 'fruit': None}
。为了解决这个问题,一种方法是使用
dict
,以便跟踪原始值,并根据匹配的哈希值对其进行分组(因为我们现在实际上需要考虑冲突):但这实际上是重复了dict应该为我们实现的检查逻辑。
另一种方法是使用我们自己的日志条目类型:
wqsoz72f2#
你有一些可行的答案,但我认为你可能把事情弄得过于复杂了。下面是我会在你的原始代码上做的快速修复。
(“12345”、无、“苹果”)
(“1234”、无、“橙色”)
hmmo2u0o3#
1.你不能仅仅根据散列来判断“已经看到”。散列比实际数据小,但可能会有冲突,这是一种权衡。使用散列对日志进行分组,然后检查是否相等。
1.仍然会有碰撞。