Pyslvs v0.9 大更新進度

  • 拓樸排列程式

拓樸排列程式

花了一些時間寫了一個排列拓樸程式,將概念寫在這邊。

在論壇爬文時,看到作者自己推薦的 Python 樹狀結構模組 anytree,看了說明文件後決定使用此模組協助樹狀管理。

以一個四連桿 (4,) 的樹狀拓樸如下,只有一種排列方法:

L0(2)
├── L1(2)
│   └── L3(2)
│       └── [L2](2)
└── L2(2)
    └── [L3](2)
-------
Answer count: 1

需要的模組有:

from anytree import Node, RenderTree
from anytree.search import findall
from itertools import permutations
from typing import Tuple

接著是印出上面樹狀結構的函式,anytree 的 RenderTree 搜尋函式會朝下列出節點的階級字元。

這邊用 noname 這個 bool 變數決定是否顯示名稱。

show_tree = lambda root: '\n'.join("{}{}({})".format(pre, n.name, n.limit) for pre, fill, n in RenderTree(root))

接下來是主函式,稱為 make_link,接收內含 int 的可迭代物件。

def topo(iter: Tuple[int,]):
    ...
    return answer

第一部分是創出連桿的數量,數字則是接頭數。

link_type = []
  for i, num in enumerate(iter):
    i += 2
    for j in range(num):
        link_type.append(i)

如輸入 (4,),可以得到 [2, 2, 2, 2];輸入 (5, 4) 可得 [2, 2, 2, 2, 2, 3, 3, 3, 3]

使用 Python 迭代工具模組的 permutations 函式來創造排列組合的迴圈。

相符無誤的項目會將 root 節點加入答案,有錯誤則用 continue 關鍵字跳過。

answer = []
for all_link in list(set(permutations(link_type))):
  ...
  answer.append(links[0])

首先轉換 link_type 的內容成為 anytree 模組的 Node 類型。

其中 limit 屬性是此節點的接頭上限,僅用於比對,並無程式上的限制。

all_link = [Node("L{}".format(i), limit=v) for i, v in enumerate(all_link)]

接著將第一項當作 root 節點,加入 links

這裡 links 清單的最後一項 links[-1] 是接下來的搜索法準備填入的項目。

links = []
links.append(all_link.pop(0))

然後使用廣度優先搜索法 (Breadth-First-Search, BFS) 填入所有節點,使用 list 類型的 pop 方法配上 while 迴圈可以確保用光所有節點。

當指派一個節點的 parent 屬性時,anytree 模組會自動將節點連上父節點,父節點可以透過 children 屬性取得一個裝有所有子節點指標的 tuple 物件。

while all_link:
    link = all_link.pop(0)
    if (len(links[-1].children) + bool(links[-1].parent))==links[-1].limit:
        links.append(links[-1].children[0])
    link.parent = links[-1]

由於數學定義的「樹 (tree)」結構中,子節點只能擁有一個父項,否則為迴路 (Loop),anytree 模組會在連接成迴路時自動回擲 LoopError 錯誤。

但是我們的運動鍊為 close chain,因此必須再創立一個配對流程,這次使用類似連結的概念,同時為「主體」連結一個虛擬節點。

虛擬節點的樣式使用中括弧 [ ] 辨識,不用指派名稱。

創立 get_no_done 函式回傳使用 anytree 的 findall 模組過濾沒配對完成的節點。

get_no_done = lambda: findall(links[0], filter_=lambda n: '[' not in n.name and (len(n.children) + bool(links[-1].parent)) < n.limit)
error = False
while get_no_done():
    nodes = get_no_done()
    try:
        l_1, l_2 = nodes[0], nodes[1]
    except (ValueError, IndexError):
        error = True
        break
    else:
        Node("[{}]".format(l_1.name), limit=str(l_1.limit), parent=l_2)
        Node("[{}]".format(l_2.name), limit=str(l_2.limit), parent=l_1)

最後檢查是否在上述迴圈出現沒閉合狀況。

if error:
    continue

或是兩對連桿之間有連到一個以上的接頭。

if findall(links[0], filter_=lambda n: len(n.children)!=len(set(c.name.replace('[', '').replace(']', '') for c in n.children))):
    continue

最後可以進行測試:

if __name__=='__main__':
    print("Topologic test")
    answer = topo([5, 4])
    #Show tree
    for root in answer:
        print(show_tree(root))
        print('-'*7)
    print("Answer count: {}".format(len(answer)))

可得:

...
-------
L0(2)
├── L1(3)
│   ├── L3(2)
│   │   └── L5(2)
│   │       └── L6(3)
│   │           ├── L7(2)
│   │           │   └── [L8](3)
│   │           └── L8(3)
│   │               ├── [L7](2)
│   │               └── [L4](3)
│   └── L4(3)
│       ├── [L8](3)
│       └── [L2](2)
└── L2(2)
    └── [L4](3)
-------
Answer count: 36

經驗證,所有接頭都有連接。


Comments

comments powered by Disqus