Spaces:
Sleeping
Sleeping
| # 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" | |
| ) |