sliding-cubes-api / algorithm_2d_1.py
quynhanh2610's picture
Update algorithm_2d_1.py
6850fea verified
Raw
History Blame Contribute Delete
21.8 kB
# algorithm_2d_1.py
from collections import deque
import heapq
from utils import (
to_tuple_set,
to_list,
is_connected,
is_move_connected,
potential,
apply_move,
make_step_info,
build_response,
is_finished_2d
)
# ============================================================
# 1. Hình học move 2D: slide và convex transition
# ============================================================
def is_slide_2d(c, e, blocks_set):
"""
Slide trong 2D dựa trên 4-cycle.
c và e phải kề cạnh.
Tồn tại một phía của cạnh c-e sao cho hai ô còn lại của hình vuông 2x2 đều có block.
"""
dx = e[0] - c[0]
dy = e[1] - c[1]
if abs(dx) + abs(dy) != 1:
return False
# Hai vector vuông góc với hướng di chuyển
p1 = (-dy, dx)
p2 = (dy, -dx)
side1_a = (c[0] + p1[0], c[1] + p1[1])
side1_b = (e[0] + p1[0], e[1] + p1[1])
side2_a = (c[0] + p2[0], c[1] + p2[1])
side2_b = (e[0] + p2[0], e[1] + p2[1])
if side1_a in blocks_set and side1_b in blocks_set:
return True
if side2_a in blocks_set and side2_b in blocks_set:
return True
return False
def is_convex_2d(c, e, blocks_set):
"""
Convex transition trong 2D.
c và e nằm chéo nhau trong một hình vuông 2x2.
Trong hai ô còn lại, đúng một ô có block làm điểm tựa.
"""
dx = abs(c[0] - e[0])
dy = abs(c[1] - e[1])
if dx != 1 or dy != 1:
return False
n1 = (c[0], e[1])
n2 = (e[0], c[1])
in_n1 = n1 in blocks_set
in_n2 = n2 in blocks_set
return in_n1 ^ in_n2
def get_move_type_2d(c, e, blocks_set):
if is_slide_2d(c, e, blocks_set):
return "slide"
if is_convex_2d(c, e, blocks_set):
return "convex"
return None
def bounding_search_area_2d(blocks_set, margin=2):
min_x = min(x for x, y in blocks_set) - margin
max_x = max(x for x, y in blocks_set) + margin
min_y = min(y for x, y in blocks_set) - margin
max_y = max(y for x, y in blocks_set) + margin
return min_x, max_x, min_y, max_y
def generate_valid_moves_2d(blocks_set):
"""
Sinh tất cả move hợp lệ quanh cấu hình hiện tại.
Điều kiện:
- ô đích trống
- không dùng tọa độ âm
- là slide hoặc convex
- C \ {c} liên thông
- cấu hình sau move liên thông
"""
moves = []
if not blocks_set:
return moves
min_x, max_x, min_y, max_y = bounding_search_area_2d(blocks_set, margin=2)
empty_cells = [
(x, y)
for x in range(min_x, max_x + 1)
for y in range(min_y, max_y + 1)
if (x, y) not in blocks_set and x >= 0 and y >= 0
]
for c in blocks_set:
remaining = set(blocks_set)
remaining.remove(c)
if not is_connected(remaining, 2):
continue
for e in empty_cells:
move_type = get_move_type_2d(c, e, blocks_set)
if move_type is None:
continue
if is_move_connected(blocks_set, c, e, 2):
moves.append((c, e, move_type))
return moves
# ============================================================
# 2. Đánh giá cấu hình: potential + holes + compact target
# ============================================================
def cell_potential_2d(cell):
"""
Potential của một ô 2D, coi z = 0.
"""
x, y = cell
if y > 1:
w = 3
elif y == 1:
w = 2
else:
w = 1
return w * (x + 2 * y)
def finished_holes_2d(blocks_set):
"""
Trả về tập các ô còn thiếu để mọi block hiện tại trở thành finished.
Nếu có block (x,y), thì mọi ô trong hình chữ nhật
{0..x} x {0..y} phải có mặt.
"""
holes = set()
for x, y in blocks_set:
for i in range(x + 1):
for j in range(y + 1):
if (i, j) not in blocks_set:
holes.add((i, j))
return holes
def compact_target_2d(n):
"""
Tạo một cấu hình target compact gồm n ô.
Đây là down-set gần gốc, được xây bằng cách thêm dần ô có potential nhỏ nhất
mà vẫn giữ tính finished/down-set.
"""
if n <= 0:
return set()
target = {(0, 0)}
frontier = {(1, 0), (0, 1)}
while len(target) < n:
candidates = []
for cell in frontier:
x, y = cell
if x < 0 or y < 0:
continue
# Một ô được thêm nếu các predecessor cần thiết đã có.
ok = True
if x > 0 and (x - 1, y) not in target:
ok = False
if y > 0 and (x, y - 1) not in target:
ok = False
if ok:
heapq.heappush(candidates, (cell_potential_2d(cell), x + y, x, y, cell))
if not candidates:
break
_, _, _, _, chosen = heapq.heappop(candidates)
target.add(chosen)
frontier.discard(chosen)
x, y = chosen
frontier.add((x + 1, y))
frontier.add((x, y + 1))
return target
def manhattan(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
def target_distance_2d(blocks_set, target):
"""
Khoảng cách xấp xỉ giữa cấu hình hiện tại và target.
Dùng khoảng cách Manhattan hai chiều:
- mỗi block hiện tại đến target gần nhất
- mỗi ô target đến block hiện tại gần nhất
"""
if not blocks_set or not target:
return 0
blocks = list(blocks_set)
target_cells = list(target)
d1 = 0
for b in blocks:
d1 += min(manhattan(b, t) for t in target_cells)
d2 = 0
for t in target_cells:
d2 += min(manhattan(t, b) for b in blocks)
return d1 + d2
def compaction_score_2d(blocks_set):
"""
Score dùng để tránh kẹt local optimum.
Khác với potential, score phạt mạnh:
- các lỗ khiến cấu hình chưa finished
- khoảng cách tới target compact
Score nhỏ hơn nghĩa là cấu hình tốt hơn.
"""
pot = potential(blocks_set, 2)
holes = finished_holes_2d(blocks_set)
target = compact_target_2d(len(blocks_set))
dist = target_distance_2d(blocks_set, target)
return pot + 25 * len(holes) + 3 * dist
def is_better_state(before, after):
"""
Kiểm tra after có tốt hơn before không.
Ưu tiên:
1. finished
2. score giảm
3. potential giảm
"""
if is_finished_2d(after):
return True
before_score = compaction_score_2d(before)
after_score = compaction_score_2d(after)
if after_score < before_score:
return True
if potential(after, 2) < potential(before, 2):
return True
return False
# ============================================================
# 3. Các operation paper-inspired dạng một bước
# ============================================================
def one_step_candidates(blocks_set, operation_name, filter_func=None):
"""
Sinh candidate move cho một operation.
Khác bản cũ:
- Không chỉ xét potential giảm.
- Cho phép move nếu score tổng thể tốt hơn.
"""
result = []
for c, e, move_type in generate_valid_moves_2d(blocks_set):
if filter_func is not None and not filter_func(c, e, move_type):
continue
next_blocks = apply_move(blocks_set, c, e)
if is_better_state(blocks_set, next_blocks):
result.append((c, e, move_type, operation_name, next_blocks))
return result
def choose_best_candidate(blocks_set, candidates):
if not candidates:
return None
best = None
best_key = None
for c, e, move_type, operation, next_blocks in candidates:
key = (
0 if is_finished_2d(next_blocks) else 1,
compaction_score_2d(next_blocks),
potential(next_blocks, 2),
e[1],
e[0]
)
if best is None or key < best_key:
best = (c, e, move_type, operation, next_blocks, potential(next_blocks, 2))
best_key = key
return best
def local_y_reduction(blocks_set):
"""
Bước 1: Local y-reduction.
Ưu tiên kéo ô xuống dưới.
"""
def filt(c, e, move_type):
return e[1] < c[1]
candidates = one_step_candidates(
blocks_set,
"local_y_reduction",
filt
)
return choose_best_candidate(blocks_set, candidates)
def is_in_nontrivial_column(c, blocks_set):
"""
Kiểm tra c có thuộc cột dọc độ dài >= 2 không.
"""
x, _ = c
return sum(1 for bx, by in blocks_set if bx == x) >= 2
def column_shove(blocks_set):
"""
Bước 2: Column shove bản heuristic.
Ưu tiên move từ một cột dọc, hướng xuống hoặc sang trái.
"""
def filt(c, e, move_type):
if not is_in_nontrivial_column(c, blocks_set):
return False
return e[1] <= c[1] or e[0] < c[0]
candidates = one_step_candidates(
blocks_set,
"column_shove",
filt
)
return choose_best_candidate(blocks_set, candidates)
def local_potential_reduction(blocks_set):
"""
Bước 3: Local potential reduction.
Tìm bất kỳ move một bước nào làm tốt score hoặc potential.
"""
candidates = one_step_candidates(
blocks_set,
"local_potential_reduction"
)
return choose_best_candidate(blocks_set, candidates)
# ============================================================
# 4. Low / high components trong 2D
# ============================================================
def connected_components_2d(cells):
cells = set(cells)
if not cells:
return []
components = []
unvisited = set(cells)
while unvisited:
start = next(iter(unvisited))
comp = {start}
queue = deque([start])
unvisited.remove(start)
while queue:
x, y = queue.popleft()
for nb in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
if nb in unvisited:
unvisited.remove(nb)
comp.add(nb)
queue.append(nb)
components.append(comp)
return components
def get_low_high_components_2d(blocks_set):
low_cells = {b for b in blocks_set if b[1] == 0}
high_cells = {b for b in blocks_set if b[1] > 0}
return connected_components_2d(low_cells), connected_components_2d(high_cells)
def get_root_low_component(low_components):
if not low_components:
return None
for comp in low_components:
if (0, 0) in comp:
return comp
return low_components[0]
def non_root_low_components(blocks_set):
low_components, _ = get_low_high_components_2d(blocks_set)
root = get_root_low_component(low_components)
if root is None:
return []
return [comp for comp in low_components if comp != root]
def is_small_low_component(component):
"""
Heuristic 2D:
component nhỏ nếu số ô nhỏ hơn khoảng cách từ ô trái nhất về gốc.
"""
if not component:
return False
min_x = min(x for x, y in component)
return len(component) < min_x + 1
def handling_low_components(blocks_set):
"""
Bước 4-5: Handling low components.
"""
comps = non_root_low_components(blocks_set)
if not comps:
return None
cells = set()
for comp in comps:
cells.update(comp)
def filt(c, e, move_type):
if c not in cells:
return False
return e[0] < c[0] or e[1] == 0
candidates = one_step_candidates(
blocks_set,
"handling_low_components",
filt
)
return choose_best_candidate(blocks_set, candidates)
def small_low_components(blocks_set):
"""
Bước 6: Small low components.
"""
comps = non_root_low_components(blocks_set)
small_cells = set()
for comp in comps:
if is_small_low_component(comp):
small_cells.update(comp)
if not small_cells:
return None
def filt(c, e, move_type):
return c in small_cells and (e[0] <= c[0] or e[1] <= c[1])
candidates = one_step_candidates(
blocks_set,
"small_low_components",
filt
)
return choose_best_candidate(blocks_set, candidates)
def big_low_components(blocks_set):
"""
Bước 7: Big low components.
"""
comps = non_root_low_components(blocks_set)
big_cells = set()
for comp in comps:
if not is_small_low_component(comp):
big_cells.update(comp)
if not big_cells:
return None
def filt(c, e, move_type):
return c in big_cells and e[0] < c[0]
candidates = one_step_candidates(
blocks_set,
"big_low_components",
filt
)
return choose_best_candidate(blocks_set, candidates)
# ============================================================
# 5. Macro escape / Beam search
# ============================================================
def move_sort_key(blocks_set, move):
"""
Sắp xếp move để beam search ưu tiên move có vẻ tốt.
"""
c, e, move_type = move
next_blocks = apply_move(blocks_set, c, e)
return (
0 if is_finished_2d(next_blocks) else 1,
compaction_score_2d(next_blocks),
potential(next_blocks, 2),
e[1],
e[0]
)
def beam_escape_sequence_2d(
blocks_set,
max_depth=5,
beam_width=80,
branch_limit=35
):
"""
Tìm một chuỗi move ngắn để thoát kẹt.
Khác greedy:
- Cho phép potential tăng tạm thời.
- Chỉ cần sau vài bước score tốt hơn hoặc finished.
"""
start_score = compaction_score_2d(blocks_set)
start_pot = potential(blocks_set, 2)
start_state = tuple(sorted(blocks_set))
beam = [
{
"blocks": set(blocks_set),
"sequence": [],
"score": start_score,
"potential": start_pot
}
]
visited = {start_state}
best_sequence = None
best_key = None
for depth in range(1, max_depth + 1):
next_beam = []
for node in beam:
current_blocks = node["blocks"]
moves = generate_valid_moves_2d(current_blocks)
moves = sorted(
moves,
key=lambda m: move_sort_key(current_blocks, m)
)[:branch_limit]
for c, e, move_type in moves:
after = apply_move(current_blocks, c, e)
state = tuple(sorted(after))
if state in visited:
continue
visited.add(state)
seq = node["sequence"] + [(c, e, move_type, "macro_escape")]
after_score = compaction_score_2d(after)
after_pot = potential(after, 2)
key = (
0 if is_finished_2d(after) else 1,
after_score,
after_pot,
len(seq)
)
# Điều kiện nhận chuỗi:
# finished hoặc score giảm rõ ràng hoặc potential giảm rõ ràng sau vài bước
accepted = (
is_finished_2d(after)
or after_score < start_score
or (depth >= 2 and after_pot < start_pot)
)
if accepted:
if best_sequence is None or key < best_key:
best_sequence = seq
best_key = key
next_beam.append(
{
"blocks": after,
"sequence": seq,
"score": after_score,
"potential": after_pot
}
)
if best_sequence is not None:
return best_sequence
next_beam.sort(
key=lambda node: (
node["score"],
node["potential"],
len(node["sequence"])
)
)
beam = next_beam[:beam_width]
if not beam:
break
return None
# ============================================================
# 6. Chọn operation theo pipeline
# ============================================================
def choose_one_step_paper_move_2d(blocks_set):
"""
Thử lần lượt các operation một bước.
"""
operations = [
local_y_reduction,
column_shove,
local_potential_reduction,
handling_low_components,
small_low_components,
big_low_components
]
for op in operations:
move = op(blocks_set)
if move is not None:
return move
return None
def choose_next_sequence_2d(blocks_set):
"""
Trả về một sequence move.
Thường sequence chỉ có 1 move.
Nếu greedy bị kẹt, dùng macro_escape để tìm chuỗi nhiều bước.
"""
one_step = choose_one_step_paper_move_2d(blocks_set)
if one_step is not None:
c, e, move_type, operation, next_blocks, next_pot = one_step
return [(c, e, move_type, operation)]
macro_seq = beam_escape_sequence_2d(
blocks_set,
max_depth=5,
beam_width=80,
branch_limit=35
)
if macro_seq:
return macro_seq
return None
# ============================================================
# 7. Vòng lặp chính
# ============================================================
def compact_2d_1(blocks, max_steps):
"""
Thuật toán 2D paper-inspired cải tiến.
Giữ nguyên input/output.
Bên trong dùng:
- guided operation one-step
- macro_escape beam search để tránh local optimum
"""
blocks_set = to_tuple_set(blocks)
curr_blocks = blocks_set
curr_pot = potential(curr_blocks, 2)
initial_info = {
"blocks": to_list(curr_blocks),
"potential": curr_pot,
"is_connected": True,
"is_finished": is_finished_2d(curr_blocks)
}
steps = []
status = "completed" if is_finished_2d(curr_blocks) else "running"
if status == "completed":
final_info = {
"blocks": to_list(curr_blocks),
"potential": curr_pot,
"is_connected": True,
"is_finished": True
}
return build_response(
True,
"Compaction completed successfully.",
2,
initial_info,
steps,
final_info,
status,
algorithm="paper_inspired_2d"
)
step_idx = 1
while step_idx <= max_steps:
if is_finished_2d(curr_blocks):
status = "completed"
break
sequence = choose_next_sequence_2d(curr_blocks)
if sequence is None:
status = "no_valid_move_found"
break
for c, e, move_type, operation in sequence:
if step_idx > max_steps:
status = "max_steps_reached"
break
# Có thể sau khi chạy vài bước trong sequence, move tiếp theo không còn hợp lệ.
# Khi đó thoát sequence và chọn lại ở vòng ngoài.
if c not in curr_blocks or e in curr_blocks:
break
current_move_type = get_move_type_2d(c, e, curr_blocks)
if current_move_type is None:
break
if not is_move_connected(curr_blocks, c, e, 2):
break
blocks_before = set(curr_blocks)
pot_before = curr_pot
next_blocks = apply_move(curr_blocks, c, e)
next_pot = potential(next_blocks, 2)
finished_after = is_finished_2d(next_blocks)
connected_after = is_connected(next_blocks, 2)
step_info = make_step_info(
step_idx=step_idx,
from_c=c,
to_c=e,
move_type=current_move_type,
blocks_before=blocks_before,
blocks_after=next_blocks,
pot_before=pot_before,
pot_after=next_pot,
connected_after=connected_after,
finished_after=finished_after,
operation=operation
)
steps.append(step_info)
curr_blocks = next_blocks
curr_pot = next_pot
step_idx += 1
if finished_after:
status = "completed"
break
if status in ["completed", "max_steps_reached"]:
break
if status == "running":
status = "max_steps_reached" if step_idx > max_steps else "no_valid_move_found"
final_finished = is_finished_2d(curr_blocks)
if final_finished:
status = "completed"
final_info = {
"blocks": to_list(curr_blocks),
"potential": curr_pot,
"is_connected": is_connected(curr_blocks, 2),
"is_finished": final_finished
}
if status == "completed":
message = "Compaction completed successfully."
elif status == "max_steps_reached":
message = "Maximum number of steps reached before finishing compaction."
else:
message = "No valid move found."
return build_response(
True,
message,
2,
initial_info,
steps,
final_info,
status,
algorithm="paper_inspired_2d"
)