我想知道为什么这不会在GNU Smalltalk中终止:
s := Set new. s add: s
理论上,s
应该只是一个包含空集合的集合。但是执行该操作会永远循环下去,炸毁堆。
有趣的是,
((s := Set with: 4 with: 5 with: 6) add: s) size.
终止并求值为4。
答案 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
内部表的初始插槽数(素数,初次创建集时设置为s
或5
,然后根据需要增加。)
到目前为止,太好了。但是可能会有一些
在哪里?根据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 <= 10
为true
,并且该方法将尝试再次递归计算s hash
,导致,哦,哦,堆栈溢出。
实施Collection >> #printOn:
时要小心。即使该集合不包含自身,但如果从一个元素间接返回包含它的集合,则可能会发生递归。
实施Collection >> #hash
时要小心(出于同样的原因)
向自身添加Collection
时要小心。更一般而言,当集合包含带有(可能是间接)引用的元素时要小心,因为枚举这样的集合可能很棘手。
在数学(自然科学)中,集合不能是其自身的元素(集合论公理明确禁止这样做)。因此,在违反来自极其成熟和发展的科学知识体系的规则之前,请重新考虑您的模型。