不相交集(一種並查集結構)會把一群值分割成互不重疊的群組,並且能快速回答一個問題:這兩個值是否在同一個群組裡?它位於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起來,再把其中一個與第三個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