neurogolf-solver / medal-solvers /task255_solver_265.py
rogermt's picture
Task 255 solver: 265/265 PERFECT (all train+test+arc-gen pass)
2e5b81a verified
Raw
History Blame
9.15 kB
"""Task 255 solver — 265/265 pass (ALL examples correct).
Algorithm:
1. Find largest empty rect → erode non-boundary sides → core
2. PHASE 1: RIGHT/LEFT extensions (single pass, dynamic eligibility from core)
3. PHASE 2: Sub-rect post-processing for runs with wide gaps (static eligibility, gap >= 4)
4. PHASE 3: DOWN/UP extensions (height>=3 guard + adjacent no-fg col extension)
5. PHASE 4: Second RIGHT/LEFT pass (width>=3 guard, cascades from UP/DOWN vertical arms)
6. Mask to background (inp==0) only
Key insights that solved all 4 previously-failing cases:
- [116],[218]: Sub-rect in static-eligible rows extends >= 4 beyond main rect → use eroded sub-rect boundary
- [155],[173]: height>=3 guard on DOWN/UP prevents short RIGHT/LEFT arms from cascading perpendicular
- [130]: Adjacent no-fg cols extend the UP/DOWN eligible run; second RIGHT/LEFT pass handles cascade from vertical arm
Validated: 3/3 train + 1/1 test + 261/261 arc-gen = 265/265 PASS
"""
import numpy as np
import json
def find_largest_empty_rect(inp):
h, w = inp.shape
heights = np.zeros(w, dtype=int)
best_area, best = 0, (0, 0, 0, 0)
for r in range(h):
for c in range(w):
heights[c] = heights[c] + 1 if inp[r, c] == 0 else 0
stack = []
for c in range(w + 1):
cur_h = heights[c] if c < w else 0
start = c
while stack and stack[-1][1] > cur_h:
idx, sh = stack.pop()
area = sh * (c - idx)
if area > best_area:
best_area = area
best = (r - sh + 1, idx, sh, c - idx)
start = idx
if not stack or cur_h > stack[-1][1]:
stack.append((start, cur_h))
return best
def erode_range(start, end, grid_max):
s, e = start, end
if s > 0: s += 1
if e < grid_max: e -= 1
return s, e
def find_contiguous_runs(indices):
if not indices: return []
runs, start, prev = [], indices[0], indices[0]
for i in indices[1:]:
if i == prev + 1: prev = i
else: runs.append((start, prev)); start = prev = i
runs.append((start, prev))
return runs
def apply_rule(inp):
h, w = inp.shape
tr, lc, rh, rw = find_largest_empty_rect(inp)
main_r1, main_r2 = tr, tr + rh - 1
main_c1, main_c2 = lc, lc + rw - 1
core_r1, core_r2 = erode_range(main_r1, main_r2, h - 1)
core_c1, core_c2 = erode_range(main_c1, main_c2, w - 1)
result = np.zeros((h, w), dtype=bool)
result[core_r1:core_r2+1, core_c1:core_c2+1] = True
# PHASE 1: RIGHT/LEFT (dynamic eligibility, single pass)
right_elig = []
for r in range(h):
rc = np.where(result[r])[0]
if len(rc) == 0: continue
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[-1] < rc[0]:
right_elig.append(r)
for rs, re in find_contiguous_runs(sorted(right_elig)):
es, ee = erode_range(rs, re, h - 1)
if es <= ee:
for r in range(es, ee + 1):
rc = np.where(result[r])[0]
if len(rc) > 0: result[r, rc[-1]+1:] = True
left_elig = []
for r in range(h):
rc = np.where(result[r])[0]
if len(rc) == 0: continue
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[0] > rc[-1]:
left_elig.append(r)
for rs, re in find_contiguous_runs(sorted(left_elig)):
es, ee = erode_range(rs, re, h - 1)
if es <= ee:
for r in range(es, ee + 1):
rc = np.where(result[r])[0]
if len(rc) > 0: result[r, :rc[0]] = True
# PHASE 2: Sub-rect post-processing (static eligibility, threshold >= 4)
static_right = []
for r in range(h):
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[-1] < core_c1:
static_right.append(r)
for rs, re in find_contiguous_runs(sorted(static_right)):
sub = inp[rs:re+1, :]
sub_tr, sub_lc, sub_rh, sub_rw = find_largest_empty_rect(sub)
if main_c1 - sub_lc >= 4:
e_r1, e_r2 = erode_range(rs + sub_tr, rs + sub_tr + sub_rh - 1, h - 1)
e_c1, _ = erode_range(sub_lc, sub_lc + sub_rw - 1, w - 1)
if e_r1 <= e_r2:
for r in range(e_r1, e_r2 + 1):
result[r, e_c1:] = True
static_left = []
for r in range(h):
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[0] > core_c2:
static_left.append(r)
for rs, re in find_contiguous_runs(sorted(static_left)):
sub = inp[rs:re+1, :]
sub_tr, sub_lc, sub_rh, sub_rw = find_largest_empty_rect(sub)
sub_c2 = sub_lc + sub_rw - 1
if sub_c2 - main_c2 >= 4:
e_r1, e_r2 = erode_range(rs + sub_tr, rs + sub_tr + sub_rh - 1, h - 1)
_, e_c2 = erode_range(sub_lc, sub_c2, w - 1)
if e_r1 <= e_r2:
for r in range(e_r1, e_r2 + 1):
result[r, :e_c2+1] = True
# PHASE 3: DOWN/UP (dynamic, height>=3 guard + adjacent no-fg col extension)
down_elig = set()
for c in range(w):
rr = np.where(result[:, c])[0]
if len(rr) == 0: continue
if (rr[-1] - rr[0] + 1) < 3: continue
fg = np.where(inp[:, c] != 0)[0]
if len(fg) == 0 or fg[-1] < rr[0]:
down_elig.add(c)
extended = set(down_elig)
for c in sorted(down_elig):
for adj in [c-1, c+1]:
if 0 <= adj < w and adj not in extended:
fg_adj = np.where(inp[:, adj] != 0)[0]
if len(fg_adj) == 0:
extended.add(adj)
for cs, ce in find_contiguous_runs(sorted(extended)):
es, ee = erode_range(cs, ce, w - 1)
if es <= ee:
for c in range(es, ee + 1):
rr = np.where(result[:, c])[0]
if len(rr) > 0:
result[rr[-1]+1:, c] = True
else:
result[core_r2+1:, c] = True
up_elig = set()
for c in range(w):
rr = np.where(result[:, c])[0]
if len(rr) == 0: continue
if (rr[-1] - rr[0] + 1) < 3: continue
fg = np.where(inp[:, c] != 0)[0]
if len(fg) == 0 or fg[0] > rr[-1]:
up_elig.add(c)
extended_up = set(up_elig)
for c in sorted(up_elig):
for adj in [c-1, c+1]:
if 0 <= adj < w and adj not in extended_up:
fg_adj = np.where(inp[:, adj] != 0)[0]
if len(fg_adj) == 0:
extended_up.add(adj)
for cs, ce in find_contiguous_runs(sorted(extended_up)):
es, ee = erode_range(cs, ce, w - 1)
if es <= ee:
for c in range(es, ee + 1):
rr = np.where(result[:, c])[0]
if len(rr) > 0:
result[:rr[0], c] = True
else:
result[:core_r1, c] = True
# PHASE 4: Second RIGHT/LEFT pass (width>=3 guard, after UP/DOWN)
right_elig2 = []
for r in range(h):
rc = np.where(result[r])[0]
if len(rc) == 0: continue
if (rc[-1] - rc[0] + 1) < 3: continue
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[-1] < rc[0]:
right_elig2.append(r)
for rs, re in find_contiguous_runs(sorted(right_elig2)):
es, ee = erode_range(rs, re, h - 1)
if es <= ee:
for r in range(es, ee + 1):
rc = np.where(result[r])[0]
if len(rc) > 0: result[r, rc[-1]+1:] = True
left_elig2 = []
for r in range(h):
rc = np.where(result[r])[0]
if len(rc) == 0: continue
if (rc[-1] - rc[0] + 1) < 3: continue
fg = np.where(inp[r] != 0)[0]
if len(fg) == 0 or fg[0] > rc[-1]:
left_elig2.append(r)
for rs, re in find_contiguous_runs(sorted(left_elig2)):
es, ee = erode_range(rs, re, h - 1)
if es <= ee:
for r in range(es, ee + 1):
rc = np.where(result[r])[0]
if len(rc) > 0: result[r, :rc[0]] = True
return result & (inp == 0)
def verify_task255(task_data_path='task-data/task255.json'):
with open(task_data_path) as f:
data = json.load(f)
total_pass, total_fail, failures = 0, 0, []
for split in ['train', 'test', 'arc-gen']:
for idx, ex in enumerate(data[split]):
inp_arr = np.array(ex['input'], dtype=int)
exp = np.array(ex['output'], dtype=int)
mask = apply_rule(inp_arr)
pred = inp_arr.copy(); pred[mask] = 3
if np.array_equal(pred, exp):
total_pass += 1
else:
total_fail += 1
m = ((exp == 3) & (pred != 3)).sum()
e = ((pred == 3) & (exp != 3)).sum()
failures.append({'split': split, 'idx': idx, 'missing': int(m), 'extra': int(e)})
print(f"Results: {total_pass} pass, {total_fail} fail")
for f in failures[:15]:
print(f" {f['split']}[{f['idx']}]: M={f['missing']}, E={f['extra']}")
return total_pass, total_fail, failures
if __name__ == '__main__':
verify_task255()