pd-code-connected-sum 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pd_code_connected_sum-0.0.1/LICENSE +21 -0
- pd_code_connected_sum-0.0.1/PKG-INFO +46 -0
- pd_code_connected_sum-0.0.1/README.md +26 -0
- pd_code_connected_sum-0.0.1/pd_code_connected_sum/__init__.py +5 -0
- pd_code_connected_sum-0.0.1/pd_code_connected_sum/main.py +306 -0
- pd_code_connected_sum-0.0.1/pyproject.toml +18 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TopologicalKnotIndexer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pd-code-connected-sum
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: make connected sum for link pd_code
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: GGN_2015
|
|
8
|
+
Author-email: neko@jlulug.org
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Requires-Dist: pd-code-sanity
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# pd_code_connected_sum
|
|
21
|
+
caculate connected sum for link pd_code.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pd-code-connected-sum
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import pd_code_connected_sum
|
|
33
|
+
|
|
34
|
+
# L2a1
|
|
35
|
+
link_pd_code_1 = [[4, 1, 3, 2], [2, 3, 1, 4]]
|
|
36
|
+
|
|
37
|
+
# L4a1
|
|
38
|
+
link_pd_code_2 = [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
|
|
39
|
+
|
|
40
|
+
connect_pos_1 = 1
|
|
41
|
+
connect_pos_2 = 1
|
|
42
|
+
|
|
43
|
+
print(pd_code_connected_sum.connected_sum(
|
|
44
|
+
link_pd_code_1, link_pd_code_2, connect_pos_1, connect_pos_2))
|
|
45
|
+
```
|
|
46
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pd_code_connected_sum
|
|
2
|
+
caculate connected sum for link pd_code.
|
|
3
|
+
|
|
4
|
+
## Install
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install pd-code-connected-sum
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import pd_code_connected_sum
|
|
14
|
+
|
|
15
|
+
# L2a1
|
|
16
|
+
link_pd_code_1 = [[4, 1, 3, 2], [2, 3, 1, 4]]
|
|
17
|
+
|
|
18
|
+
# L4a1
|
|
19
|
+
link_pd_code_2 = [[6, 1, 7, 2], [8, 3, 5, 4], [2, 5, 3, 6], [4, 7, 1, 8]]
|
|
20
|
+
|
|
21
|
+
connect_pos_1 = 1
|
|
22
|
+
connect_pos_2 = 1
|
|
23
|
+
|
|
24
|
+
print(pd_code_connected_sum.connected_sum(
|
|
25
|
+
link_pd_code_1, link_pd_code_2, connect_pos_1, connect_pos_2))
|
|
26
|
+
```
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import pd_code_sanity
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
# 为编码增加前缀
|
|
5
|
+
def add_prefix(pd_code:list[list], prefix:str) -> list[list]:
|
|
6
|
+
pd_code = json.loads(json.dumps(pd_code))
|
|
7
|
+
for i in range(len(pd_code)):
|
|
8
|
+
for j in range(len(pd_code[i])):
|
|
9
|
+
pd_code[i][j] = prefix + str(pd_code[i][j])
|
|
10
|
+
return json.loads(json.dumps(pd_code))
|
|
11
|
+
|
|
12
|
+
# 提取原始数值
|
|
13
|
+
def get_raw_val(val_in_crs:int|str) -> int:
|
|
14
|
+
if isinstance(val_in_crs, str):
|
|
15
|
+
return int(val_in_crs.split("_")[-1])
|
|
16
|
+
return int(val_in_crs)
|
|
17
|
+
|
|
18
|
+
def add_edge(pre:dict, nxt:dict, val1, val2):
|
|
19
|
+
raw_val1 = get_raw_val(val1)
|
|
20
|
+
raw_val2 = get_raw_val(val2)
|
|
21
|
+
|
|
22
|
+
# 记录前后关系
|
|
23
|
+
pre_val = -1
|
|
24
|
+
nxt_val = -1
|
|
25
|
+
|
|
26
|
+
# 确定前后关系
|
|
27
|
+
if abs(raw_val1 - raw_val2) == 1:
|
|
28
|
+
if raw_val1 < raw_val2:
|
|
29
|
+
pre_val = val1
|
|
30
|
+
nxt_val = val2
|
|
31
|
+
else:
|
|
32
|
+
pre_val = val2
|
|
33
|
+
nxt_val = val1
|
|
34
|
+
else:
|
|
35
|
+
if raw_val1 < raw_val2:
|
|
36
|
+
pre_val = val2
|
|
37
|
+
nxt_val = val1
|
|
38
|
+
else:
|
|
39
|
+
pre_val = val1
|
|
40
|
+
nxt_val = val2
|
|
41
|
+
|
|
42
|
+
# 设置 dict 值
|
|
43
|
+
pre[nxt_val] = pre_val
|
|
44
|
+
nxt[pre_val] = nxt_val
|
|
45
|
+
|
|
46
|
+
# 计算 pre 和 nxt
|
|
47
|
+
# 没有 r1 crossing 就不会出现混乱
|
|
48
|
+
def get_pre_nxt(pd_code:list[list]) -> tuple[dict, dict]:
|
|
49
|
+
pre = dict()
|
|
50
|
+
nxt = dict()
|
|
51
|
+
for crossing in pd_code:
|
|
52
|
+
if len(set(crossing)) > 2: # 至少有三个不同的数
|
|
53
|
+
add_edge(pre, nxt, crossing[0], crossing[2])
|
|
54
|
+
add_edge(pre, nxt, crossing[1], crossing[3])
|
|
55
|
+
else:
|
|
56
|
+
two_values = list(set(crossing)) # 只有两个不同的数
|
|
57
|
+
if len(two_values) != 2:
|
|
58
|
+
raise AssertionError()
|
|
59
|
+
for i in range(2):
|
|
60
|
+
pre[two_values[i]] = two_values[1-i] # 互为前驱后继
|
|
61
|
+
nxt[two_values[i]] = two_values[1-i]
|
|
62
|
+
|
|
63
|
+
# 获取数值合集
|
|
64
|
+
num_set = get_num_set(pd_code)
|
|
65
|
+
for num in num_set:
|
|
66
|
+
if pre.get(num) is None:
|
|
67
|
+
pre[num] = nxt[num]
|
|
68
|
+
if nxt.get(num) is None:
|
|
69
|
+
nxt[num] = pre[num]
|
|
70
|
+
return pre, nxt
|
|
71
|
+
|
|
72
|
+
# 替换一个值恰好一次
|
|
73
|
+
def replace_val(crossing:list, val_now:str, val_new:str):
|
|
74
|
+
cnt = 0
|
|
75
|
+
for i in range(len(crossing)):
|
|
76
|
+
if crossing[i] == val_now:
|
|
77
|
+
crossing[i] = val_new
|
|
78
|
+
cnt += 1
|
|
79
|
+
if cnt != 1:
|
|
80
|
+
raise ValueError()
|
|
81
|
+
|
|
82
|
+
# 检查一个 crossing 是否包含某个值
|
|
83
|
+
def check_has(crossing:list, val_now:str) -> bool:
|
|
84
|
+
for i in range(len(crossing)):
|
|
85
|
+
if crossing[i] == val_now:
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# 检查两个元素同时存在
|
|
90
|
+
def check_has2(crossing:list, val1:str, val2:str) -> bool:
|
|
91
|
+
return (
|
|
92
|
+
check_has(crossing, val1) and
|
|
93
|
+
check_has(crossing, val2))
|
|
94
|
+
|
|
95
|
+
def get_num_set(pd_code:list[list]):
|
|
96
|
+
val_set = set()
|
|
97
|
+
for crossing in pd_code:
|
|
98
|
+
for term in crossing:
|
|
99
|
+
if term not in val_set:
|
|
100
|
+
val_set.add(term)
|
|
101
|
+
return sorted(list(val_set))
|
|
102
|
+
|
|
103
|
+
# 按照顺序对循环进行遍历
|
|
104
|
+
def dfs(val, nxt:dict[str, str], vis: set[int], idx:dict[str, int]):
|
|
105
|
+
if val in vis:
|
|
106
|
+
return
|
|
107
|
+
assert nxt.get(val) is not None
|
|
108
|
+
|
|
109
|
+
# 向已经访问元素队列中添加该节点
|
|
110
|
+
vis.add(val)
|
|
111
|
+
idx[val] = len(vis) # 当前元素个数,就是当前节点新编号
|
|
112
|
+
|
|
113
|
+
if nxt[val] not in vis:
|
|
114
|
+
dfs(nxt[val], nxt, vis, idx)
|
|
115
|
+
|
|
116
|
+
# 在有向图中重新编号
|
|
117
|
+
def get_new_number_map(nxt:dict[str, str], num_set:list[int]) -> dict[str, int]:
|
|
118
|
+
vis = set()
|
|
119
|
+
idx = dict()
|
|
120
|
+
for val in num_set:
|
|
121
|
+
if val not in vis:
|
|
122
|
+
dfs(val, nxt, vis, idx)
|
|
123
|
+
return idx
|
|
124
|
+
|
|
125
|
+
# 检查有多少个 crossing 包含了 val_now
|
|
126
|
+
def count_crossing_exists(pd_code_now:list[list], val_now) -> int:
|
|
127
|
+
ans = 0
|
|
128
|
+
for crossing in pd_code_now:
|
|
129
|
+
if check_has(crossing, val_now):
|
|
130
|
+
ans += 1
|
|
131
|
+
return ans
|
|
132
|
+
|
|
133
|
+
# 找到同一个连通分量中合法的位置
|
|
134
|
+
def find_avaible_pos(pd_code:list[list], val_now):
|
|
135
|
+
for crossing in pd_code:
|
|
136
|
+
if check_has(crossing, val_now):
|
|
137
|
+
values = []
|
|
138
|
+
for i in range(len(crossing)):
|
|
139
|
+
if crossing[i] != val_now and crossing[i] not in values:
|
|
140
|
+
values.append(crossing[i])
|
|
141
|
+
if len(values) == 1:
|
|
142
|
+
return None
|
|
143
|
+
else:
|
|
144
|
+
return values[0]
|
|
145
|
+
|
|
146
|
+
# 在这里报错说明 pd_code 中没找到 val_now
|
|
147
|
+
raise AssertionError()
|
|
148
|
+
|
|
149
|
+
# 删除 pd_code1 中 val_1 所在的 crossing
|
|
150
|
+
# 然后把两个扭结直接合并
|
|
151
|
+
# 然后重新编号
|
|
152
|
+
def del_component_and_merge(pd_code1, pd_code2, val_1, val_2) -> list[list]:
|
|
153
|
+
suc = False
|
|
154
|
+
for i in range(len(pd_code1)):
|
|
155
|
+
if check_has(pd_code1[i], val_1):
|
|
156
|
+
suc = True
|
|
157
|
+
pd_code1 = pd_code1[:i] + pd_code1[i+1:] # 跳过第 i 个元素
|
|
158
|
+
|
|
159
|
+
# 这里报错说明没找到 val_1 对应的 component
|
|
160
|
+
if not suc:
|
|
161
|
+
raise AssertionError()
|
|
162
|
+
|
|
163
|
+
# 获取元素集合
|
|
164
|
+
num_set_1 = add_prefix([get_num_set(pd_code1)], "a_")[0]
|
|
165
|
+
num_set_2 = add_prefix([get_num_set(pd_code2)], "b_")[0]
|
|
166
|
+
|
|
167
|
+
# 增加前缀编码
|
|
168
|
+
pd_code1 = add_prefix(pd_code1, "a_")
|
|
169
|
+
pd_code2 = add_prefix(pd_code2, "b_")
|
|
170
|
+
|
|
171
|
+
# 计算前驱后继
|
|
172
|
+
_, nxt1 = get_pre_nxt(pd_code1)
|
|
173
|
+
_, nxt2 = get_pre_nxt(pd_code2)
|
|
174
|
+
nxt = nxt1 | nxt2
|
|
175
|
+
|
|
176
|
+
# 合并得到新的 pd_code
|
|
177
|
+
new_pd_code = json.loads(json.dumps(pd_code1 + pd_code2))
|
|
178
|
+
|
|
179
|
+
# 获得新的编号方式
|
|
180
|
+
num_map = get_new_number_map(nxt, num_set_1 + num_set_2)
|
|
181
|
+
for crossing in new_pd_code:
|
|
182
|
+
for i in range(len(crossing)):
|
|
183
|
+
if num_map.get(crossing[i]) is None:
|
|
184
|
+
assert AssertionError()
|
|
185
|
+
crossing[i] = num_map[crossing[i]]
|
|
186
|
+
return sorted(new_pd_code)
|
|
187
|
+
|
|
188
|
+
# 将 pd_code1 里面的 val_1 和 pd_code2 里面的 val_2 连接起来
|
|
189
|
+
def connected_sum(
|
|
190
|
+
pd_code1:list[list], pd_code2:list[list], val_1, val_2) -> list[list]:
|
|
191
|
+
|
|
192
|
+
# 如果有一个扭结平凡,则不需要连接
|
|
193
|
+
if pd_code1 == [] or pd_code2 == []:
|
|
194
|
+
return json.loads(json.dumps(pd_code1 + pd_code2))
|
|
195
|
+
|
|
196
|
+
# 检查 pd_code 的弱合法性
|
|
197
|
+
for pd_code in [pd_code1, pd_code2]:
|
|
198
|
+
if not pd_code_sanity.sanity(pd_code):
|
|
199
|
+
raise TypeError()
|
|
200
|
+
|
|
201
|
+
# 检查 val_1 和 val_2 是否出现了
|
|
202
|
+
lis_wrap_1 = [0]
|
|
203
|
+
lis_wrap_2 = [0]
|
|
204
|
+
lis_wrap = [lis_wrap_1, lis_wrap_2]
|
|
205
|
+
for cnt_wrap, val_now, pd_now in [
|
|
206
|
+
(lis_wrap_1, val_1, pd_code1),
|
|
207
|
+
(lis_wrap_2, val_2, pd_code2)
|
|
208
|
+
]:
|
|
209
|
+
for crossing in pd_now:
|
|
210
|
+
for term in crossing:
|
|
211
|
+
if term == val_now:
|
|
212
|
+
cnt_wrap[0] += 1
|
|
213
|
+
|
|
214
|
+
# 检查出现次数是否正确
|
|
215
|
+
for i in range(2):
|
|
216
|
+
if lis_wrap[i][0] != 2:
|
|
217
|
+
raise ValueError(f"val_{i+1} not in pd_code{i+1}")
|
|
218
|
+
|
|
219
|
+
# 分别计算两个需要连接的地方
|
|
220
|
+
# 是不是在一个 r1 crossing 上
|
|
221
|
+
# 如果 has_val_x_cnt[0] != 2
|
|
222
|
+
# 则说明,val_x 在一个 r1 crossing
|
|
223
|
+
# 其中 x = 1 或者 2
|
|
224
|
+
has_val_1_cnt = [0]
|
|
225
|
+
has_val_2_cnt = [0]
|
|
226
|
+
for pd_code_now, has_val_cnt, val_now in [
|
|
227
|
+
(pd_code1, has_val_1_cnt, val_1),
|
|
228
|
+
(pd_code2, has_val_2_cnt, val_2)
|
|
229
|
+
]:
|
|
230
|
+
has_val_cnt[0] = count_crossing_exists(pd_code_now, val_now)
|
|
231
|
+
|
|
232
|
+
vals = [val_1, val_2]
|
|
233
|
+
for val_pos, pd_code_now, pd_code_other, has_cnt_now in [
|
|
234
|
+
(0, pd_code1, pd_code2, has_val_1_cnt),
|
|
235
|
+
(1, pd_code2, pd_code1, has_val_2_cnt),
|
|
236
|
+
]:
|
|
237
|
+
if has_cnt_now[0] != 2:
|
|
238
|
+
|
|
239
|
+
# 在同一个连通分量中
|
|
240
|
+
# 找到一个出现在多个 crossing 中的编号
|
|
241
|
+
vals_new = find_avaible_pos(pd_code_now, vals[val_pos])
|
|
242
|
+
if vals_new is None:
|
|
243
|
+
|
|
244
|
+
# 遇到了 [a, b, b, a] 或者 [a, a, b, b] 之类的
|
|
245
|
+
# 直接删掉当前 components 然后 merge 即可
|
|
246
|
+
return del_component_and_merge(
|
|
247
|
+
pd_code_now, pd_code_other, vals[val_pos], vals[1-val_pos])
|
|
248
|
+
|
|
249
|
+
vals[val_pos] = vals_new
|
|
250
|
+
|
|
251
|
+
# 覆盖旧的元素
|
|
252
|
+
val_1, val_2 = vals
|
|
253
|
+
|
|
254
|
+
# 获取元素集合
|
|
255
|
+
num_set_1 = add_prefix([get_num_set(pd_code1)], "a_")[0]
|
|
256
|
+
num_set_2 = add_prefix([get_num_set(pd_code2)], "b_")[0]
|
|
257
|
+
|
|
258
|
+
# 增加前缀编码
|
|
259
|
+
pd_code1 = add_prefix(pd_code1, "a_")
|
|
260
|
+
pd_code2 = add_prefix(pd_code2, "b_")
|
|
261
|
+
|
|
262
|
+
# 由于不存在 nugatory crossing 和 r1 crossing
|
|
263
|
+
# 所以两侧各有两个相关 crossing
|
|
264
|
+
_, nxt1 = get_pre_nxt(pd_code1)
|
|
265
|
+
_, nxt2 = get_pre_nxt(pd_code2)
|
|
266
|
+
nxt = nxt1 | nxt2
|
|
267
|
+
|
|
268
|
+
# 带有字母前缀的成分
|
|
269
|
+
a_val_1 = f"a_{val_1}"
|
|
270
|
+
b_val_2 = f"b_{val_2}"
|
|
271
|
+
|
|
272
|
+
# 将两组 pd_code 合并
|
|
273
|
+
# 需要注意 nxt[a_val_1] 和 pre[a_val_1] 可能相同
|
|
274
|
+
# 所以要保证只替换一次
|
|
275
|
+
suc_1 = False
|
|
276
|
+
suc_2 = False
|
|
277
|
+
new_pd_code = json.loads(json.dumps(pd_code1 + pd_code2))
|
|
278
|
+
for crossing in new_pd_code:
|
|
279
|
+
if not suc_1 and check_has2(crossing, a_val_1, nxt[a_val_1]):
|
|
280
|
+
replace_val(crossing, a_val_1, b_val_2)
|
|
281
|
+
suc_1 = True
|
|
282
|
+
if not suc_2 and check_has2(crossing, b_val_2, nxt[b_val_2]):
|
|
283
|
+
replace_val(crossing, b_val_2, a_val_1)
|
|
284
|
+
suc_2 = True
|
|
285
|
+
if suc_1 and suc_2:
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
# 调整前驱后继关系
|
|
289
|
+
nxt[b_val_2] = nxt1[a_val_1]
|
|
290
|
+
nxt[a_val_1] = nxt2[b_val_2]
|
|
291
|
+
|
|
292
|
+
# 获得新的编号方式
|
|
293
|
+
num_map = get_new_number_map(nxt, num_set_1 + num_set_2)
|
|
294
|
+
for crossing in new_pd_code:
|
|
295
|
+
for i in range(len(crossing)):
|
|
296
|
+
if num_map.get(crossing[i]) is None:
|
|
297
|
+
assert AssertionError()
|
|
298
|
+
crossing[i] = num_map[crossing[i]]
|
|
299
|
+
|
|
300
|
+
ans_pd_code = sorted(new_pd_code)
|
|
301
|
+
if not pd_code_sanity.sanity(ans_pd_code):
|
|
302
|
+
raise AssertionError()
|
|
303
|
+
return ans_pd_code
|
|
304
|
+
|
|
305
|
+
if __name__ == "__main__":
|
|
306
|
+
print(connected_sum([[1, 2, 2, 1]], [[1, 2, 2, 1]], 1, 1))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pd-code-connected-sum"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "make connected sum for link pd_code"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "GGN_2015",email = "neko@jlulug.org"}
|
|
7
|
+
]
|
|
8
|
+
license = {text = "MIT"}
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pd-code-sanity"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
18
|
+
build-backend = "poetry.core.masonry.api"
|