iBLP:修订间差异
更多操作
小无编辑摘要 |
无编辑摘要 |
||
| 第1行: | 第1行: | ||
== 记号简介 == | == 记号简介 == | ||
无限基本Laver图案(Infinite Basic Laver Pattern)是由test_alpha0的记号Basic Laver | 无限基本Laver图案(Infinite Basic Laver Pattern)是由test_alpha0的记号Basic Laver Pattren改造而来。IBLP目前尚不理想,还存在许多的坏图案。test_alpha0规定其极限表达式为(1,0)1(2,1,0)1(3,2,1,0)2(4,3,2)1(5,4,3,2)2(6,5,4)1,因为现在认为在该图案下方不存在坏图案,而其上方不远处就出现了很多坏图案。尽管如此,IBLP仍然被认为是目前最强的记号。本页面介绍的规则对应[https://hypcos.github.io/notation-explorer/ NE]上的DEN2。 | ||
== 定义 == | == 定义 == | ||
| 第123行: | 第123行: | ||
=== 定义15 (补全主循环(用于 ''E''(''A, m''))) === | === 定义15 (补全主循环(用于 ''E''(''A, m''))) === | ||
令初始<math>r=n</math>(''n'' 为原始 ''A'' 的行数)。当 ''r'' 不超过当前 ''B'' 的行数时循环:先对行 ''r'' 执行标记补全,再执行原生补全得到 ''t'',然后更新 <math>r=r+t+1</math>。当 ''r'' 越界时补全结束,丢弃 <math>\rm Rec</math>,所得 ''B'' 即为<math>E(A,m)</math>。 | 令初始<math>r=n</math>(''n'' 为原始 ''A'' 的行数)。当 ''r'' 不超过当前 ''B'' 的行数时循环:先对行 ''r'' 执行标记补全,再执行原生补全得到 ''t'',然后更新 <math>r=r+t+1</math>。当 ''r'' 越界时补全结束,丢弃 <math>\rm Rec</math>,所得 ''B'' 即为<math>E(A,m)</math>。 | ||
== 展开器 == | |||
iblp的展开器在[https://hypcos.github.io/notation-explorer/ NE]上可以找到,同时也可以使用如下Python代码直观地看到每个图案的行为。<syntaxhighlight lang="python3"> | |||
import bisect | |||
def _clone_rows(rows): | |||
return [row[:] for row in rows] | |||
def _clone_mask(mask): | |||
return [set(s) for s in mask] | |||
def _find_index(sorted_row, val): | |||
i = bisect.bisect_left(sorted_row, val) | |||
if i < len(sorted_row) and sorted_row[i] == val: | |||
return i | |||
return None | |||
def _shift_sorted_row_inplace(sorted_row, threshold, delta): | |||
if delta == 0: | |||
return | |||
i = bisect.bisect_left(sorted_row, threshold) | |||
for j in range(i, len(sorted_row)): | |||
sorted_row[j] += delta | |||
def _shift_mark_set(mark_set, threshold, delta): | |||
if delta == 0 or not mark_set: | |||
return mark_set | |||
new = set() | |||
for x in mark_set: | |||
new.add(x + delta if x >= threshold else x) | |||
return new | |||
class ModifyUnpleasant(Exception): | |||
pass | |||
MSG_UNPLEASANT = ( | |||
"Something unpleasant happened. Please contact the author (E-mail: qwerasdfyh@126.com) " | |||
"about the previous pattern so he can improve the rule design." | |||
) | |||
class BasicLaverPattern: | |||
def __init__(self, rows, mask=None): | |||
self.rows = _clone_rows(rows) | |||
if mask is None: | |||
self.mask = [set() for _ in self.rows] | |||
else: | |||
self.mask = _clone_mask(mask) | |||
if self.mask: | |||
self.mask[0] = set() | |||
self._normalize_rows_inplace() | |||
def _normalize_rows_inplace(self, start_row=1): | |||
for r in range(max(1, start_row), len(self.rows)): | |||
row = self.rows[r] | |||
if row and row[-1] == r + 1: | |||
row.pop() | |||
self.mask[r].discard(r + 1) | |||
def clone(self): | |||
return BasicLaverPattern(self.rows, self.mask) | |||
def is_zero(self): | |||
return len(self.rows) == 1 and len(self.rows[0]) == 0 | |||
def is_successor(self): | |||
if self.is_zero(): | |||
return False | |||
last = self.rows[-1] | |||
return len(last) == 2 and last[0] == 0 | |||
def draw(self): | |||
if self.is_zero(): | |||
return | |||
base_list = self.rows[0] | |||
other_lists = self.rows[1:] | |||
if not other_lists: | |||
return | |||
max_len = max((seq[-1] for seq in other_lists if seq), default=0) + 1 | |||
result = [] | |||
for i, seq in enumerate(other_lists, start=1): | |||
line = [' '] * max_len | |||
mset = self.mask[i] | |||
for num in seq: | |||
if 0 <= num < max_len: | |||
line[num] = 'a' if num in mset else 'o' | |||
if i <= len(base_list) and seq: | |||
last_circle_index = seq[-1] | |||
result.append(''.join(line[:last_circle_index + 1]) + f" {base_list[i-1]}") | |||
for line in result: | |||
print(line) | |||
def to_string(self): | |||
if self.is_zero(): | |||
return "" | |||
base_list = self.rows[0] | |||
out = [] | |||
for i in range(1, len(self.rows)): | |||
seq = self.rows[i] | |||
mset = self.mask[i] | |||
parts = [] | |||
for x in reversed(seq): | |||
parts.append(f"*{x}" if x in mset else str(x)) | |||
step = base_list[i - 1] if i - 1 < len(base_list) else 0 | |||
out.append("(" + ",".join(parts) + ")" + str(step)) | |||
return "".join(out) | |||
def cut(self): | |||
if self.is_zero(): | |||
return False | |||
if len(self.rows) <= 1: | |||
return False | |||
if len(self.rows[0]) == 0: | |||
self.rows = [[]] | |||
self.mask = [set()] | |||
return False | |||
self.rows[0].pop() | |||
self.rows.pop() | |||
self.mask.pop() | |||
if len(self.rows) == 1 and len(self.rows[0]) == 0: | |||
self.mask = [set()] | |||
self._normalize_rows_inplace() | |||
return True | |||
def _transmission_penultimate_and_terminal_checked(self, row_idx, n_value): | |||
rows = self.rows | |||
base = rows[0] | |||
if n_value <= 0 or n_value >= len(rows): | |||
return None | |||
row = rows[row_idx] | |||
l_m = base[row_idx - 1] | |||
k = _find_index(row, n_value) | |||
if k is None or k - l_m < 0: | |||
return None | |||
threshold = row[k - l_m] | |||
cur = n_value | |||
visited = {cur} | |||
while True: | |||
if cur <= 0 or cur >= len(rows): | |||
return None | |||
l_s = base[cur - 1] | |||
if len(rows[cur]) < l_s + 1: | |||
return None | |||
nxt = rows[cur][-l_s - 1] | |||
if nxt > threshold: | |||
if nxt + 1 != cur + 1: | |||
if _find_index(rows[cur], nxt + 1) is None: | |||
return None | |||
prev, cur = cur, nxt | |||
if cur <= threshold: | |||
return (prev, cur) | |||
if cur in visited: | |||
return None | |||
visited.add(cur) | |||
def _first_not_copied_in_transmission(self, orig_rows, orig_base, copied_set, row_idx, n_value): | |||
row = orig_rows[row_idx] | |||
l_m = orig_base[row_idx - 1] | |||
k = _find_index(row, n_value) | |||
if k is None or k - l_m < 0: | |||
raise RuntimeError("Unpleasant.") | |||
threshold = row[k - l_m] | |||
cur = n_value | |||
seq = [cur] | |||
visited = {cur} | |||
while True: | |||
l_s = orig_base[cur - 1] | |||
if len(orig_rows[cur]) < l_s + 1: | |||
raise RuntimeError("Unpleasant.") | |||
nxt = orig_rows[cur][-l_s - 1] | |||
seq.append(nxt) | |||
cur = nxt | |||
if cur <= threshold: | |||
break | |||
if cur in visited: | |||
raise RuntimeError("Unpleasant.") | |||
visited.add(cur) | |||
t = seq[-2] | |||
terminal = seq[-1] | |||
tprime = None | |||
for x in seq: | |||
if x not in copied_set: | |||
tprime = x | |||
break | |||
return tprime, t, terminal | |||
def _slice_right_block(self, row_idx, anchor, q): | |||
row = self.rows[row_idx] | |||
pos = _find_index(row, anchor) | |||
if pos is None: | |||
raise RuntimeError("Unpleasant.") | |||
block = row[pos + 1: pos + 1 + q] | |||
if len(block) != q: | |||
raise RuntimeError("Unpleasant.") | |||
return block | |||
def _mark_completion_for_row(self, r, meta, native_done): | |||
base = self.rows[0] | |||
row0 = self.rows[r] | |||
initial_marks = [x for x in row0 if x in self.mask[r]] | |||
before = set(row0) | |||
added_total = 0 | |||
for n in initial_marks: | |||
if _find_index(self.rows[r], n) is None: | |||
continue | |||
if n <= 0 or n >= len(meta): | |||
continue | |||
info = meta[n] | |||
if not info or not info.get("native_generated", False): | |||
continue | |||
tn = self._transmission_penultimate_and_terminal_checked(r, n) | |||
if tn is None: | |||
continue | |||
t, n_terminal = tn | |||
q = native_done.get(t, 0) | |||
if q <= 0: | |||
continue | |||
target_row = t + q | |||
left_block = self._slice_right_block(target_row, n_terminal, q) | |||
right_block = list(range(n + 1, n + q + 1)) | |||
new_vals = set(left_block) | set(right_block) | |||
truly_new = new_vals - before | |||
if truly_new: | |||
added_total += len(truly_new) | |||
before |= truly_new | |||
row_set = set(self.rows[r]) | |||
row_set.update(new_vals) | |||
self.rows[r] = sorted(row_set) | |||
self.mask[r].difference_update(left_block) | |||
self.mask[r].update(right_block) | |||
if added_total > 0: | |||
base[r - 1] += (added_total // 2) | |||
def _shift_values_ge(self, start_row_idx, threshold, delta): | |||
for i in range(start_row_idx, len(self.rows)): | |||
_shift_sorted_row_inplace(self.rows[i], threshold, delta) | |||
self.mask[i] = _shift_mark_set(self.mask[i], threshold, delta) | |||
def _native_completion_step(self, m, meta): | |||
rows = self.rows | |||
base = rows[0] | |||
l = base[m - 1] | |||
e = len(rows[m]) | |||
if e > 2 * l: | |||
return False, 0 | |||
if l <= 0 or e < l: | |||
return False, 0 | |||
s = [rows[m][-l]] | |||
while True: | |||
if s[-1] <= 0 or s[-1] >= len(rows): | |||
return False, 0 | |||
if len(rows[s[-1]]) < 2: | |||
return False, 0 | |||
s.append(rows[s[-1]][-2]) | |||
if len(rows[m]) < l + 1: | |||
return False, 0 | |||
if s[-1] <= rows[m][-l - 1]: | |||
break | |||
k = len(s) - 1 | |||
if k == 1: | |||
return False, 0 | |||
s.pop() | |||
q = k - 1 | |||
if q <= 0: | |||
return False, 0 | |||
marks_m_orig = set(self.mask[m]) | |||
self._shift_values_ge(m, m + 1, q) | |||
if e == 2 * l: | |||
c = rows[m][:] | |||
else: | |||
c = rows[m][:l - 1] + rows[m][l:] | |||
ext = s[1:][::-1] + list(range(m + 1, m + q + 1)) | |||
rows[m].extend(ext) | |||
rows[m].sort() | |||
base[m - 1] += q | |||
d = [] | |||
for i in range(q): | |||
d_i = c + s[q - i:] + list(range(m + 1, m + i + 2)) | |||
d.append(sorted(d_i)) | |||
old_e = e + 1 | |||
base[:] = base[:m - 1] + list(range(old_e - l, old_e - l + q)) + base[m - 1:] | |||
rows[:] = rows[:m] + d + rows[m:] | |||
self.mask[:] = self.mask[:m] + [set() for _ in range(q)] + self.mask[m:] | |||
meta_insert = [{"native_generated": True, "native_q": q} for _ in range(q)] | |||
meta[:] = meta[:m] + meta_insert + meta[m:] | |||
marks_to_propagate = {x + q if x >= m + 1 else x for x in marks_m_orig} | |||
for row_idx in range(m, m + q + 1): | |||
self.mask[row_idx].update(marks_to_propagate) | |||
for j in range(1, q + 1): | |||
self.mask[m + j].update(range(m, m + j)) | |||
self.mask[m + q].discard(m + 1 + q) | |||
self._normalize_rows_inplace(start_row=m) | |||
return True, q | |||
def modify(self, copy_only=False, silent=False): | |||
try: | |||
orig_rows = _clone_rows(self.rows) | |||
orig_mask = _clone_mask(self.mask) | |||
orig_base = orig_rows[0][:] | |||
orig_last_mask = set(orig_mask[-1]) if orig_mask else set() | |||
rows = self.rows | |||
base0 = rows[0] | |||
n_before_cut = len(base0) | |||
l_last = base0[n_before_cut - 1] | |||
b = rows[-1][:] | |||
b0 = b[0] | |||
p_leftmost = b[0] | |||
self.cut() | |||
rows = self.rows | |||
base0 = rows[0] | |||
u = b[-l_last - 1] | |||
v_copy = n_before_cut | |||
base0.extend(orig_base[u - 1: v_copy]) | |||
b_map = {} | |||
limit = len(b) - l_last | |||
for i in range(limit): | |||
key = b[i] | |||
if key not in b_map: | |||
b_map[key] = b[i + l_last] | |||
def map_elem(x): | |||
if x < b0: | |||
return x | |||
if x > u: | |||
return x - u + n_before_cut | |||
return b_map.get(x, -1) | |||
copied_set = set(range(u, v_copy + 1)) | |||
for row_idx in range(u, v_copy + 1): | |||
src_row = orig_rows[row_idx] | |||
new_seq = [] | |||
for elem in src_row: | |||
new_val = map_elem(elem) | |||
if new_val == -1: | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
new_seq.append(new_val) | |||
new_seq.sort() | |||
rows.append(new_seq) | |||
new_marks = set() | |||
src_marks = orig_mask[row_idx] | |||
if src_marks: | |||
l_m = orig_base[row_idx - 1] | |||
for marked_val in src_marks: | |||
if _find_index(orig_rows[row_idx], marked_val) is None: | |||
continue | |||
tprime, t, _terminal = self._first_not_copied_in_transmission( | |||
orig_rows, orig_base, copied_set, row_idx, marked_val | |||
) | |||
keep = False | |||
if t in copied_set: | |||
keep = True | |||
else: | |||
if tprime is not None and tprime < p_leftmost: | |||
keep = True | |||
elif tprime is not None: | |||
u_img = b_map.get(tprime, None) | |||
if u_img is not None and u_img in orig_last_mask: | |||
mv_img = map_elem(marked_val) | |||
if mv_img != -1: | |||
pos_u = _find_index(new_seq, mv_img) | |||
if pos_u is not None: | |||
idx_check = pos_u - l_m + 1 | |||
if idx_check >= 0 and new_seq[idx_check] <= p_leftmost: | |||
keep = True | |||
if keep: | |||
new_marks.add(map_elem(marked_val)) | |||
self.mask.append(new_marks) | |||
if copy_only: | |||
self._normalize_rows_inplace() | |||
return self.clone() | |||
meta = [None] * len(self.rows) | |||
native_done = {} | |||
m = n_before_cut | |||
while True: | |||
base0 = self.rows[0] | |||
if m > len(base0): | |||
break | |||
self._mark_completion_for_row(m, meta, native_done) | |||
did, q = self._native_completion_step(m, meta) | |||
if did: | |||
native_done[m] = q | |||
m += q + 1 | |||
else: | |||
m += 1 | |||
self._normalize_rows_inplace() | |||
return self.clone() | |||
except ModifyUnpleasant: | |||
raise | |||
except RuntimeError as e: | |||
if str(e) == "Unpleasant.": | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
raise | |||
initial_rows = [ | |||
[1, 1, 2, 2, 2], | |||
[0, 1], | |||
[0, 1, 2], | |||
[0, 1, 2, 3], | |||
[0, 1, 2, 3, 4], | |||
[2, 3, 4, 5] | |||
] | |||
initial_mask = [set() for _ in initial_rows] | |||
initial_mask[4] = {3} | |||
def _encode_expr(pat: BasicLaverPattern): | |||
base = pat.rows[0] | |||
expr = [] | |||
for i in range(1, len(pat.rows)): | |||
L = base[i - 1] if (i - 1) < len(base) else 0 | |||
vals_desc = list(reversed(pat.rows[i])) | |||
mset = pat.mask[i] | |||
row = [L] + [[v, (v in mset)] for v in vals_desc] | |||
expr.append(row) | |||
return expr | |||
def _decode_expr(expr): | |||
base = [row[0] for row in expr] | |||
rows = [base] | |||
mask = [set()] | |||
for row in expr: | |||
vals = [x[0] for x in row[1:]] | |||
vals = sorted(set(vals)) | |||
rows.append(vals) | |||
mask.append({x[0] for x in row[1:] if x[1]}) | |||
return BasicLaverPattern(rows, mask) | |||
def _deepcopy_expr(expr): | |||
return [[row[0]] + [[x[0], bool(x[1])] for x in row[1:]] for row in expr] | |||
def _values(row): | |||
return [row[0]] + [x[0] for x in row[1:]] | |||
def _cut_expr(expr): | |||
return _deepcopy_expr(expr[:-1]) | |||
def _pleasant_until(rows, t): | |||
tv = _values(t) | |||
L = t[0] | |||
tcheck = tv[1 + L:] | |||
if not tcheck: | |||
return -1 | |||
tmax = tcheck[0] | |||
tmin = tcheck[-1] | |||
tset = set(tcheck) | |||
for n, s in enumerate(rows): | |||
scheck = _values(s)[1:] | |||
i1 = -1 | |||
for idx, x in enumerate(scheck): | |||
if x < tmax: | |||
i1 = idx | |||
break | |||
i2 = -1 | |||
for idx in range(len(scheck) - 1, -1, -1): | |||
if scheck[idx] > tmin: | |||
i2 = idx | |||
break | |||
if i1 != -1 and i2 != -1 and i1 <= i2: | |||
mid = scheck[i1:i2 + 1] | |||
if any(x not in tset for x in mid): | |||
return n | |||
return -1 | |||
def _seq_from(expr, i, j): | |||
row = expr[i] | |||
val = row[j][0] | |||
L = row[0] | |||
threshold = row[j + L][0] if (j + L) < len(row) else 0 | |||
record = [[i + 1, j], [val]] | |||
while val > threshold: | |||
row = expr[val - 1] | |||
idx = 1 + row[0] | |||
record[-1].append(idx) | |||
val = row[idx][0] if idx < len(row) else 0 | |||
record.append([val]) | |||
record.pop() | |||
return record | |||
def _apv(s_vals, t_vals): | |||
L = t_vals[0] | |||
t_last = t_vals[-1] | |||
t_1 = t_vals[1] | |||
t_1L = t_vals[1 + L] if (1 + L) < len(t_vals) else 0 | |||
out = [] | |||
for x in s_vals: | |||
if x < t_last: | |||
out.append(x) | |||
elif x >= t_1L: | |||
out.append(x - t_1L + t_1) | |||
else: | |||
k = -1 | |||
for idx in range(len(t_vals) - 1, -1, -1): | |||
if t_vals[idx] == x: | |||
k = idx | |||
break | |||
out.append(None if k == -1 else t_vals[k - L]) | |||
return out | |||
def _ap(row_s, row_t): | |||
svals = _values(row_s)[1:] | |||
tvals = _values(row_t) | |||
mapped = _apv(svals, tvals) | |||
return [row_s[0]] + [[x, False] for x in mapped] | |||
def _copy_block(raw, flag): | |||
active = raw[-1] | |||
expr = _cut_expr(raw) | |||
begin = active[1 + active[0]][0] | |||
end = (begin + flag) if (flag != -1) else (len(raw) + 1) | |||
offset = len(raw) - begin | |||
expr.extend([_ap(row, active) for row in raw[begin - 1:end - 1]]) | |||
active_min = active[-1][0] | |||
begin_rowno = begin | |||
for i in range(begin - 1, end - 1): | |||
row = raw[i] | |||
target_row = expr[i + offset] | |||
for j in range(1, len(row)): | |||
if not row[j][1]: | |||
continue | |||
seq = _seq_from(raw, i, j) | |||
nomove = -1 | |||
for k, item in enumerate(seq): | |||
if item[0] < begin_rowno: | |||
nomove = k | |||
break | |||
if nomove == -1: | |||
target_row[j][1] = True | |||
continue | |||
if seq[nomove][0] < active_min: | |||
target_row[j][1] = True | |||
continue | |||
c = seq[nomove - 1][0] + offset | |||
rowc = expr[c - 1] | |||
b = rowc[seq[nomove - 1][1]][0] | |||
idx_check = j + target_row[0] - 1 | |||
left_ok = (idx_check < len(target_row)) and (target_row[idx_check][0] <= active_min) | |||
active_has_b_mark = any((x[0] == b and x[1]) for x in active[1:]) | |||
if left_ok and active_has_b_mark: | |||
target_row[j][1] = True | |||
return expr | |||
def _comp_to(raw, r, already): | |||
expr = _deepcopy_expr(raw) | |||
for j in range(len(raw[r]) - 1, 0, -1): | |||
if not raw[r][j][1]: | |||
continue | |||
n = raw[r][j][0] | |||
seq = _seq_from(raw, r, j) | |||
t = seq[-1][0] | |||
T = already[t - 1] if (t - 1) < len(already) else None | |||
if not T: | |||
continue | |||
q = len(T) | |||
entries = ( | |||
[[x[0], bool(x[1])] for x in expr[r][1:]] + | |||
[[x, False] for x in T] + | |||
[[n + 1 + uu, True] for uu in range(q)] | |||
) | |||
entries.sort(key=lambda z: z[0], reverse=True) | |||
expr[r] = [expr[r][0] + q] + entries | |||
return expr | |||
def _comp_from(raw, r, T): | |||
q = len(T) | |||
expr = [[row[0]] + [[x[0], bool(x[1])] for x in row[1:]] for row in raw[:r]] | |||
if len(raw[r]) < raw[r][0] * 2 + 1: | |||
lr = raw[r][0] | |||
cr = raw[r][1:-raw[r][0]] + raw[r][1 + raw[r][0]:] | |||
else: | |||
lr = raw[r][0] + 1 | |||
cr = raw[r][1:] | |||
need_len = r + q + 1 | |||
if len(expr) < need_len: | |||
expr.extend([None] * (need_len - len(expr))) | |||
for qq in range(q): | |||
entries = ( | |||
[[x[0], bool(x[1])] for x in cr] + | |||
[[x, False] for x in T[:1 + qq]] + | |||
[[raw[r][1][0] + 1 + uu, False] for uu in range(qq)] | |||
) | |||
entries.sort(key=lambda z: z[0], reverse=True) | |||
expr[r + qq] = [lr + qq] + entries | |||
entries = ( | |||
[[x[0], bool(x[1])] for x in raw[r][1:]] + | |||
[[x, False] for x in T] + | |||
[[raw[r][1][0] + 1 + uu, False] for uu in range(q)] | |||
) | |||
entries.sort(key=lambda z: z[0], reverse=True) | |||
expr[r + q] = [raw[r][0] + q] + entries | |||
for qq in range(1, q + 1): | |||
for uu in range(2, 1 + qq + 1): | |||
expr[r + qq][uu][1] = True | |||
threshold = raw[r][1][0] | |||
def m(entry, idx): | |||
if idx == 0: | |||
return entry | |||
vv = entry[0] | |||
if vv <= threshold: | |||
return [vv, bool(entry[1])] | |||
return [vv + q, bool(entry[1])] | |||
for row in raw[r + 1:]: | |||
new_row = [] | |||
for idx, entry in enumerate(row): | |||
new_row.append(m(entry, idx)) | |||
expr.append(new_row) | |||
return expr | |||
def _expand_pleasant_only(raw, FSterm, longer=False): | |||
if FSterm < 0: | |||
FSterm = 0 | |||
active = raw[-1] | |||
L = active[0] | |||
if (1 + L) >= len(active) or (active[1 + L][0] == 0): | |||
return _cut_expr(raw) | |||
begin = active[1 + L][0] | |||
flag = _pleasant_until(raw[begin - 1:-1], active) | |||
if flag != -1: | |||
raise ModifyUnpleasant | |||
expr = _deepcopy_expr(raw) | |||
for _ in range(FSterm): | |||
expr = _copy_block(expr, -1) | |||
expr = _copy_block(expr, 1) if longer else _cut_expr(expr) | |||
already = [] | |||
r = len(raw) - 1 | |||
while r < len(expr): | |||
expr = _comp_to(expr, r, already) | |||
if not (len(expr[r]) <= expr[r][0] * 2 + 1): | |||
r += 1 | |||
continue | |||
idx0 = expr[r][expr[r][0]][0] | |||
T = [idx0] | |||
bound = expr[r][expr[r][0] + 1][0] | |||
while T[0] > bound: | |||
rr = T[0] - 1 | |||
T.insert(0, expr[rr][2][0]) | |||
T = T[1:-1] | |||
if len(T) < 1: | |||
r += 1 | |||
continue | |||
expr = _comp_from(expr, r, T) | |||
while len(already) <= r: | |||
already.append(None) | |||
already[r] = T | |||
r += len(T) + 1 | |||
return expr | |||
def _expand_like_model(pattern: BasicLaverPattern, FSterm: int, longer: bool, silent: bool): | |||
if pattern.is_zero(): | |||
return pattern.clone(), 0 | |||
if pattern.is_successor(): | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
base0 = pattern.rows[0] | |||
if not base0: | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
if FSterm <= 0: | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
try: | |||
raw = _encode_expr(pattern) | |||
res = _expand_pleasant_only(raw, FSterm=FSterm, longer=longer) | |||
p2 = _decode_expr(res) | |||
return p2.clone(), 1 | |||
except ModifyUnpleasant: | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
def _apply_special_one(pattern: BasicLaverPattern, silent=False): | |||
if pattern.is_zero(): | |||
return pattern.clone(), 0 | |||
if pattern.is_successor(): | |||
p = pattern.clone() | |||
p.cut() | |||
return p, 0 | |||
p = pattern.clone() | |||
try: | |||
orig_rows = _clone_rows(p.rows) | |||
orig_mask = _clone_mask(p.mask) | |||
orig_base = orig_rows[0][:] | |||
orig_last_mask = set(orig_mask[-1]) if orig_mask else set() | |||
rows = p.rows | |||
base0 = rows[0] | |||
n_before_cut = len(base0) | |||
if n_before_cut <= 0: | |||
p2 = p.clone() | |||
p2.cut() | |||
return p2, 0 | |||
original_total_rows = len(rows) | |||
l_last = base0[n_before_cut - 1] | |||
b = rows[-1][:] | |||
if not b: | |||
p2 = p.clone() | |||
p2.cut() | |||
return p2, 0 | |||
b0 = b[0] | |||
p_leftmost = b[0] | |||
p.cut() | |||
rows = p.rows | |||
base0 = rows[0] | |||
if l_last < 0 or len(b) < l_last + 1: | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
u = b[-l_last - 1] | |||
if u - 1 < 0 or u - 1 >= len(orig_base): | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
base0.append(orig_base[u - 1]) | |||
b_map = {} | |||
limit = len(b) - l_last | |||
for i in range(limit): | |||
key = b[i] | |||
if key not in b_map: | |||
b_map[key] = b[i + l_last] | |||
def map_elem(x): | |||
if x < b0: | |||
return x | |||
if x > u: | |||
return x - u + n_before_cut | |||
return b_map.get(x, -1) | |||
copied_set = {u} | |||
if u <= 0 or u >= len(orig_rows): | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
src_row = orig_rows[u] | |||
new_seq = [] | |||
for elem in src_row: | |||
new_val = map_elem(elem) | |||
if new_val == -1: | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
raise ModifyUnpleasant | |||
new_seq.append(new_val) | |||
new_seq.sort() | |||
rows.append(new_seq) | |||
new_marks = set() | |||
src_marks = orig_mask[u] | |||
if src_marks: | |||
l_m = orig_base[u - 1] | |||
for marked_val in src_marks: | |||
if _find_index(orig_rows[u], marked_val) is None: | |||
continue | |||
tprime, t, _terminal = p._first_not_copied_in_transmission( | |||
orig_rows, orig_base, copied_set, u, marked_val | |||
) | |||
keep = False | |||
if t in copied_set: | |||
keep = True | |||
else: | |||
if tprime is not None and tprime < p_leftmost: | |||
keep = True | |||
elif tprime is not None: | |||
u_img = b_map.get(tprime, None) | |||
if u_img is not None and u_img in orig_last_mask: | |||
mv_img = map_elem(marked_val) | |||
if mv_img != -1: | |||
pos_u = _find_index(new_seq, mv_img) | |||
if pos_u is not None: | |||
idx_check = pos_u - l_m + 1 | |||
if idx_check >= 0 and new_seq[idx_check] <= p_leftmost: | |||
keep = True | |||
if keep: | |||
mv = map_elem(marked_val) | |||
if mv != -1: | |||
new_marks.add(mv) | |||
p.mask.append(new_marks) | |||
p._normalize_rows_inplace(start_row=len(p.rows) - 1) | |||
meta = [None] * len(p.rows) | |||
m = len(p.rows[0]) | |||
did, q = p._native_completion_step(m, meta) | |||
if did and q > 0: | |||
for _ in range(q): | |||
p.cut() | |||
while len(p.rows) > original_total_rows: | |||
p.cut() | |||
p._normalize_rows_inplace() | |||
return p.clone(), 1 | |||
except ModifyUnpleasant: | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
except RuntimeError as e: | |||
if str(e) == "Unpleasant.": | |||
if not silent: | |||
print(MSG_UNPLEASANT) | |||
q = pattern.clone() | |||
q.cut() | |||
return q, 0 | |||
raise | |||
def _apply_number(pattern, n, silent=False): | |||
if pattern.is_zero(): | |||
return pattern.clone(), 0 | |||
if n == 0: | |||
p = pattern.clone() | |||
p.cut() | |||
return p, 0 | |||
if pattern.is_successor(): | |||
p = pattern.clone() | |||
p.cut() | |||
return p, 0 | |||
if n == 1: | |||
return _apply_special_one(pattern, silent=silent) | |||
FSterm = n - 1 | |||
nxt, ok = _expand_like_model(pattern, FSterm=FSterm, longer=False, silent=silent) | |||
if ok == 0: | |||
return nxt, 0 | |||
return nxt, n | |||
def reconstruct_pattern_list(op_numbers, silent=False): | |||
pattern_list = [BasicLaverPattern(initial_rows, initial_mask)] | |||
executed = [] | |||
for n in op_numbers: | |||
cur = pattern_list[-1] | |||
nxt, actual = _apply_number(cur, n, silent=silent) | |||
executed.append(actual) | |||
pattern_list.append(nxt) | |||
return executed, pattern_list, None | |||
def _cmp_lists(a, b): | |||
la, lb = len(a), len(b) | |||
m = la if la < lb else lb | |||
for i in range(m): | |||
if a[i] < b[i]: | |||
return -1 | |||
if a[i] > b[i]: | |||
return 1 | |||
if la < lb: | |||
return -1 | |||
if la > lb: | |||
return 1 | |||
return 0 | |||
def _row_key_for_compare(pat, row_idx): | |||
base = pat.rows[0] | |||
row = pat.rows[row_idx] | |||
l = base[row_idx - 1] if row_idx - 1 < len(base) else 0 | |||
if l <= 1: | |||
keep = row[:] | |||
else: | |||
if len(row) < l: | |||
keep = row[:] | |||
else: | |||
keep = [row[0]] + row[l:] | |||
keep = keep[::-1] | |||
return keep | |||
def compare_patterns(a, b): | |||
ra = len(a.rows) - 1 | |||
rb = len(b.rows) - 1 | |||
m = ra if ra < rb else rb | |||
for i in range(1, m + 1): | |||
ka = _row_key_for_compare(a, i) | |||
kb = _row_key_for_compare(b, i) | |||
c = _cmp_lists(ka, kb) | |||
if c != 0: | |||
return c | |||
if ra < rb: | |||
return -1 | |||
if ra > rb: | |||
return 1 | |||
return 0 | |||
def _is_prefix(seg, full): | |||
if len(seg.rows) > len(full.rows): | |||
return False | |||
if seg.rows[0] != full.rows[0][:len(seg.rows[0])]: | |||
return False | |||
for i in range(1, len(seg.rows)): | |||
if seg.rows[i] != full.rows[i]: | |||
return False | |||
return True | |||
def _is_proper_prefix(seg, full): | |||
return _is_prefix(seg, full) and (len(seg.rows) < len(full.rows)) | |||
def _pattern_equal(a: BasicLaverPattern, b: BasicLaverPattern): | |||
return a.rows == b.rows and a.mask == b.mask | |||
def _pattern_signature(p: BasicLaverPattern): | |||
rows_sig = tuple(tuple(r) for r in p.rows) | |||
mask_sig = tuple(tuple(sorted(s)) for s in p.mask) | |||
return rows_sig, mask_sig | |||
_EXPAND_COUNTS_CACHE = {} | |||
def _expand_row_counts_from(start_pat: BasicLaverPattern, n: int): | |||
if n < 0: | |||
n = 0 | |||
key = (_pattern_signature(start_pat), n) | |||
if key in _EXPAND_COUNTS_CACHE: | |||
return _EXPAND_COUNTS_CACHE[key][:] | |||
counts = [len(start_pat.rows)] | |||
for k in range(1, n + 1): | |||
res, _act = _apply_number(start_pat, k, silent=True) | |||
counts.append(len(res.rows)) | |||
_EXPAND_COUNTS_CACHE[key] = counts[:] | |||
return counts | |||
def _simplify(op_numbers, pattern_list): | |||
target = pattern_list[-1].clone() | |||
s = op_numbers[:] | |||
executed, pats, _ = reconstruct_pattern_list(s, silent=True) | |||
s = executed | |||
pattern_list = pats | |||
i = len(s) - 1 | |||
while i >= 0: | |||
if i >= len(s): | |||
i = len(s) - 1 | |||
if i < 0: | |||
break | |||
if s[i] == 0: | |||
i -= 1 | |||
continue | |||
while True: | |||
if i >= len(s): | |||
break | |||
n = s[i] | |||
z = 0 | |||
j = i + 1 | |||
while j < len(s) and s[j] == 0: | |||
z += 1 | |||
j += 1 | |||
candidate = None | |||
need = None | |||
if n == 1: | |||
if z >= 1: | |||
candidate = s[:i] + s[i + 1:] | |||
else: | |||
break | |||
else: | |||
start_pat = pattern_list[i] | |||
counts = _expand_row_counts_from(start_pat, n) | |||
need = counts[n] - counts[n - 1] | |||
if need < 0: | |||
need = 0 | |||
if z < need: | |||
break | |||
candidate = s[:] | |||
candidate[i] = n - 1 | |||
if need > 0: | |||
del candidate[i + 1: i + 1 + need] | |||
cand_exec, cand_pats, _ = reconstruct_pattern_list(candidate, silent=True) | |||
if not cand_pats or not _pattern_equal(cand_pats[-1], target): | |||
break | |||
s = cand_exec | |||
pattern_list = cand_pats | |||
if i >= len(s): | |||
break | |||
if s[i] == 0: | |||
break | |||
i = min(i, len(s) - 1) | |||
i -= 1 | |||
while i >= 0 and s[i] == 0: | |||
i -= 1 | |||
executed, pattern_list, _ = reconstruct_pattern_list(s, silent=True) | |||
return executed, pattern_list | |||
def _seq_str(nums): | |||
return ",".join(str(x) for x in nums) | |||
def _parse_o_string(s): | |||
s = s.strip() | |||
if s == "": | |||
return BasicLaverPattern([[]], [set()]), None | |||
pos = 0 | |||
rows_desc = [] | |||
steps = [] | |||
n = len(s) | |||
while pos < n: | |||
if s[pos] != "(": | |||
return None, "error" | |||
pos += 1 | |||
close = s.find(")", pos) | |||
if close == -1: | |||
return None, "error" | |||
inside = s[pos:close].strip() | |||
pos = close + 1 | |||
nums = [] | |||
if inside != "": | |||
parts = inside.split(",") | |||
for part in parts: | |||
part = part.strip() | |||
if part.startswith("*"): | |||
part = part[1:].strip() | |||
if part == "" or (not part.isdigit()): | |||
return None, "error" | |||
nums.append(int(part)) | |||
nums_asc = sorted(nums) | |||
for i in range(1, len(nums_asc)): | |||
if nums_asc[i] == nums_asc[i - 1]: | |||
return None, "error" | |||
if pos >= n or (not s[pos].isdigit()): | |||
return None, "error" | |||
j = pos | |||
while j < n and s[j].isdigit(): | |||
j += 1 | |||
step = int(s[pos:j]) | |||
pos = j | |||
rows_desc.append(nums_asc) | |||
steps.append(step) | |||
rows = [steps[:]] + rows_desc | |||
mask = [set()] + [set() for _ in rows_desc] | |||
return BasicLaverPattern(rows, mask), None | |||
def _read_find_pattern(I): | |||
initial = BasicLaverPattern(initial_rows, initial_mask) | |||
C = initial.clone() | |||
ops = [] | |||
pats = [C.clone()] | |||
if compare_patterns(C, I) <= 0: | |||
return C, ops, pats | |||
MAX_OUTER = 50000 | |||
MAX_N = 20000 | |||
outer = 0 | |||
while outer < MAX_OUTER: | |||
outer += 1 | |||
if compare_patterns(C, I) == 0: | |||
return C, ops, pats | |||
n = 0 | |||
while n <= MAX_N: | |||
Cn, actual = _apply_number(C, n, silent=True) | |||
if _is_proper_prefix(Cn, I): | |||
n += 1 | |||
continue | |||
if (compare_patterns(Cn, I) < 0) and (not _is_prefix(Cn, I)): | |||
return C, ops, pats | |||
if compare_patterns(Cn, I) >= 0: | |||
if Cn.rows == C.rows and Cn.mask == C.mask: | |||
return C, ops, pats | |||
C = Cn | |||
ops.append(actual) | |||
pats.append(C.clone()) | |||
break | |||
n += 1 | |||
else: | |||
return C, ops, pats | |||
return C, ops, pats | |||
def main_program(): | |||
op_numbers = [] | |||
executed, pattern_list, _ = reconstruct_pattern_list(op_numbers, silent=True) | |||
op_numbers = executed | |||
while True: | |||
cur = pattern_list[-1] | |||
print("\nCurrent pattern:") | |||
if cur.is_zero(): | |||
print("(empty)") | |||
else: | |||
cur.draw() | |||
print(f"Operation sequence: {_seq_str(op_numbers)}") | |||
if cur.is_zero(): | |||
pattern_type = "Zero" | |||
elif cur.is_successor(): | |||
pattern_type = "Successor" | |||
else: | |||
pattern_type = "Limit" | |||
msg = f"This is a {pattern_type} pattern." | |||
msg += " Natural Number: Operation." | |||
msg += " O: Output." | |||
msg += " R: Read." | |||
if len(pattern_list) > 1: | |||
msg += " U: Undo." | |||
msg += " S: Simplify." | |||
msg += " I: Input operations." | |||
print(msg) | |||
user_input = input("Enter your operation: ").strip().upper() | |||
if user_input.isdigit(): | |||
n_in = int(user_input) | |||
nxt, actual = _apply_number(cur, n_in, silent=False) | |||
pattern_list.append(nxt) | |||
op_numbers.append(actual) | |||
if actual == 0: | |||
print("Applied cut operation.") | |||
else: | |||
print(f"Applied operation {actual}.") | |||
continue | |||
if user_input == 'O': | |||
print(cur.to_string()) | |||
continue | |||
if user_input == 'R': | |||
raw = input("Input pattern string (from O): ").strip() | |||
pat, err = _parse_o_string(raw) | |||
if err: | |||
print("error") | |||
continue | |||
found, ops, pats = _read_find_pattern(pat) | |||
op_numbers = ops | |||
pattern_list = pats | |||
print(found.to_string()) | |||
continue | |||
if user_input == 'U' and len(pattern_list) > 1: | |||
op_numbers = op_numbers[:-1] | |||
executed, pattern_list, _ = reconstruct_pattern_list(op_numbers, silent=True) | |||
op_numbers = executed | |||
print("Undo the last operation.") | |||
continue | |||
if user_input == 'S': | |||
new_ops, new_patterns = _simplify(op_numbers, pattern_list) | |||
if new_ops != op_numbers: | |||
op_numbers = new_ops | |||
pattern_list = new_patterns | |||
print(f"Simplified operation sequence: {_seq_str(op_numbers)}") | |||
else: | |||
print("No further simplifications possible.") | |||
continue | |||
if user_input == 'I': | |||
raw = input("Input the operation sequence (comma-separated natural numbers, e.g., 3,0,2,1): ").strip() | |||
if raw == "": | |||
parsed = [] | |||
else: | |||
parts = [p.strip() for p in raw.split(",")] | |||
if any(p == "" or (not p.isdigit()) for p in parts): | |||
print("error") | |||
continue | |||
parsed = [int(p) for p in parts] | |||
executed, pattern_list, _ = reconstruct_pattern_list(parsed, silent=True) | |||
op_numbers = executed | |||
continue | |||
print("Invalid operation. Please try again.") | |||
if __name__ == "__main__": | |||
main_program() | |||
</syntaxhighlight> | |||
[[分类:记号]] | [[分类:记号]] | ||
2026年2月22日 (日) 15:59的版本
记号简介
无限基本Laver图案(Infinite Basic Laver Pattern)是由test_alpha0的记号Basic Laver Pattren改造而来。IBLP目前尚不理想,还存在许多的坏图案。test_alpha0规定其极限表达式为(1,0)1(2,1,0)1(3,2,1,0)2(4,3,2)1(5,4,3,2)2(6,5,4)1,因为现在认为在该图案下方不存在坏图案,而其上方不远处就出现了很多坏图案。尽管如此,IBLP仍然被认为是目前最强的记号。本页面介绍的规则对应NE上的DEN2。
定义
定义1 (IBLP)
一个IBLP是一个四元组,
其中:
。
对每个,是严格递增的非负整数序列,满足。
对每个,步长。
对每个,标记集合
约定所有编号从 1 开始; 表示第 k 个元素,表示倒数第 j 个元素。
定义2 (长行、中等行、短行)
行 称为长行若,称为中等行若,称为短行若 。
定义3 (零图案、后继图案、极限图案)
若 n = 0,称 A 为零图案。若 ,称 A 为后继图案。否则称 A 为极限图案。
定义4 (行比较键)
令。定义保留序列
定义行键为(即逆序,从大到小)。
定义5 (IBLP的大小比较)
给定两个 IBLP ,从 起逐行比较其
行键 的字典序;第一处不同决定大小。若前行都相等,则行数较小者更小。
定义6 (一行的根)
对,定义.
定义7 (传递序列)
固定行 i。若,定义阈值 。令 ,并在时令 。若存在最小 使,则定义,否则称 无定义。若 ,亦称无定义。
定义8 (部分函数)
设 A 非零且有至少1行。令 。定义部分函数 :
若,则;
若,则;
若,则;
其余情形 无定义。
定义9 (copy 的行与步长)
设 A 行数为,令 。若 copy 过程中出现 无定义,则无定义。否则定义 行数为 ,满足:
对;
对每个,令源行,目标行i,定义,要求。
定义10 (copy的标记过滤)
延续上一定义。对目标行中元素,令
为其唯一来源(即 )。则 当且仅当:
,并且满足下列之一:
- 有定义且;
- 令为中首次出现的的元素(等价于“最大的
的元素”)。要么;要么存在 k 使,并且若 x 在 中的位置 为 q(从 1 开始),则当 时需满足
定义11 (E(A,0))
若 A 非零且 n ≥ 1,则 E(A, 0) 为删除最后一行后的图案(行数变为 n − 1,其余行、步长、标记保持不变)。
定义12 (E(A, m)的copy阶段 (m≥1))
设A是极限图案。令。若第一次 无定义,则称 A 为坏图案并定义。否则定义
,
并令
接下来对B做补全,初始行号 (这里 n 为原始 A 的行数)。补全过程允许使用临时记录映射 (行号 有限序列),结束后丢弃。
定义13 (标记补全)
固定当前行 r。令并按升序枚举其元素(该集合在本轮开始时冻结,新产生标记不加入本轮)。对每个 依次:
若 无定义则跳过;
令。若不存在或为空则跳过,否则记 ;
若对所有都有,那么把s 及 加入 ,并重新排序去重使
之严格递增。更新标记:s 中元素不标记, 全标
记,其余标记保留(按上述规则修正)。更新步长:。
定义14 (原生补全)
固定当前行 r。若为长行,则原生补全不执行并返回 0。否则令.
构造序列 s:令 ,并在 存在且 时令 并把 追加到 s,直至无法继续。令 。若 返回 0;若,令 并继续:
在 r 后插入 t 行,使原本行号 的行号整体加 t;
仅对原本行号 的那些行,在其中把所有 的元素加 t,并同步
标记集合,步长不变;
令旧行及旧步长为 。将旧行替换为 t + 1 行:
.
标记规则:除s和r+t外均标记。
对,由构造 :删除元素,并删除元素 ,去除 的标记,并令 。
中等行例外:若 且 为中等行,则在构造 时不删除 且不减步长(令 ),但仍删除 并去除 的标记。
原生补全返回t。
定义15 (补全主循环(用于 E(A, m)))
令初始(n 为原始 A 的行数)。当 r 不超过当前 B 的行数时循环:先对行 r 执行标记补全,再执行原生补全得到 t,然后更新 。当 r 越界时补全结束,丢弃 ,所得 B 即为。
展开器
iblp的展开器在NE上可以找到,同时也可以使用如下Python代码直观地看到每个图案的行为。
import bisect
def _clone_rows(rows):
return [row[:] for row in rows]
def _clone_mask(mask):
return [set(s) for s in mask]
def _find_index(sorted_row, val):
i = bisect.bisect_left(sorted_row, val)
if i < len(sorted_row) and sorted_row[i] == val:
return i
return None
def _shift_sorted_row_inplace(sorted_row, threshold, delta):
if delta == 0:
return
i = bisect.bisect_left(sorted_row, threshold)
for j in range(i, len(sorted_row)):
sorted_row[j] += delta
def _shift_mark_set(mark_set, threshold, delta):
if delta == 0 or not mark_set:
return mark_set
new = set()
for x in mark_set:
new.add(x + delta if x >= threshold else x)
return new
class ModifyUnpleasant(Exception):
pass
MSG_UNPLEASANT = (
"Something unpleasant happened. Please contact the author (E-mail: qwerasdfyh@126.com) "
"about the previous pattern so he can improve the rule design."
)
class BasicLaverPattern:
def __init__(self, rows, mask=None):
self.rows = _clone_rows(rows)
if mask is None:
self.mask = [set() for _ in self.rows]
else:
self.mask = _clone_mask(mask)
if self.mask:
self.mask[0] = set()
self._normalize_rows_inplace()
def _normalize_rows_inplace(self, start_row=1):
for r in range(max(1, start_row), len(self.rows)):
row = self.rows[r]
if row and row[-1] == r + 1:
row.pop()
self.mask[r].discard(r + 1)
def clone(self):
return BasicLaverPattern(self.rows, self.mask)
def is_zero(self):
return len(self.rows) == 1 and len(self.rows[0]) == 0
def is_successor(self):
if self.is_zero():
return False
last = self.rows[-1]
return len(last) == 2 and last[0] == 0
def draw(self):
if self.is_zero():
return
base_list = self.rows[0]
other_lists = self.rows[1:]
if not other_lists:
return
max_len = max((seq[-1] for seq in other_lists if seq), default=0) + 1
result = []
for i, seq in enumerate(other_lists, start=1):
line = [' '] * max_len
mset = self.mask[i]
for num in seq:
if 0 <= num < max_len:
line[num] = 'a' if num in mset else 'o'
if i <= len(base_list) and seq:
last_circle_index = seq[-1]
result.append(''.join(line[:last_circle_index + 1]) + f" {base_list[i-1]}")
for line in result:
print(line)
def to_string(self):
if self.is_zero():
return ""
base_list = self.rows[0]
out = []
for i in range(1, len(self.rows)):
seq = self.rows[i]
mset = self.mask[i]
parts = []
for x in reversed(seq):
parts.append(f"*{x}" if x in mset else str(x))
step = base_list[i - 1] if i - 1 < len(base_list) else 0
out.append("(" + ",".join(parts) + ")" + str(step))
return "".join(out)
def cut(self):
if self.is_zero():
return False
if len(self.rows) <= 1:
return False
if len(self.rows[0]) == 0:
self.rows = [[]]
self.mask = [set()]
return False
self.rows[0].pop()
self.rows.pop()
self.mask.pop()
if len(self.rows) == 1 and len(self.rows[0]) == 0:
self.mask = [set()]
self._normalize_rows_inplace()
return True
def _transmission_penultimate_and_terminal_checked(self, row_idx, n_value):
rows = self.rows
base = rows[0]
if n_value <= 0 or n_value >= len(rows):
return None
row = rows[row_idx]
l_m = base[row_idx - 1]
k = _find_index(row, n_value)
if k is None or k - l_m < 0:
return None
threshold = row[k - l_m]
cur = n_value
visited = {cur}
while True:
if cur <= 0 or cur >= len(rows):
return None
l_s = base[cur - 1]
if len(rows[cur]) < l_s + 1:
return None
nxt = rows[cur][-l_s - 1]
if nxt > threshold:
if nxt + 1 != cur + 1:
if _find_index(rows[cur], nxt + 1) is None:
return None
prev, cur = cur, nxt
if cur <= threshold:
return (prev, cur)
if cur in visited:
return None
visited.add(cur)
def _first_not_copied_in_transmission(self, orig_rows, orig_base, copied_set, row_idx, n_value):
row = orig_rows[row_idx]
l_m = orig_base[row_idx - 1]
k = _find_index(row, n_value)
if k is None or k - l_m < 0:
raise RuntimeError("Unpleasant.")
threshold = row[k - l_m]
cur = n_value
seq = [cur]
visited = {cur}
while True:
l_s = orig_base[cur - 1]
if len(orig_rows[cur]) < l_s + 1:
raise RuntimeError("Unpleasant.")
nxt = orig_rows[cur][-l_s - 1]
seq.append(nxt)
cur = nxt
if cur <= threshold:
break
if cur in visited:
raise RuntimeError("Unpleasant.")
visited.add(cur)
t = seq[-2]
terminal = seq[-1]
tprime = None
for x in seq:
if x not in copied_set:
tprime = x
break
return tprime, t, terminal
def _slice_right_block(self, row_idx, anchor, q):
row = self.rows[row_idx]
pos = _find_index(row, anchor)
if pos is None:
raise RuntimeError("Unpleasant.")
block = row[pos + 1: pos + 1 + q]
if len(block) != q:
raise RuntimeError("Unpleasant.")
return block
def _mark_completion_for_row(self, r, meta, native_done):
base = self.rows[0]
row0 = self.rows[r]
initial_marks = [x for x in row0 if x in self.mask[r]]
before = set(row0)
added_total = 0
for n in initial_marks:
if _find_index(self.rows[r], n) is None:
continue
if n <= 0 or n >= len(meta):
continue
info = meta[n]
if not info or not info.get("native_generated", False):
continue
tn = self._transmission_penultimate_and_terminal_checked(r, n)
if tn is None:
continue
t, n_terminal = tn
q = native_done.get(t, 0)
if q <= 0:
continue
target_row = t + q
left_block = self._slice_right_block(target_row, n_terminal, q)
right_block = list(range(n + 1, n + q + 1))
new_vals = set(left_block) | set(right_block)
truly_new = new_vals - before
if truly_new:
added_total += len(truly_new)
before |= truly_new
row_set = set(self.rows[r])
row_set.update(new_vals)
self.rows[r] = sorted(row_set)
self.mask[r].difference_update(left_block)
self.mask[r].update(right_block)
if added_total > 0:
base[r - 1] += (added_total // 2)
def _shift_values_ge(self, start_row_idx, threshold, delta):
for i in range(start_row_idx, len(self.rows)):
_shift_sorted_row_inplace(self.rows[i], threshold, delta)
self.mask[i] = _shift_mark_set(self.mask[i], threshold, delta)
def _native_completion_step(self, m, meta):
rows = self.rows
base = rows[0]
l = base[m - 1]
e = len(rows[m])
if e > 2 * l:
return False, 0
if l <= 0 or e < l:
return False, 0
s = [rows[m][-l]]
while True:
if s[-1] <= 0 or s[-1] >= len(rows):
return False, 0
if len(rows[s[-1]]) < 2:
return False, 0
s.append(rows[s[-1]][-2])
if len(rows[m]) < l + 1:
return False, 0
if s[-1] <= rows[m][-l - 1]:
break
k = len(s) - 1
if k == 1:
return False, 0
s.pop()
q = k - 1
if q <= 0:
return False, 0
marks_m_orig = set(self.mask[m])
self._shift_values_ge(m, m + 1, q)
if e == 2 * l:
c = rows[m][:]
else:
c = rows[m][:l - 1] + rows[m][l:]
ext = s[1:][::-1] + list(range(m + 1, m + q + 1))
rows[m].extend(ext)
rows[m].sort()
base[m - 1] += q
d = []
for i in range(q):
d_i = c + s[q - i:] + list(range(m + 1, m + i + 2))
d.append(sorted(d_i))
old_e = e + 1
base[:] = base[:m - 1] + list(range(old_e - l, old_e - l + q)) + base[m - 1:]
rows[:] = rows[:m] + d + rows[m:]
self.mask[:] = self.mask[:m] + [set() for _ in range(q)] + self.mask[m:]
meta_insert = [{"native_generated": True, "native_q": q} for _ in range(q)]
meta[:] = meta[:m] + meta_insert + meta[m:]
marks_to_propagate = {x + q if x >= m + 1 else x for x in marks_m_orig}
for row_idx in range(m, m + q + 1):
self.mask[row_idx].update(marks_to_propagate)
for j in range(1, q + 1):
self.mask[m + j].update(range(m, m + j))
self.mask[m + q].discard(m + 1 + q)
self._normalize_rows_inplace(start_row=m)
return True, q
def modify(self, copy_only=False, silent=False):
try:
orig_rows = _clone_rows(self.rows)
orig_mask = _clone_mask(self.mask)
orig_base = orig_rows[0][:]
orig_last_mask = set(orig_mask[-1]) if orig_mask else set()
rows = self.rows
base0 = rows[0]
n_before_cut = len(base0)
l_last = base0[n_before_cut - 1]
b = rows[-1][:]
b0 = b[0]
p_leftmost = b[0]
self.cut()
rows = self.rows
base0 = rows[0]
u = b[-l_last - 1]
v_copy = n_before_cut
base0.extend(orig_base[u - 1: v_copy])
b_map = {}
limit = len(b) - l_last
for i in range(limit):
key = b[i]
if key not in b_map:
b_map[key] = b[i + l_last]
def map_elem(x):
if x < b0:
return x
if x > u:
return x - u + n_before_cut
return b_map.get(x, -1)
copied_set = set(range(u, v_copy + 1))
for row_idx in range(u, v_copy + 1):
src_row = orig_rows[row_idx]
new_seq = []
for elem in src_row:
new_val = map_elem(elem)
if new_val == -1:
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
new_seq.append(new_val)
new_seq.sort()
rows.append(new_seq)
new_marks = set()
src_marks = orig_mask[row_idx]
if src_marks:
l_m = orig_base[row_idx - 1]
for marked_val in src_marks:
if _find_index(orig_rows[row_idx], marked_val) is None:
continue
tprime, t, _terminal = self._first_not_copied_in_transmission(
orig_rows, orig_base, copied_set, row_idx, marked_val
)
keep = False
if t in copied_set:
keep = True
else:
if tprime is not None and tprime < p_leftmost:
keep = True
elif tprime is not None:
u_img = b_map.get(tprime, None)
if u_img is not None and u_img in orig_last_mask:
mv_img = map_elem(marked_val)
if mv_img != -1:
pos_u = _find_index(new_seq, mv_img)
if pos_u is not None:
idx_check = pos_u - l_m + 1
if idx_check >= 0 and new_seq[idx_check] <= p_leftmost:
keep = True
if keep:
new_marks.add(map_elem(marked_val))
self.mask.append(new_marks)
if copy_only:
self._normalize_rows_inplace()
return self.clone()
meta = [None] * len(self.rows)
native_done = {}
m = n_before_cut
while True:
base0 = self.rows[0]
if m > len(base0):
break
self._mark_completion_for_row(m, meta, native_done)
did, q = self._native_completion_step(m, meta)
if did:
native_done[m] = q
m += q + 1
else:
m += 1
self._normalize_rows_inplace()
return self.clone()
except ModifyUnpleasant:
raise
except RuntimeError as e:
if str(e) == "Unpleasant.":
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
raise
initial_rows = [
[1, 1, 2, 2, 2],
[0, 1],
[0, 1, 2],
[0, 1, 2, 3],
[0, 1, 2, 3, 4],
[2, 3, 4, 5]
]
initial_mask = [set() for _ in initial_rows]
initial_mask[4] = {3}
def _encode_expr(pat: BasicLaverPattern):
base = pat.rows[0]
expr = []
for i in range(1, len(pat.rows)):
L = base[i - 1] if (i - 1) < len(base) else 0
vals_desc = list(reversed(pat.rows[i]))
mset = pat.mask[i]
row = [L] + [[v, (v in mset)] for v in vals_desc]
expr.append(row)
return expr
def _decode_expr(expr):
base = [row[0] for row in expr]
rows = [base]
mask = [set()]
for row in expr:
vals = [x[0] for x in row[1:]]
vals = sorted(set(vals))
rows.append(vals)
mask.append({x[0] for x in row[1:] if x[1]})
return BasicLaverPattern(rows, mask)
def _deepcopy_expr(expr):
return [[row[0]] + [[x[0], bool(x[1])] for x in row[1:]] for row in expr]
def _values(row):
return [row[0]] + [x[0] for x in row[1:]]
def _cut_expr(expr):
return _deepcopy_expr(expr[:-1])
def _pleasant_until(rows, t):
tv = _values(t)
L = t[0]
tcheck = tv[1 + L:]
if not tcheck:
return -1
tmax = tcheck[0]
tmin = tcheck[-1]
tset = set(tcheck)
for n, s in enumerate(rows):
scheck = _values(s)[1:]
i1 = -1
for idx, x in enumerate(scheck):
if x < tmax:
i1 = idx
break
i2 = -1
for idx in range(len(scheck) - 1, -1, -1):
if scheck[idx] > tmin:
i2 = idx
break
if i1 != -1 and i2 != -1 and i1 <= i2:
mid = scheck[i1:i2 + 1]
if any(x not in tset for x in mid):
return n
return -1
def _seq_from(expr, i, j):
row = expr[i]
val = row[j][0]
L = row[0]
threshold = row[j + L][0] if (j + L) < len(row) else 0
record = [[i + 1, j], [val]]
while val > threshold:
row = expr[val - 1]
idx = 1 + row[0]
record[-1].append(idx)
val = row[idx][0] if idx < len(row) else 0
record.append([val])
record.pop()
return record
def _apv(s_vals, t_vals):
L = t_vals[0]
t_last = t_vals[-1]
t_1 = t_vals[1]
t_1L = t_vals[1 + L] if (1 + L) < len(t_vals) else 0
out = []
for x in s_vals:
if x < t_last:
out.append(x)
elif x >= t_1L:
out.append(x - t_1L + t_1)
else:
k = -1
for idx in range(len(t_vals) - 1, -1, -1):
if t_vals[idx] == x:
k = idx
break
out.append(None if k == -1 else t_vals[k - L])
return out
def _ap(row_s, row_t):
svals = _values(row_s)[1:]
tvals = _values(row_t)
mapped = _apv(svals, tvals)
return [row_s[0]] + [[x, False] for x in mapped]
def _copy_block(raw, flag):
active = raw[-1]
expr = _cut_expr(raw)
begin = active[1 + active[0]][0]
end = (begin + flag) if (flag != -1) else (len(raw) + 1)
offset = len(raw) - begin
expr.extend([_ap(row, active) for row in raw[begin - 1:end - 1]])
active_min = active[-1][0]
begin_rowno = begin
for i in range(begin - 1, end - 1):
row = raw[i]
target_row = expr[i + offset]
for j in range(1, len(row)):
if not row[j][1]:
continue
seq = _seq_from(raw, i, j)
nomove = -1
for k, item in enumerate(seq):
if item[0] < begin_rowno:
nomove = k
break
if nomove == -1:
target_row[j][1] = True
continue
if seq[nomove][0] < active_min:
target_row[j][1] = True
continue
c = seq[nomove - 1][0] + offset
rowc = expr[c - 1]
b = rowc[seq[nomove - 1][1]][0]
idx_check = j + target_row[0] - 1
left_ok = (idx_check < len(target_row)) and (target_row[idx_check][0] <= active_min)
active_has_b_mark = any((x[0] == b and x[1]) for x in active[1:])
if left_ok and active_has_b_mark:
target_row[j][1] = True
return expr
def _comp_to(raw, r, already):
expr = _deepcopy_expr(raw)
for j in range(len(raw[r]) - 1, 0, -1):
if not raw[r][j][1]:
continue
n = raw[r][j][0]
seq = _seq_from(raw, r, j)
t = seq[-1][0]
T = already[t - 1] if (t - 1) < len(already) else None
if not T:
continue
q = len(T)
entries = (
[[x[0], bool(x[1])] for x in expr[r][1:]] +
[[x, False] for x in T] +
[[n + 1 + uu, True] for uu in range(q)]
)
entries.sort(key=lambda z: z[0], reverse=True)
expr[r] = [expr[r][0] + q] + entries
return expr
def _comp_from(raw, r, T):
q = len(T)
expr = [[row[0]] + [[x[0], bool(x[1])] for x in row[1:]] for row in raw[:r]]
if len(raw[r]) < raw[r][0] * 2 + 1:
lr = raw[r][0]
cr = raw[r][1:-raw[r][0]] + raw[r][1 + raw[r][0]:]
else:
lr = raw[r][0] + 1
cr = raw[r][1:]
need_len = r + q + 1
if len(expr) < need_len:
expr.extend([None] * (need_len - len(expr)))
for qq in range(q):
entries = (
[[x[0], bool(x[1])] for x in cr] +
[[x, False] for x in T[:1 + qq]] +
[[raw[r][1][0] + 1 + uu, False] for uu in range(qq)]
)
entries.sort(key=lambda z: z[0], reverse=True)
expr[r + qq] = [lr + qq] + entries
entries = (
[[x[0], bool(x[1])] for x in raw[r][1:]] +
[[x, False] for x in T] +
[[raw[r][1][0] + 1 + uu, False] for uu in range(q)]
)
entries.sort(key=lambda z: z[0], reverse=True)
expr[r + q] = [raw[r][0] + q] + entries
for qq in range(1, q + 1):
for uu in range(2, 1 + qq + 1):
expr[r + qq][uu][1] = True
threshold = raw[r][1][0]
def m(entry, idx):
if idx == 0:
return entry
vv = entry[0]
if vv <= threshold:
return [vv, bool(entry[1])]
return [vv + q, bool(entry[1])]
for row in raw[r + 1:]:
new_row = []
for idx, entry in enumerate(row):
new_row.append(m(entry, idx))
expr.append(new_row)
return expr
def _expand_pleasant_only(raw, FSterm, longer=False):
if FSterm < 0:
FSterm = 0
active = raw[-1]
L = active[0]
if (1 + L) >= len(active) or (active[1 + L][0] == 0):
return _cut_expr(raw)
begin = active[1 + L][0]
flag = _pleasant_until(raw[begin - 1:-1], active)
if flag != -1:
raise ModifyUnpleasant
expr = _deepcopy_expr(raw)
for _ in range(FSterm):
expr = _copy_block(expr, -1)
expr = _copy_block(expr, 1) if longer else _cut_expr(expr)
already = []
r = len(raw) - 1
while r < len(expr):
expr = _comp_to(expr, r, already)
if not (len(expr[r]) <= expr[r][0] * 2 + 1):
r += 1
continue
idx0 = expr[r][expr[r][0]][0]
T = [idx0]
bound = expr[r][expr[r][0] + 1][0]
while T[0] > bound:
rr = T[0] - 1
T.insert(0, expr[rr][2][0])
T = T[1:-1]
if len(T) < 1:
r += 1
continue
expr = _comp_from(expr, r, T)
while len(already) <= r:
already.append(None)
already[r] = T
r += len(T) + 1
return expr
def _expand_like_model(pattern: BasicLaverPattern, FSterm: int, longer: bool, silent: bool):
if pattern.is_zero():
return pattern.clone(), 0
if pattern.is_successor():
q = pattern.clone()
q.cut()
return q, 0
base0 = pattern.rows[0]
if not base0:
q = pattern.clone()
q.cut()
return q, 0
if FSterm <= 0:
q = pattern.clone()
q.cut()
return q, 0
try:
raw = _encode_expr(pattern)
res = _expand_pleasant_only(raw, FSterm=FSterm, longer=longer)
p2 = _decode_expr(res)
return p2.clone(), 1
except ModifyUnpleasant:
if not silent:
print(MSG_UNPLEASANT)
q = pattern.clone()
q.cut()
return q, 0
def _apply_special_one(pattern: BasicLaverPattern, silent=False):
if pattern.is_zero():
return pattern.clone(), 0
if pattern.is_successor():
p = pattern.clone()
p.cut()
return p, 0
p = pattern.clone()
try:
orig_rows = _clone_rows(p.rows)
orig_mask = _clone_mask(p.mask)
orig_base = orig_rows[0][:]
orig_last_mask = set(orig_mask[-1]) if orig_mask else set()
rows = p.rows
base0 = rows[0]
n_before_cut = len(base0)
if n_before_cut <= 0:
p2 = p.clone()
p2.cut()
return p2, 0
original_total_rows = len(rows)
l_last = base0[n_before_cut - 1]
b = rows[-1][:]
if not b:
p2 = p.clone()
p2.cut()
return p2, 0
b0 = b[0]
p_leftmost = b[0]
p.cut()
rows = p.rows
base0 = rows[0]
if l_last < 0 or len(b) < l_last + 1:
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
u = b[-l_last - 1]
if u - 1 < 0 or u - 1 >= len(orig_base):
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
base0.append(orig_base[u - 1])
b_map = {}
limit = len(b) - l_last
for i in range(limit):
key = b[i]
if key not in b_map:
b_map[key] = b[i + l_last]
def map_elem(x):
if x < b0:
return x
if x > u:
return x - u + n_before_cut
return b_map.get(x, -1)
copied_set = {u}
if u <= 0 or u >= len(orig_rows):
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
src_row = orig_rows[u]
new_seq = []
for elem in src_row:
new_val = map_elem(elem)
if new_val == -1:
if not silent:
print(MSG_UNPLEASANT)
raise ModifyUnpleasant
new_seq.append(new_val)
new_seq.sort()
rows.append(new_seq)
new_marks = set()
src_marks = orig_mask[u]
if src_marks:
l_m = orig_base[u - 1]
for marked_val in src_marks:
if _find_index(orig_rows[u], marked_val) is None:
continue
tprime, t, _terminal = p._first_not_copied_in_transmission(
orig_rows, orig_base, copied_set, u, marked_val
)
keep = False
if t in copied_set:
keep = True
else:
if tprime is not None and tprime < p_leftmost:
keep = True
elif tprime is not None:
u_img = b_map.get(tprime, None)
if u_img is not None and u_img in orig_last_mask:
mv_img = map_elem(marked_val)
if mv_img != -1:
pos_u = _find_index(new_seq, mv_img)
if pos_u is not None:
idx_check = pos_u - l_m + 1
if idx_check >= 0 and new_seq[idx_check] <= p_leftmost:
keep = True
if keep:
mv = map_elem(marked_val)
if mv != -1:
new_marks.add(mv)
p.mask.append(new_marks)
p._normalize_rows_inplace(start_row=len(p.rows) - 1)
meta = [None] * len(p.rows)
m = len(p.rows[0])
did, q = p._native_completion_step(m, meta)
if did and q > 0:
for _ in range(q):
p.cut()
while len(p.rows) > original_total_rows:
p.cut()
p._normalize_rows_inplace()
return p.clone(), 1
except ModifyUnpleasant:
q = pattern.clone()
q.cut()
return q, 0
except RuntimeError as e:
if str(e) == "Unpleasant.":
if not silent:
print(MSG_UNPLEASANT)
q = pattern.clone()
q.cut()
return q, 0
raise
def _apply_number(pattern, n, silent=False):
if pattern.is_zero():
return pattern.clone(), 0
if n == 0:
p = pattern.clone()
p.cut()
return p, 0
if pattern.is_successor():
p = pattern.clone()
p.cut()
return p, 0
if n == 1:
return _apply_special_one(pattern, silent=silent)
FSterm = n - 1
nxt, ok = _expand_like_model(pattern, FSterm=FSterm, longer=False, silent=silent)
if ok == 0:
return nxt, 0
return nxt, n
def reconstruct_pattern_list(op_numbers, silent=False):
pattern_list = [BasicLaverPattern(initial_rows, initial_mask)]
executed = []
for n in op_numbers:
cur = pattern_list[-1]
nxt, actual = _apply_number(cur, n, silent=silent)
executed.append(actual)
pattern_list.append(nxt)
return executed, pattern_list, None
def _cmp_lists(a, b):
la, lb = len(a), len(b)
m = la if la < lb else lb
for i in range(m):
if a[i] < b[i]:
return -1
if a[i] > b[i]:
return 1
if la < lb:
return -1
if la > lb:
return 1
return 0
def _row_key_for_compare(pat, row_idx):
base = pat.rows[0]
row = pat.rows[row_idx]
l = base[row_idx - 1] if row_idx - 1 < len(base) else 0
if l <= 1:
keep = row[:]
else:
if len(row) < l:
keep = row[:]
else:
keep = [row[0]] + row[l:]
keep = keep[::-1]
return keep
def compare_patterns(a, b):
ra = len(a.rows) - 1
rb = len(b.rows) - 1
m = ra if ra < rb else rb
for i in range(1, m + 1):
ka = _row_key_for_compare(a, i)
kb = _row_key_for_compare(b, i)
c = _cmp_lists(ka, kb)
if c != 0:
return c
if ra < rb:
return -1
if ra > rb:
return 1
return 0
def _is_prefix(seg, full):
if len(seg.rows) > len(full.rows):
return False
if seg.rows[0] != full.rows[0][:len(seg.rows[0])]:
return False
for i in range(1, len(seg.rows)):
if seg.rows[i] != full.rows[i]:
return False
return True
def _is_proper_prefix(seg, full):
return _is_prefix(seg, full) and (len(seg.rows) < len(full.rows))
def _pattern_equal(a: BasicLaverPattern, b: BasicLaverPattern):
return a.rows == b.rows and a.mask == b.mask
def _pattern_signature(p: BasicLaverPattern):
rows_sig = tuple(tuple(r) for r in p.rows)
mask_sig = tuple(tuple(sorted(s)) for s in p.mask)
return rows_sig, mask_sig
_EXPAND_COUNTS_CACHE = {}
def _expand_row_counts_from(start_pat: BasicLaverPattern, n: int):
if n < 0:
n = 0
key = (_pattern_signature(start_pat), n)
if key in _EXPAND_COUNTS_CACHE:
return _EXPAND_COUNTS_CACHE[key][:]
counts = [len(start_pat.rows)]
for k in range(1, n + 1):
res, _act = _apply_number(start_pat, k, silent=True)
counts.append(len(res.rows))
_EXPAND_COUNTS_CACHE[key] = counts[:]
return counts
def _simplify(op_numbers, pattern_list):
target = pattern_list[-1].clone()
s = op_numbers[:]
executed, pats, _ = reconstruct_pattern_list(s, silent=True)
s = executed
pattern_list = pats
i = len(s) - 1
while i >= 0:
if i >= len(s):
i = len(s) - 1
if i < 0:
break
if s[i] == 0:
i -= 1
continue
while True:
if i >= len(s):
break
n = s[i]
z = 0
j = i + 1
while j < len(s) and s[j] == 0:
z += 1
j += 1
candidate = None
need = None
if n == 1:
if z >= 1:
candidate = s[:i] + s[i + 1:]
else:
break
else:
start_pat = pattern_list[i]
counts = _expand_row_counts_from(start_pat, n)
need = counts[n] - counts[n - 1]
if need < 0:
need = 0
if z < need:
break
candidate = s[:]
candidate[i] = n - 1
if need > 0:
del candidate[i + 1: i + 1 + need]
cand_exec, cand_pats, _ = reconstruct_pattern_list(candidate, silent=True)
if not cand_pats or not _pattern_equal(cand_pats[-1], target):
break
s = cand_exec
pattern_list = cand_pats
if i >= len(s):
break
if s[i] == 0:
break
i = min(i, len(s) - 1)
i -= 1
while i >= 0 and s[i] == 0:
i -= 1
executed, pattern_list, _ = reconstruct_pattern_list(s, silent=True)
return executed, pattern_list
def _seq_str(nums):
return ",".join(str(x) for x in nums)
def _parse_o_string(s):
s = s.strip()
if s == "":
return BasicLaverPattern([[]], [set()]), None
pos = 0
rows_desc = []
steps = []
n = len(s)
while pos < n:
if s[pos] != "(":
return None, "error"
pos += 1
close = s.find(")", pos)
if close == -1:
return None, "error"
inside = s[pos:close].strip()
pos = close + 1
nums = []
if inside != "":
parts = inside.split(",")
for part in parts:
part = part.strip()
if part.startswith("*"):
part = part[1:].strip()
if part == "" or (not part.isdigit()):
return None, "error"
nums.append(int(part))
nums_asc = sorted(nums)
for i in range(1, len(nums_asc)):
if nums_asc[i] == nums_asc[i - 1]:
return None, "error"
if pos >= n or (not s[pos].isdigit()):
return None, "error"
j = pos
while j < n and s[j].isdigit():
j += 1
step = int(s[pos:j])
pos = j
rows_desc.append(nums_asc)
steps.append(step)
rows = [steps[:]] + rows_desc
mask = [set()] + [set() for _ in rows_desc]
return BasicLaverPattern(rows, mask), None
def _read_find_pattern(I):
initial = BasicLaverPattern(initial_rows, initial_mask)
C = initial.clone()
ops = []
pats = [C.clone()]
if compare_patterns(C, I) <= 0:
return C, ops, pats
MAX_OUTER = 50000
MAX_N = 20000
outer = 0
while outer < MAX_OUTER:
outer += 1
if compare_patterns(C, I) == 0:
return C, ops, pats
n = 0
while n <= MAX_N:
Cn, actual = _apply_number(C, n, silent=True)
if _is_proper_prefix(Cn, I):
n += 1
continue
if (compare_patterns(Cn, I) < 0) and (not _is_prefix(Cn, I)):
return C, ops, pats
if compare_patterns(Cn, I) >= 0:
if Cn.rows == C.rows and Cn.mask == C.mask:
return C, ops, pats
C = Cn
ops.append(actual)
pats.append(C.clone())
break
n += 1
else:
return C, ops, pats
return C, ops, pats
def main_program():
op_numbers = []
executed, pattern_list, _ = reconstruct_pattern_list(op_numbers, silent=True)
op_numbers = executed
while True:
cur = pattern_list[-1]
print("\nCurrent pattern:")
if cur.is_zero():
print("(empty)")
else:
cur.draw()
print(f"Operation sequence: {_seq_str(op_numbers)}")
if cur.is_zero():
pattern_type = "Zero"
elif cur.is_successor():
pattern_type = "Successor"
else:
pattern_type = "Limit"
msg = f"This is a {pattern_type} pattern."
msg += " Natural Number: Operation."
msg += " O: Output."
msg += " R: Read."
if len(pattern_list) > 1:
msg += " U: Undo."
msg += " S: Simplify."
msg += " I: Input operations."
print(msg)
user_input = input("Enter your operation: ").strip().upper()
if user_input.isdigit():
n_in = int(user_input)
nxt, actual = _apply_number(cur, n_in, silent=False)
pattern_list.append(nxt)
op_numbers.append(actual)
if actual == 0:
print("Applied cut operation.")
else:
print(f"Applied operation {actual}.")
continue
if user_input == 'O':
print(cur.to_string())
continue
if user_input == 'R':
raw = input("Input pattern string (from O): ").strip()
pat, err = _parse_o_string(raw)
if err:
print("error")
continue
found, ops, pats = _read_find_pattern(pat)
op_numbers = ops
pattern_list = pats
print(found.to_string())
continue
if user_input == 'U' and len(pattern_list) > 1:
op_numbers = op_numbers[:-1]
executed, pattern_list, _ = reconstruct_pattern_list(op_numbers, silent=True)
op_numbers = executed
print("Undo the last operation.")
continue
if user_input == 'S':
new_ops, new_patterns = _simplify(op_numbers, pattern_list)
if new_ops != op_numbers:
op_numbers = new_ops
pattern_list = new_patterns
print(f"Simplified operation sequence: {_seq_str(op_numbers)}")
else:
print("No further simplifications possible.")
continue
if user_input == 'I':
raw = input("Input the operation sequence (comma-separated natural numbers, e.g., 3,0,2,1): ").strip()
if raw == "":
parsed = []
else:
parts = [p.strip() for p in raw.split(",")]
if any(p == "" or (not p.isdigit()) for p in parts):
print("error")
continue
parsed = [int(p) for p in parts]
executed, pattern_list, _ = reconstruct_pattern_list(parsed, silent=True)
op_numbers = executed
continue
print("Invalid operation. Please try again.")
if __name__ == "__main__":
main_program()