JavaScript使用哈希表来实现Map和Set吗?

12

我是一名Python开发者,正在学习JavaScript的基础知识。

我开始使用MapSet。它们似乎有着与Python中的dictset相同的API,因此我认为它们是哈希表并且可以保证O(1)的查找时间。

但出于好奇心,我尝试在Chrome控制台中执行以下操作:

new Set([new Set([1, 2, 3])])

发生的情况是这样的:

Set(1) {Set(3)}

JavaScript可以愉快地创建集合。这是为什么呢?在Python中,您会收到一个错误,因为您不能将可变项放入集合或字典中。为什么JavaScript允许它?


4
因为JavaScript允许对可变数据类型进行哈希,这并不是不可能的,但非常不建议,以至于Python不允许这样做。当然,在Python中,您可以定义自己的可变和可哈希的自定义类型。但请注意,对象是通过其身份进行哈希处理,因此它并不是非常危险,但也不是非常有用。 - juanpa.arrivillaga
JS允许它,因为它允许它。请注意,Java和C#也允许可变项放入哈希桶中。当然,如果它们的哈希标识不稳定,这可能会导致问题,这可能是Python试图避免的,但是在这里,JS肯定没有做任何与众不同的事情。实际上,在某些方面,JS通过不允许对象的哈希标识更改来缓解了一些问题-它始终是对象的引用,这避免了将对象排序到一个哈希桶中后“丢失”对象的问题,然后代码在另一个哈希桶中查找它。 - VLAZ
1
然而,这样做也会带来其他问题,因为如果没有实际的对象,就不能在哈希集合中查找对象。使用自定义哈希标识可以通过构造散列到相同值的东西来查找它。因此,这种方法有其优点和缺点,但再次强调,这并不是不正常的。 - VLAZ
2个回答

7

以下是JS代码:

> m1 = new Map([['a', 1]])
Map { 'a' => 1 }
> m2 = new Map()
Map {}
> m2.set(m1, 3)
Map { Map { 'a' => 1 } => 3 }
> m2.get(m1)
3

但是请注意,这里基于身份(即===)进行哈希运算,所以...

> m2.get(new Map([['a',1]]))
undefined

那么,这张地图真的有多有用呢?

请注意,这与Python的默认行为没有区别。用户定义类型的默认状态是可哈希的:

>>> class Foo: pass
...
>>> f0 = Foo()
>>> s = {f0}
>>> Foo() in s
False
>>> f0 in s
True

在 Python 中,默认情况下,object.__eq__ 是基于对象标识进行比较的,因此上述代码是可行的。但是,如果你重写了 __eq__,默认情况下,__hash__ 会被设置为 None,并且在尝试使用哈希容器时会失败:
>>> class Bar:
...    def __init__(self, value):
...       self.value = value
...    def __eq__(self, other):
...       return self.value == other.value
...
>>> b0 = Bar(0)
>>> b1 = Bar(2)
>>> {b0, b1}
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'Bar'

此时,您必须实现__hash__以保持一致性,并注意,虽然您的用户定义对象从未真正变得非常“不可变”


1
“那么,这个地图到底有多大用处?”它可以作为一种解耦且相当便宜的方式,向对象添加元信息而无需修改对象本身。实际上,键也是一个映射的映射是一个完美的例子 - 你甚至不能改变Map类。你可以使用同样的方法来处理其他第三方对象,以允许你在不接触它们的情况下增强它们。将对象添加到地图中作为键,将您想要的任何数据作为值添加到它们中(甚至可以是函数/方法),然后使用您的对象从地图中获取元信息。 - VLAZ
1
@VLAZ,你可能想要使用WeakKeyMap来实现,否则你的属性表可能会无限增长。对于更ECS-ish的系统,你应该使用ID而不是任意对象作为键。 - Masklinn
1
@VLAZ 另一个身份基础成员资格有用的例子是在某些图形结构中使用“Set”来跟踪已访问的节点。 - juanpa.arrivillaga
@juanpa.arrivillaga 我现在正在尝试使用 Map,我发现如果你将一个 Map 作为键添加进去,它会接受,但是如果你尝试使用任何其他的 map 作为第一个 map 的键,它会给你第二个 map 的值。 就好像它只关心对象的类型而不是它的内容一样。这里到底发生了什么? - Ram Rachum
@VLAZ 这是代码:https://gist.github.com/cool-RR/036b9e6f7ce3f448b275eeb4f707ab8f 真是疯狂。 - Ram Rachum
显示剩余6条评论

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接