一个不相交集(并查集结构)维护一组值,把它们划分成互不重叠的组,并能快速回答一个问题:这两个值是否在同一组?它位于disjoint-sets词汇表中。
<disjoint-set>构建一个空结构。值,也就是原子,可以用add-atom一次添加一个,也可以用add-atoms从一个序列中添加。每个原子一开始都单独成组。
<disjoint-set> ( -- disjoint-set )
add-atom ( atom disjoint-set -- )
add-atoms ( seq disjoint-set -- )
USING: disjoint-sets ;
<disjoint-set>
{ 1 2 3 } over add-atoms ! 1, 2 and 3 each in their own group
不相交集是可变的:add-atom、add-atoms和equate会就地修改它,而不是返回一个新的。当某个单词消耗掉不相交集之后,你想再把它放回栈上时,keep就很好用。
equate会合并包含其原子的那两个组。合并具有传递性:先合并两个原子,再把其中一个与第三个合并,这三个原子最终就在同一个组里。
equate ( atom1 atom2 disjoint-set -- )
每个组都有一个规范成员,也就是它的代表元。representative会返回它,而两个原子属于同一组,当且仅当它们的代表元相等。
representative ( atom disjoint-set -- representative )
USING: disjoint-sets ;
<disjoint-set>
{ 1 2 3 } over add-atoms
1 2 pick equate
1 over representative . ! => 1
2 over representative . ! => 1 (same representative as 1)
3 over representative . ! => 3 (still on its own)
一个组会选哪个原子作为代表元,属于内部细节:永远不要依赖某个特定原子被选中,只需依赖同一组中的两个原子能得到一致的结果。
当地的诗歌俱乐部要开始新的一季了。诗人加入俱乐部,只要其中两位合写一首诗,他们就属于同一个写作圈。俱乐部想记录谁和谁在同一个圈子里,于是请你编写管理它的那些词。
不相交集合再合适不过:每位诗人是一个原子,一个写作圈就是一个组。
定义new-club,它接收一个诗人序列,返回一个全新的不相交集合,其中每位诗人都已登记。每位诗人一开始都待在自己的写作圈里。
{ "Keats" "Byron" "Dickinson" } new-club
! => a disjoint set holding the three poets
定义collaborate,它接收两位诗人和一个俱乐部。它把两位诗人的写作圈合并,并返回这个俱乐部,这样就可以一次接一次地记录合作。
{ "Keats" "Byron" "Dickinson" } new-club
"Keats" "Byron" rot collaborate
! => the club, with Keats and Byron now in one circle
定义circle-of,它接收一位诗人和一个俱乐部,返回这位诗人所在写作圈的代表。同一个圈子里的两位诗人,报告出来的代表总是同一个。
{ "Keats" "Byron" "Dickinson" } new-club
"Dickinson" swap circle-of
! => "Dickinson" (still in a circle of their own)
定义same-circle?,它接收两位诗人和一个俱乐部,当两位诗人属于同一个写作圈时返回t,否则返回f。请比较这两位诗人的代表,不要使用内置的等价测试。
{ "Keats" "Byron" "Dickinson" } new-club
"Keats" "Byron" rot collaborate
dup [ "Keats" "Byron" ] dip same-circle? . ! => t
[ "Keats" "Dickinson" ] dip same-circle? . ! => f