mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
632 lines
21 KiB
Python
Executable file
632 lines
21 KiB
Python
Executable file
from copy import deepcopy
|
|
from libs.futoshiki.Futoshiki import FutoshikiPuzzle
|
|
from libs.futoshiki.log import output_log
|
|
|
|
############################
|
|
# Functional Below this line
|
|
############################
|
|
|
|
|
|
def empty_array_returner(puzzle_size, item_size, contents):
|
|
e = []
|
|
for j in range(puzzle_size):
|
|
line = []
|
|
for i in range(puzzle_size):
|
|
if item_size == 1:
|
|
item = contents
|
|
else:
|
|
item = []
|
|
for k in range(item_size):
|
|
if contents is None:
|
|
item.append(contents)
|
|
elif contents == "range":
|
|
# print("Returning range using dimension sizes to infer length")
|
|
item.append(k + 1)
|
|
line.append(item)
|
|
e.append(line)
|
|
return e
|
|
|
|
|
|
def puzzle_printer(puzzle, logic):
|
|
for i, l_line in enumerate(logic):
|
|
if (i + 1) % 2: # number lines
|
|
line = ""
|
|
for j in range(len(puzzle[0]) - 1):
|
|
line += "{n}{l:1}".format(n=puzzle[i // 2][j], l=l_line[j])
|
|
line += "{}".format(puzzle[i // 2][-1])
|
|
print(line)
|
|
|
|
else: # logic lines
|
|
line = ""
|
|
for l in range(len(l_line) - 1):
|
|
line += "{:1} ".format(l_line[l])
|
|
line += "{}".format(l_line[-1])
|
|
print(line)
|
|
|
|
|
|
def logic_finder(logic):
|
|
# Puzzle size is always the same as the length of the second line of logic.
|
|
puzzle_size = len(logic[1])
|
|
logic_matrix = empty_array_returner(puzzle_size, 4, None)
|
|
for j in range(puzzle_size):
|
|
for i in range(puzzle_size):
|
|
# Finding logic to test this cell against
|
|
try:
|
|
# right
|
|
logic_matrix[j][i][0] = logic[j * 2][i]
|
|
except IndexError:
|
|
logic_matrix[j][i][0] = None
|
|
try:
|
|
# below
|
|
logic_matrix[j][i][1] = logic[(j * 2) + 1][i]
|
|
except IndexError:
|
|
logic_matrix[j][i][1] = None
|
|
|
|
try:
|
|
# left
|
|
if i - 1 < 0:
|
|
logic_matrix[j][i][2] = None
|
|
else:
|
|
logic_matrix[j][i][2] = logic[j * 2][i - 1]
|
|
except IndexError:
|
|
logic_matrix[j][i][2] = None
|
|
try:
|
|
# above
|
|
if (j * 2) - 1 < 0:
|
|
logic_matrix[j][i][3] = None
|
|
else:
|
|
logic_matrix[j][i][3] = logic[(j * 2) - 1][i]
|
|
except IndexError:
|
|
logic_matrix[j][i][3] = None
|
|
return logic_matrix
|
|
|
|
|
|
def get_neighbours(puzzle, j, i):
|
|
neighbours = [None] * 4
|
|
try:
|
|
neighbours[0] = puzzle[j][i + 1]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[1] = puzzle[j + 1][i]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[2] = puzzle[j][i - 1]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[3] = puzzle[j - 1][i]
|
|
except IndexError:
|
|
pass
|
|
|
|
return neighbours
|
|
|
|
|
|
def single_cell_tester(cell_number, neighbours, nearby_logic, only_check_two=False):
|
|
# "Only check two" allows for the fact that checking only right and down for all squares will be equally valid and quicker.
|
|
result = True
|
|
if nearby_logic[0] == ">" and neighbours[0] is not None:
|
|
result = result & (cell_number > neighbours[0])
|
|
if nearby_logic[1] == u"\u2228" and neighbours[1] is not None:
|
|
result = result & (cell_number > neighbours[1])
|
|
if nearby_logic[2] == "<" and neighbours[2] is not None and not only_check_two:
|
|
result = result & (cell_number > neighbours[2])
|
|
if (
|
|
nearby_logic[3] == u"\u2227"
|
|
and neighbours[3] is not None
|
|
and not only_check_two
|
|
):
|
|
result = result & (cell_number > neighbours[3])
|
|
|
|
if nearby_logic[0] == "<" and neighbours[0] is not None and neighbours[0] != 0:
|
|
result = result & (cell_number < neighbours[0])
|
|
if (
|
|
nearby_logic[1] == u"\u2227"
|
|
and neighbours[1] is not None
|
|
and neighbours[1] != 0
|
|
):
|
|
result = result & (cell_number < neighbours[1])
|
|
if (
|
|
nearby_logic[2] == ">"
|
|
and neighbours[2] is not None
|
|
and neighbours[2] != 0
|
|
and not only_check_two
|
|
):
|
|
result = result & (cell_number < neighbours[2])
|
|
if (
|
|
nearby_logic[3] == u"\u2228"
|
|
and neighbours[3] is not None
|
|
and neighbours[3] != 0
|
|
and not only_check_two
|
|
):
|
|
result = result & (cell_number < neighbours[3])
|
|
|
|
return result
|
|
|
|
|
|
def get_possible_neighbours(p, j, i, index=0):
|
|
neighbours = [None] * 4
|
|
try:
|
|
neighbours[0] = p[j][i + 1][index]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[1] = p[j + 1][i][index]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[2] = p[j][i - 1][index]
|
|
except IndexError:
|
|
pass
|
|
try:
|
|
neighbours[3] = p[j - 1][i][index]
|
|
except IndexError:
|
|
pass
|
|
|
|
return neighbours
|
|
|
|
|
|
def valid(puzzle, logic):
|
|
tests_to_be_performed = logic_finder(logic)
|
|
all_cells_valid = True
|
|
zero_flag = False
|
|
all_lines_filled = True
|
|
logic_failures = []
|
|
for j, line in enumerate(puzzle):
|
|
for i, n in enumerate(line):
|
|
if n == 0:
|
|
# any zeroes mean not completed puzzle
|
|
print("Not Complete (Zeroes Present) Checking Logic")
|
|
zero_flag = True
|
|
else:
|
|
# Applying the tests to the cell
|
|
cell_valid = single_cell_tester(
|
|
cell_number=puzzle[j][i],
|
|
neighbours=get_neighbours(puzzle, j, i),
|
|
nearby_logic=tests_to_be_performed[j][i],
|
|
only_check_two=True,
|
|
)
|
|
if not cell_valid:
|
|
print("Logic Failure caused by cell: {},{} (line, index)".format(j, i))
|
|
logic_failures.append((j, i))
|
|
all_cells_valid = False
|
|
l = line.copy()
|
|
l.sort()
|
|
# Check the line sorted is the same as an array of 1->size of puzzle
|
|
if l != [k + 1 for k in range(len(puzzle[0]))]:
|
|
print(
|
|
"The line {} does not have solely the numbers 1-{} (inclusive), therefore is invalid".format(
|
|
puzzle.index(line), len(puzzle[0])
|
|
)
|
|
)
|
|
all_lines_filled = False
|
|
|
|
return (all_cells_valid, all_lines_filled, zero_flag), logic_failures
|
|
|
|
|
|
def recursive_more_than(logic_matrix, p, j, i):
|
|
nearby_logic = logic_matrix[j][i]
|
|
# print("Currently in {},{}".format(j, i))
|
|
# CHECK WHETHER RECURSION COULD BE MOVED UP
|
|
# next cell is less than current cell
|
|
if nearby_logic[0] == ">":
|
|
try:
|
|
p[j][i] = [x for x in p[j][i] if x > min(p[j][i + 1])]
|
|
except ValueError:
|
|
pass
|
|
recursive_more_than(logic_matrix, p, j, i + 1)
|
|
|
|
if nearby_logic[1] == u"\u2228":
|
|
try:
|
|
# p[j][i].remove((min(p[j+1][i])))
|
|
p[j][i] = [x for x in p[j][i] if x > min(p[j + 1][i])]
|
|
except ValueError:
|
|
pass
|
|
recursive_more_than(logic_matrix, p, j + 1, i)
|
|
if nearby_logic[2] == "<":
|
|
try:
|
|
# p[j][i].remove((min(p[j][i-1])))
|
|
p[j][i] = [x for x in p[j][i] if x > min(p[j][i - 1])]
|
|
except ValueError:
|
|
pass
|
|
recursive_more_than(logic_matrix, p, j, i - 1)
|
|
if nearby_logic[3] == u"\u2227":
|
|
try:
|
|
# p[j][i].remove((min(p[j-1][i])))
|
|
p[j][i] = [x for x in p[j][i] if x > min(p[j - 1][i])]
|
|
except ValueError:
|
|
pass
|
|
recursive_more_than(logic_matrix, p, j - 1, i)
|
|
return p
|
|
|
|
|
|
def recursive_less_than(logic_matrix, p, j, i):
|
|
nearby_logic = logic_matrix[j][i]
|
|
# print("Currently in {},{}".format(j, i))
|
|
# next cell is greater than current cell
|
|
if nearby_logic[0] == "<":
|
|
try:
|
|
p[j][i] = [x for x in p[j][i] if x < max(p[j][i + 1])]
|
|
except ValueError:
|
|
pass
|
|
recursive_less_than(logic_matrix, p, j, i + 1)
|
|
|
|
if nearby_logic[1] == u"\u2227":
|
|
try:
|
|
p[j][i] = [x for x in p[j][i] if x < max(p[j + 1][i])]
|
|
except ValueError:
|
|
pass
|
|
recursive_less_than(logic_matrix, p, j + 1, i)
|
|
if nearby_logic[2] == ">":
|
|
try:
|
|
p[j][i] = [x for x in p[j][i] if x < max(p[j][i - 1])]
|
|
except ValueError:
|
|
pass
|
|
recursive_less_than(logic_matrix, p, j, i - 1)
|
|
if nearby_logic[3] == u"\u2228":
|
|
try:
|
|
p[j][i] = [x for x in p[j][i] if x < max(p[j - 1][i])]
|
|
except ValueError:
|
|
pass
|
|
recursive_less_than(logic_matrix, p, j - 1, i)
|
|
return p
|
|
|
|
|
|
def line_match(row, match_list):
|
|
row_matches = [index for index, val in enumerate(row) if val == match_list]
|
|
if len(row_matches) == len(match_list):
|
|
non_matched_row_indices = [x for x in range(len(row)) if x not in row_matches]
|
|
for index in non_matched_row_indices:
|
|
try:
|
|
for num in match_list:
|
|
row[index].remove(num)
|
|
except ValueError:
|
|
continue
|
|
|
|
# Now all exact matches are removed, look for partial matches:
|
|
if len(match_list) == 2 and len(row_matches) == 1:
|
|
# print("Partial matching: match_list {} and \n row:{} ".format(match_list, row))
|
|
rest_of_row = list(filter(lambda a: a != match_list, deepcopy(row)))
|
|
third_values_attempted = []
|
|
for item in rest_of_row:
|
|
if len(item) == 3 and all(
|
|
x in item for x in match_list
|
|
): # all of the match list is contained
|
|
|
|
ic = [x for x in deepcopy(item) if x not in match_list]
|
|
# ic.remove(match_list[0])
|
|
# ic.remove(match_list[1])
|
|
# print("Checking {}, Third value in item = {}".format(item, ic[0]))
|
|
if ic[0] in third_values_attempted:
|
|
pass
|
|
else:
|
|
third_values_attempted.append(ic[0])
|
|
new_match_value = deepcopy(match_list)
|
|
new_match_value.append(ic[0])
|
|
new_match_value.sort()
|
|
|
|
new_row_matches = [
|
|
index
|
|
for index, val in enumerate(row)
|
|
if (val == new_match_value) or (val == match_list)
|
|
]
|
|
|
|
if len(new_row_matches) == 3:
|
|
# print("Partial matching match_list: {} extended to: {} for \nrow: {}".format(
|
|
# match_list, new_match_value, row))
|
|
|
|
# print("Found {} matches, removing {} from row ".format(
|
|
# len(new_row_matches), new_match_value))
|
|
non_matched_row_indices = [
|
|
x for x in range(len(row)) if x not in new_row_matches
|
|
]
|
|
for index in non_matched_row_indices:
|
|
for num in new_match_value:
|
|
try:
|
|
row[index].remove(num)
|
|
except ValueError:
|
|
continue
|
|
# print("New row: {}".format(row))
|
|
|
|
return row
|
|
|
|
|
|
def only_possible_location(line, index):
|
|
other_indices = [k for k in range(len(line))]
|
|
other_indices.remove(index)
|
|
|
|
poss_values = line[index]
|
|
|
|
for val in poss_values:
|
|
other_occurences = 0
|
|
for i in other_indices:
|
|
if val in line[i]:
|
|
# Count up if there is another possible location
|
|
other_occurences += 1
|
|
if other_occurences == 0:
|
|
# If none then this place is the only one you can put this value
|
|
line[index] = [val]
|
|
return line
|
|
|
|
|
|
def poss_locations(pc, j, i):
|
|
row = pc[j].copy()
|
|
row = only_possible_location(row, i)
|
|
pc[j] = row
|
|
col = [x[i] for x in pc.copy()]
|
|
col = only_possible_location(col, j)
|
|
for m in range(len(pc[0])):
|
|
pc[m][i] = col[m]
|
|
return pc
|
|
|
|
|
|
def find_matches(pc, j, i):
|
|
match_list = pc[j][i]
|
|
# Match the row first
|
|
row = pc[j].copy()
|
|
row = line_match(row, match_list)
|
|
# Match the column next
|
|
col = [x[i] for x in pc.copy()]
|
|
col = line_match(col, match_list)
|
|
# As shallow copies have been used, the possibility matrix will have been altered
|
|
return pc
|
|
|
|
|
|
def corner_rule(logic_matrix, pc, j, i):
|
|
match_list = pc[j][i]
|
|
if len(match_list) != 2:
|
|
return pc
|
|
else:
|
|
row = pc[j].copy()
|
|
col = [x[i] for x in pc.copy()]
|
|
row_matches = [index for index, val in enumerate(row) if val == match_list]
|
|
if len(row_matches) != 2:
|
|
return pc
|
|
else:
|
|
i1, i2 = row_matches[0], row_matches[1]
|
|
if (
|
|
logic_matrix[j][i1][1] == u"\u2227"
|
|
and logic_matrix[j][i2][3] == u"\u2228"
|
|
) and pc[j + 1][i1] == pc[j - 1][i2]:
|
|
# print("Both pairs less than same pair - applying corner rule 1")
|
|
num_to_remove = max(pc[j + 1][i1])
|
|
try:
|
|
pc[j + 1][i2].remove(num_to_remove)
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
pc[j - 1][i1].remove(num_to_remove)
|
|
except ValueError:
|
|
pass
|
|
if (
|
|
logic_matrix[j][i1][3] == u"\u2228"
|
|
and logic_matrix[j][i2][1] == u"\u2227"
|
|
and pc[j - 1][i1] == pc[j + 1][i2]
|
|
):
|
|
# print("Both pairs less than same pair - applying corner rule 2")
|
|
num_to_remove = max(pc[j - 1][i1])
|
|
try:
|
|
pc[j + 1][i1].remove(num_to_remove)
|
|
except ValueError:
|
|
pass
|
|
try:
|
|
pc[j - 1][i2].remove(num_to_remove)
|
|
except ValueError:
|
|
pass
|
|
|
|
# col_matches = [index for index, val in enumerate(col) if val == match_list]
|
|
|
|
return pc
|
|
|
|
|
|
def possible_values(puzzle, logic, p):
|
|
logic_layout = logic_finder(logic)
|
|
pc = deepcopy(p)
|
|
for j, line in enumerate(puzzle):
|
|
for i, number in enumerate(line):
|
|
if number != 0 or len(pc[j][i]) == 1:
|
|
if number == 0:
|
|
no = pc[j][i][0]
|
|
else:
|
|
no = number
|
|
# print('Removing {} from column and line due to appearance at {}, {}'.format(number, j, i))
|
|
other_line = [k for k in range(len(pc[0]))]
|
|
other_line.remove(i)
|
|
|
|
for k in other_line:
|
|
try:
|
|
# print('{},{}'.format(l, i))
|
|
pc[j][k].remove(no)
|
|
except (ValueError, IndexError):
|
|
pass
|
|
other_column = [k for k in range(len(pc[0]))]
|
|
other_column.remove(j)
|
|
for l in other_column:
|
|
try:
|
|
pc[l][i].remove(no)
|
|
except (ValueError, IndexError):
|
|
pass
|
|
pc[j][i] = [no]
|
|
else:
|
|
# Removing the maxima from neighbours in 'less than' locations, and minima from 'more than' locations
|
|
pc = recursive_more_than(logic_layout, pc, j, i)
|
|
# current cell is less than the next cell along
|
|
pc = recursive_less_than(logic_layout, pc, j, i)
|
|
if len(pc[j][i]) == 2 or len(pc[j][i]) == 3:
|
|
pc = find_matches(pc, j, i)
|
|
pc = poss_locations(pc, j, i)
|
|
pc = corner_rule(logic_layout, pc, j, i)
|
|
|
|
return pc
|
|
|
|
|
|
def brute_force(puzzle, logic, p):
|
|
logic_matrix = logic_finder(logic)
|
|
new_p = deepcopy(p)
|
|
new_puzzle = deepcopy(puzzle)
|
|
# Box lookup is a dictionary of box locations, indexed by number of possible values for each location
|
|
box_lookup = {}
|
|
# We want boxes with two values, up to boxes with any possible value (ie size of puzzle)
|
|
for m in range(2, len(p[0]) + 1):
|
|
box_lookup[m] = []
|
|
|
|
for j in range(len(new_puzzle[0])):
|
|
for i in range(len(new_puzzle[0])):
|
|
if len(p[j][i]) == 1:
|
|
new_puzzle[j][i] = new_p[j][i][0]
|
|
else:
|
|
box_lookup[len(new_p[j][i])].append((j, i))
|
|
# print(box_lookup)
|
|
solved = False
|
|
for m in range(2, len(p[0]) + 1):
|
|
for box in box_lookup[m]:
|
|
print("Attempting brute force at cell {},{}".format(box[0], box[1]))
|
|
for index in range(len(new_p[box[0]][box[1]])):
|
|
test_puzzle = new_puzzle
|
|
# Try value at index as the value for this box:
|
|
test_puzzle[box[0]][box[1]] = new_p[box[0]][box[1]][index]
|
|
pc, t = consistent_past_values(test_puzzle, logic)
|
|
# pc, t, solved = solve(test_puzzle, logic, brute_force_if_needed=True)
|
|
solved = True
|
|
# # TODO fix this bit up so that it brute forces all options below a current option, if all of those don't work then it removes the current option.
|
|
# if not solved:
|
|
# new_p[box[0]][box[1]].pop(index)
|
|
# # remove option that does not solve puzzle
|
|
# #####################################################
|
|
# # Old stuff down here
|
|
|
|
solution = []
|
|
for j in range(len(new_puzzle[0])):
|
|
line = []
|
|
for i in range(len(new_puzzle[0])):
|
|
if len(pc[j][i]) == 1:
|
|
line.append(pc[j][i][0])
|
|
else:
|
|
solved = False
|
|
break
|
|
solution.append(line)
|
|
#####################################################
|
|
if solved and valid(solution, logic):
|
|
# print("Problem solved through brute forcing")
|
|
# puzzle_printer(solution, logic)
|
|
return pc
|
|
else:
|
|
continue
|
|
print("Not able to solve by brute forcing, you might have to figure it out mate.")
|
|
return new_p
|
|
|
|
|
|
def sol_print(p):
|
|
for line in p:
|
|
print(line)
|
|
|
|
|
|
def consistent_past_values(puzzle, logic):
|
|
p = empty_array_returner(len(puzzle[0]), len(puzzle[0]), "range")
|
|
p_prev = None
|
|
t = 0
|
|
|
|
while p != p_prev and t < 15:
|
|
p_prev = p
|
|
p = possible_values(puzzle, logic, p_prev)
|
|
t += 1
|
|
|
|
return p, t
|
|
|
|
|
|
def solve(puzzle, logic, brute_force_if_needed=False):
|
|
print("Finding a solution to the puzzle:")
|
|
puzzle_printer(puzzle, logic)
|
|
p, t = consistent_past_values(puzzle, logic)
|
|
solved = True
|
|
solution = []
|
|
for j in range(len(puzzle[0])):
|
|
line = []
|
|
for i in range(len(puzzle[0])):
|
|
if len(p[j][i]) == 1:
|
|
line.append(p[j][i][0])
|
|
else:
|
|
solved = False
|
|
break
|
|
solution.append(line)
|
|
|
|
if solved and valid(solution, logic):
|
|
# print("Problem solved after {} iterations!".format(t))
|
|
# puzzle_printer(solution, logic)
|
|
return p, t, solved
|
|
else:
|
|
print("Problem not solved after {} iterations, possible values:".format(t))
|
|
sol_print(p)
|
|
if brute_force_if_needed:
|
|
print("Brute Forcing")
|
|
p = brute_force(puzzle, logic, p)
|
|
else:
|
|
brute_q = input("Do you want to brute force a solution? (y/n)")
|
|
if brute_q == "y":
|
|
p = brute_force(puzzle, logic, p)
|
|
return p, t, solved
|
|
|
|
|
|
# p1 = FutoshikiPuzzle(puzzle1, logic1)
|
|
# # print(p1)
|
|
# p1.solve()
|
|
# print(p1.solution)
|
|
# for x in p1.possible_values:
|
|
# print(x)
|
|
# solve(puzzle1, logic1)
|
|
puzzle11 = [
|
|
[0, 0, 0, 1, 0],
|
|
[0, 0, 0, 0, 0],
|
|
[0, 0, 0, 0, 0],
|
|
[0, 0, 0, 2, 0],
|
|
[0, 0, 0, 0, 0]
|
|
]
|
|
|
|
# '/\': u"\u2227"
|
|
# '\/': u"\u2228"
|
|
|
|
logic11 = [
|
|
["", "", "", ""],
|
|
["", "", "", "", ""],
|
|
["", "", "", ""],
|
|
["", "", "", "", ""],
|
|
["", "", ">", ""],
|
|
["", "", "", u"\u2228", ""],
|
|
["", ">", "", ""],
|
|
[u"\u2227", "", "", "", ""],
|
|
["", ">", ">", ""],
|
|
["", "", "", "", ""]
|
|
]
|
|
|
|
def test():
|
|
p = FutoshikiPuzzle(puzzle11, logic11)
|
|
p.solve(output_log())
|
|
|
|
def solve_futoshiki(puzzle, logic):
|
|
# 确保 puzzle 和 logic 不为 None
|
|
if puzzle is None:
|
|
output_str = "error"
|
|
solution = "出现错误,跳过此条训练数据"
|
|
print(output_str)
|
|
return output_str, solution
|
|
raise ValueError("puzzle cannot be None")
|
|
if logic is None:
|
|
output_str = "error"
|
|
solution = "出现错误,跳过此条训练数据"
|
|
print(output_str)
|
|
return output_str, solution
|
|
raise ValueError("logic cannot be None")
|
|
|
|
|
|
# 检查 puzzle 和 logic 的类型,确保它们符合预期
|
|
if not isinstance(puzzle, list) or not isinstance(logic, list):
|
|
raise TypeError("puzzle and logic must be of type list")
|
|
steps = output_log() # 创建日志对象
|
|
p = FutoshikiPuzzle(initial_puzzle_numbers=puzzle, puzzle_logic=logic, alog=steps,gen_mode=False)
|
|
solution = p.solve()
|
|
output_str = steps.get_log()
|
|
print(output_str)
|
|
return output_str, solution
|
|
|
|
|