为什么在Smalltalk中给自己添加一个收藏夹会很麻烦?

时间:2019-06-20 08:28:13

标签: collections smalltalk gnu-smalltalk

我想知道为什么这不会在GNU Smalltalk中终止:

s := Set new. s add: s

理论上,s应该只是一个包含空集合的集合。但是执行该操作会永远循环下去,炸毁堆。

有趣的是, ((s := Set with: 4 with: 5 with: 6) add: s) size.终止并求值为4。

1 个答案:

答案 0 :(得分:1)

简介

Set是一种HashedCollection,专门用于快速成员资格检查。内部Set有一个HashTable,这是一个 sparce 数组,其中有许多空插槽,以尽量减少 collisions 的数量。当我们#add:的元素Set时,index的{​​{1}}被计算为HashTable,其中(hash \\ size) + 1 mod 操作,#\\是表的长度。如果size处的插槽已被占用,则index会递增,直到找到一个空闲插槽为止,然后将元素存储在此处( NB index,因为Smalltalk数组基于+ 1。) How does Set actually work]

我们的案子

现在,让我们看看问题代码会发生什么:

1

如上所述,在步骤2中,1. s := Set new. 2. s add: s. 将计算:

add: s

其中s hash \\ p + 1 p内部表的初始插槽数(素数,初次创建集时设置为s5,然后根据需要增加。)

到目前为止,太好了。但是可能会有一些

问题

在哪里?根据Smalltalk方言的不同,7或元素的下一个#printOn:类可能存在问题。

打印问题

add:可能发生的问题是无限递归。在打印#printOn:时,人们也会希望打印其元素,而在我们这种情况下,这种方法将递归地尝试在此过程中打印s,从而产生无尽的圆度。

为防止这种情况的发生,s使用Pharo,该LimitedWriteStream将在一定数量的迭代后停止写,如果存在则中断递归。

我自己还没有检查过,但这是GNU Smalltalk中似乎正在发生的问题(根据问题。)

请注意,仅打印Set中最大数量的元素是不够的。实际上,我们的集合只包含一个元素(本身),足以创建递归。

附加问题

@ aka.nice在评论中观察到,第二次添加s时也必须小心。为什么?因为,正如我们在上面的简介中所见,消息add: s将需要计算s hash …。以及s hash是如何定义的?这是个有趣的问题。 Smalltalkers通常会遇到在某些类中实现好的#hash的问题。由于s是一个集合,因此很容易考虑其元素的hash作为最终结果,对吗? Pharo采用这种方法,请看:

hash
  | hash |
  hash := self species hash.
  self size <= 10 ifTrue:
    [self do: [:elem | hash := hash bitXor: elem hash]].
  ^hash bitXor: self size hash

吞下诱饵。为什么?因为集合s具有1个元素(本身),所以条件self size <= 10true,并且该方法将尝试再次递归计算s hash,导致,哦,哦,堆栈溢出

结论

  1. 实施Collection >> #printOn:时要小心。即使该集合不包含自身,但如果从一个元素间接返回包含它的集合,则可能会发生递归。

  2. 实施Collection >> #hash时要小心(出于同样的原因)

  3. 向自身添加Collection时要小心。更一般而言,当集合包含带有(可能是间接)引用的元素时要小心,因为枚举这样的集合可能很棘手。

  4. 在数学(自然科学)中,集合不能是其自身的元素(集合论公理明确禁止这样做)。因此,在违反来自极其成熟和发展的科学知识体系的规则之前,请重新考虑您的模型。