from lib.colorutils import ColorUtils
def __init__(self, i_width, i_height, o_color=ColorUtils()):
self.a_map = self.generate_empty_map()
def generate_empty_map(self):
a_i_possible_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i_x in range(self.i_width):
a_row.append(a_i_possible_numbers.copy())
for i_y in range(self.i_height):
a_map.append(copy.deepcopy(a_row))
def set_number(self, i_number, i_x, i_y):
Sets a well known (already defined in the original map) number for a position
self.a_map[i_y][i_x] = [i_number]
def detect_and_remove_a_number_from_possibles_from_a_row(self, i_y):
We will elinate this possibility from the row
self.o_color.print_label("Detecting numbers to remove from row " + str(i_y))
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1:
i_number_found = self.a_map[i_y][i_x][0]
print("Found a number that will be removed from horizontal and vertical and in quadrant", i_number_found, "at", i_x, i_y)
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_number_found, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number_found, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_number_found, i_x=i_x, i_y=i_y)
def remove_a_number_from_possibles_in_a_row(self, i_number_to_remove, i_y):
Removes a number from the list of possibles in that row
:param i_number_to_remove:
self.o_color.print_label("> Scanning for removing " + str(i_number_to_remove) + " in row " + str(i_y))
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("> Removed", i_number_to_remove, "From:", str(i_x) + "x" + str(i_y), a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
self.a_map[i_y][i_x] = a_i_numbers_possible
if len(a_i_numbers_possible) == 1:
# Trigger it again for the number recently discovered
i_new_number_to_remove = a_i_numbers_possible[0]
self.o_color.print_success("> Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + "x" + str(i_y))
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_new_number_to_remove, i_x=i_x, i_y=i_y)
self.o_color.print_label("> Leaving scan for " + str(i_number_to_remove) + " in row " + str(i_y))
def remove_a_number_from_possibles_in_a_column(self, i_number_to_remove, i_x):
Removes a number from the list of possibles in that row
:param i_number_to_remove:
self.o_color.print_label("V Scanning for removing " + str(i_number_to_remove) + " in col " + str(i_x))
for i_y in range(0, self.i_height):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("V Removed", i_number_to_remove, "From:", i_x, i_y, a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
# @TODO: Remove, as it's a pointer it is not needed
self.a_map[i_y][i_x] = a_i_numbers_possible
if len(a_i_numbers_possible) == 1:
# Trigger it again for the number recently discovered
i_new_number_to_remove = a_i_numbers_possible[0]
self.o_color.print_success("Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + " " + str(i_y))
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_new_number_to_remove, i_x=i_x, i_y=i_y)
self.o_color.print_label("V Leaving scan for " + str(i_number_to_remove) + " in col " + str(i_x))
def remove_a_number_from_possibles_in_quadrant(self, i_number_to_remove, i_x, i_y):
:param i_number_to_remove:
i_x_quadrant = int(i_x / 3)
i_y_quadrant = int(i_y / 3)
i_x_ini = i_x_quadrant * 3
i_y_ini = i_y_quadrant * 3
for i_y_rel in range(i_y_ini, i_y_end + 1):
for i_x_rel in range(i_x_ini, i_x_end + 1):
a_i_numbers_possible = self.a_map[i_y_rel][i_x_rel]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("X Removed", i_number_to_remove, "From:", i_x_rel, i_y_rel, a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
# Nota: Here I had a bug and I was "liant-la parda"
# if len(a_i_numbers_possible) == 1:
# # Trigger it again for the number recently discovered
# i_new_number_to_remove = a_i_numbers_possible[0]
# string_ints = [str(int) for int in ints]
# self.o_color.print_success("X Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + "x" + str(i_y) + "[]")
# self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
# self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
def check_if_number_possibles_in_quadrant_is_unique(self, i_number_to_check, i_x, i_y):
:param i_number_to_remove:
i_x_quadrant = int(i_x / 3)
i_y_quadrant = int(i_y / 3)
i_x_ini = i_x_quadrant * 3
i_y_ini = i_y_quadrant * 3
i_number_of_occurrences_found = 0
for i_y_rel in range(i_y_ini, i_y_end + 1):
for i_x_rel in range(i_x_ini, i_x_end + 1):
a_i_numbers_possible = self.a_map[i_y_rel][i_x_rel]
for i_number_in_possibles in a_i_numbers_possible:
if len(a_i_numbers_possible) > 1 and i_number_in_possibles == i_number_to_check:
# This is the right cell, ignore it
i_number_of_occurrences_found += 1
i_x_position_number = i_x_rel
i_y_position_number = i_y_rel
if i_number_of_occurrences_found > 1:
if i_number_of_occurrences_found == 1:
a_i_numbers_possible = [i_number_to_check]
self.a_map[i_y_position_number][i_x_position_number] = a_i_numbers_possible
return b_unique, i_x_position_number, i_y_position_number
def check_if_number_possibles_in_row_is_unique(self, i_number_to_check, i_y):
:param i_number_to_check:
i_number_of_occurrences_found = 0
for i_x_rel in range(0, 9):
a_i_numbers_possible = self.a_map[i_y][i_x_rel]
for i_number_in_possibles in a_i_numbers_possible:
if len(a_i_numbers_possible) > 1 and i_number_in_possibles == i_number_to_check:
# This is the right cell, ignore it
i_number_of_occurrences_found += 1
i_x_position_number = i_x_rel
i_y_position_number = i_y
if i_number_of_occurrences_found > 1:
if i_number_of_occurrences_found == 1:
a_i_numbers_possible = [i_number_to_check]
self.a_map[i_y_position_number][i_x_position_number] = a_i_numbers_possible
return b_unique, i_x_position_number, i_y_position_number
def get_map_drawing_as_string(self, a_map_alternative=None):
a_map_to_use = self.a_map
if a_map_alternative is not None:
a_map_to_use = a_map_alternative
s_map = s_map + s_separator_rows * 37 + "\n"
for a_row in a_map_to_use:
for a_i_numbers_possible in a_row:
if len(a_i_numbers_possible) == 1:
s_number = str(a_i_numbers_possible[0])
s_map = s_map + " " + s_number + " " + s_separator
s_map = s_map + s_separator_rows * 37 + "\n"
s_map = s_map.replace("0", " ")
i_total_numbers_found, a_s_numbers_found = self.get_total_numbers_found()
s_map = s_map + "Total numbers found: " + str(i_total_numbers_found) + " Numbers found: " + " ".join(a_s_numbers_found) + "\n"
def get_map_drawing_of_possibles_as_string(self, a_map_alternative=None):
a_map_to_use = self.a_map
if a_map_alternative is not None:
a_map_to_use = a_map_alternative
s_map = s_map + self.o_color.color_blue(s_separator_rows * ((9 * ( 9 + 2 )) + 10)) + "\n"
for a_row in a_map_to_use:
s_map = s_map + self.o_color.color_blue("|")
for a_i_numbers_possible in a_row:
if len(a_i_numbers_possible) == 1:
s_number = str(a_i_numbers_possible[0]).center(9)
s_number = self.o_color.color_success(s_number)
a_i_numbers_possible_string = []
for i_number in a_i_numbers_possible:
# Replace by the color sequence
s_number = self.o_color.color_red(s_number)
s_number = self.o_color.color_yellow(s_number)
self.o_color.color_magenta(s_number)
a_i_numbers_possible_string.append(s_number)
# s_number = "".join(a_i_numbers_possible_string).ljust(9)
s_number = "".join(a_i_numbers_possible_string) + " " * (9-len(a_i_numbers_possible))
s_separator = self.o_color.color_blue("|")
s_separator = self.o_color.color_blue("¦")
s_map = s_map + " " + s_number + " " + s_separator
s_map = s_map + self.o_color.color_blue(s_separator_rows * ((9 * (9 + 2)) + 10)) + "\n"
s_map = s_map.replace("0", " ")
def get_total_numbers_found(self):
i_total_numbers_found = 0
for i_y in range(0, self.i_height):
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1:
i_total_numbers_found = i_total_numbers_found + 1
i_number_found = self.a_map[i_y][i_x][0]
s_number_found = str(i_number_found)
if s_number_found not in a_s_numbers_found:
a_s_numbers_found.append(s_number_found)
return i_total_numbers_found, a_s_numbers_found
if __name__ == "__main__":
o_map = SudokuMap(9, 9, o_color=o_color)
o_map.set_number(i_number=1, i_x=1, i_y=0)
o_map.set_number(3, 4, 0)
o_map.set_number(8, 7, 0)
o_map.set_number(8, 0, 1)
o_map.set_number(7, 3, 1)
o_map.set_number(4, 5, 1)
o_map.set_number(6, 8, 1)
o_map.set_number(3, 2, 2)
o_map.set_number(9, 6, 2)
o_map.set_number(2, 1, 3)
o_map.set_number(4, 4, 3)
o_map.set_number(6, 7, 3)
o_map.set_number(5, 0, 4)
o_map.set_number(6, 3, 4)
o_map.set_number(2, 5, 4)
o_map.set_number(8, 8, 4)
o_map.set_number(3, 1, 5)
o_map.set_number(8, 4, 5)
o_map.set_number(7, 7, 5)
o_map.set_number(2, 2, 6)
o_map.set_number(6, 6, 6)
o_map.set_number(9, 0, 7)
o_map.set_number(4, 3, 7)
o_map.set_number(3, 5, 7)
o_map.set_number(2, 8, 7)
o_map.set_number(8, 1, 8)
o_map.set_number(6, 4, 8)
o_map.set_number(1, 7, 8)
# o_map.set_number(2, 0, 0)
o_map.set_number(7, 0, 3)
o_map2 = SudokuMap(9, 9, o_color=o_color)
o_map2.set_number(i_number=5, i_x=0, i_y=0)
o_map2.set_number(i_number=9, i_x=5, i_y=0)
o_map2.set_number(i_number=7, i_x=2, i_y=1)
o_map2.set_number(i_number=2, i_x=7, i_y=1)
o_map2.set_number(i_number=2, i_x=0, i_y=2)
o_map2.set_number(i_number=3, i_x=4, i_y=2)
o_map2.set_number(i_number=1, i_x=5, i_y=2)
o_map2.set_number(i_number=9, i_x=7, i_y=2)
o_map2.set_number(i_number=7, i_x=0, i_y=3)
o_map2.set_number(i_number=1, i_x=2, i_y=3)
o_map2.set_number(i_number=6, i_x=3, i_y=3)
o_map2.set_number(i_number=9, i_x=4, i_y=3)
o_map2.set_number(i_number=4, i_x=8, i_y=3)
o_map2.set_number(i_number=1, i_x=4, i_y=4)
o_map2.set_number(i_number=6, i_x=0, i_y=5)
o_map2.set_number(i_number=7, i_x=4, i_y=5)
o_map2.set_number(i_number=4, i_x=5, i_y=5)
o_map2.set_number(i_number=3, i_x=6, i_y=5)
o_map2.set_number(i_number=1, i_x=8, i_y=5)
o_map2.set_number(i_number=5, i_x=1, i_y=6)
o_map2.set_number(i_number=3, i_x=3, i_y=6)
o_map2.set_number(i_number=6, i_x=4, i_y=6)
o_map2.set_number(i_number=8, i_x=8, i_y=6)
o_map2.set_number(i_number=6, i_x=1, i_y=7)
o_map2.set_number(i_number=7, i_x=6, i_y=7)
o_map2.set_number(i_number=9, i_x=3, i_y=8)
o_map2.set_number(i_number=3, i_x=8, i_y=8)
# Extra help while not implemented the best algorithm
# =============================================================================================================
# | 5 ¦ 148 ¦ 48 | 7 ¦ 2 ¦ 9 | 148 ¦ 3 ¦ 6 |
# -------------------------------------------------------------------------------------------------------------
# | 13489 ¦ 13489 ¦ 7 | 48 ¦ 48 ¦ 6 | 148 ¦ 2 ¦ 5 |
# -------------------------------------------------------------------------------------------------------------
# | 2 ¦ 48 ¦ 6 | 5 ¦ 3 ¦ 1 | 48 ¦ 9 ¦ 7 |
# =============================================================================================================
# | 7 ¦ 38 ¦ 1 | 6 ¦ 9 ¦ 358 | 2 ¦ 58 ¦ 4 |
# -------------------------------------------------------------------------------------------------------------
# | 348 ¦ 2348 ¦ 3458 | 28 ¦ 1 ¦ 358 | 6 ¦ 7 ¦ 9 |
# -------------------------------------------------------------------------------------------------------------
# | 6 ¦ 289 ¦ 589 | 28 ¦ 7 ¦ 4 | 3 ¦ 58 ¦ 1 |
# =============================================================================================================
# | 14 ¦ 5 ¦ 2 | 3 ¦ 6 ¦ 7 | 9 ¦ 14 ¦ 8 |
# -------------------------------------------------------------------------------------------------------------
# | 3489 ¦ 6 ¦ 3489 | 1 ¦ 458 ¦ 58 | 7 ¦ 45 ¦ 2 |
# -------------------------------------------------------------------------------------------------------------
# | 148 ¦ 7 ¦ 48 | 9 ¦ 458 ¦ 2 | 145 ¦ 6 ¦ 3 |
# =============================================================================================================
# By best algorithm I mean that the last in the middle vertical quadrant from the top right horizontally,
# only 5 can be in a column. That clarifies that 5 must go to the other column in last quadrant, first column. Coord 6x8
# o_map2.set_number(i_number=5, i_x=6, i_y=8)
# ERROR traces from a bug fixed to mentioned during the code review
# Surprisingly this fails
# > Leaving scan for 8 in row1
# V Scanning for removing 8 in col 4
# V Removed 8 From: 4 7 [5, 8] Pending: [5]
# > Scanning for removing 5 in row 7
# > Removed 5 From: 5 7 [5, 8] Pending: [8]
# > Scanning for removing 8 in row 7
# > Removed 8 From: 0 7 [3, 8] Pending: [3]
print(o_map.get_map_drawing_as_string())
while b_changes_found is True:
for i_y in range(0, o_map.i_height):
b_found = o_map.detect_and_remove_a_number_from_possibles_from_a_row(i_y=i_y)
print(o_map.get_map_drawing_as_string())
for i_y in range(0, o_map.i_height):
o_map.o_color.print_label("Scanning quadrants for row " + str(i_y))
for i_number in range(1, 10):
for i_x in range(0, o_map.i_width):
b_found, i_x_found, i_y_found = o_map.check_if_number_possibles_in_quadrant_is_unique(i_number_to_check=i_number, i_x=i_x, i_y=i_y)
o_map.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_number, i_y=i_y_found)
o_map.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number, i_x=i_x_found)
b_found, i_x_found, i_y_found = o_map.check_if_number_possibles_in_row_is_unique(i_number_to_check=i_number, i_y=i_y)
o_map.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number, i_x=i_x_found)
if b_changes_found is True:
print(o_map.get_map_drawing_as_string())
# @TODO: Implement check if number in quadrant can only go to a column, to remove the non possible in that column from another quadrant
# @TODO: Implement check if in a line only one number can go to a column.
print(o_map.get_map_drawing_as_string())
print(o_map.get_map_drawing_of_possibles_as_string())
import copy
from lib.colorutils import ColorUtils
class SudokuMap():
def __init__(self, i_width, i_height, o_color=ColorUtils()):
self.i_width = i_width
self.i_height = i_height
self.o_color = o_color
self.a_map = self.generate_empty_map()
def generate_empty_map(self):
a_map = []
a_row = []
a_i_possible_numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
for i_x in range(self.i_width):
a_row.append(a_i_possible_numbers.copy())
for i_y in range(self.i_height):
a_map.append(copy.deepcopy(a_row))
return a_map
def set_number(self, i_number, i_x, i_y):
"""
Sets a well known (already defined in the original map) number for a position
:param i_number:
:param i_x:
:param i_y:
:return:
"""
self.a_map[i_y][i_x] = [i_number]
def detect_and_remove_a_number_from_possibles_from_a_row(self, i_y):
"""
We will elinate this possibility from the row
:return: Boolean
"""
b_found = False
self.o_color.print_label("Detecting numbers to remove from row " + str(i_y))
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1:
b_found = True
i_number_found = self.a_map[i_y][i_x][0]
print("Found a number that will be removed from horizontal and vertical and in quadrant", i_number_found, "at", i_x, i_y)
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_number_found, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number_found, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_number_found, i_x=i_x, i_y=i_y)
return b_found
def remove_a_number_from_possibles_in_a_row(self, i_number_to_remove, i_y):
"""
Removes a number from the list of possibles in that row
:param i_number_to_remove:
:param i_y:
:return:
"""
self.o_color.print_label("> Scanning for removing " + str(i_number_to_remove) + " in row " + str(i_y))
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
pass
else:
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("> Removed", i_number_to_remove, "From:", str(i_x) + "x" + str(i_y), a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
self.a_map[i_y][i_x] = a_i_numbers_possible
if len(a_i_numbers_possible) == 1:
# Trigger it again for the number recently discovered
i_new_number_to_remove = a_i_numbers_possible[0]
self.o_color.print_success("> Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + "x" + str(i_y))
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_new_number_to_remove, i_x=i_x, i_y=i_y)
self.o_color.print_label("> Leaving scan for " + str(i_number_to_remove) + " in row " + str(i_y))
def remove_a_number_from_possibles_in_a_column(self, i_number_to_remove, i_x):
"""
Removes a number from the list of possibles in that row
:param i_number_to_remove:
:param i_y:
:return:
"""
self.o_color.print_label("V Scanning for removing " + str(i_number_to_remove) + " in col " + str(i_x))
for i_y in range(0, self.i_height):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
pass
else:
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("V Removed", i_number_to_remove, "From:", i_x, i_y, a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
# @TODO: Remove, as it's a pointer it is not needed
self.a_map[i_y][i_x] = a_i_numbers_possible
if len(a_i_numbers_possible) == 1:
# Trigger it again for the number recently discovered
i_new_number_to_remove = a_i_numbers_possible[0]
self.o_color.print_success("Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + " " + str(i_y))
self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
self.remove_a_number_from_possibles_in_quadrant(i_number_to_remove=i_new_number_to_remove, i_x=i_x, i_y=i_y)
self.o_color.print_label("V Leaving scan for " + str(i_number_to_remove) + " in col " + str(i_x))
def remove_a_number_from_possibles_in_quadrant(self, i_number_to_remove, i_x, i_y):
"""
:param i_number_to_remove:
:param i_x:
:param i_y:
:return:
"""
i_x_quadrant = int(i_x / 3)
i_y_quadrant = int(i_y / 3)
i_x_ini = i_x_quadrant * 3
i_x_end = i_x_ini + 2
i_y_ini = i_y_quadrant * 3
i_y_end = i_y_ini + 2
for i_y_rel in range(i_y_ini, i_y_end + 1):
for i_x_rel in range(i_x_ini, i_x_end + 1):
a_i_numbers_possible = self.a_map[i_y_rel][i_x_rel]
if len(a_i_numbers_possible) == 1 and a_i_numbers_possible[0] == i_number_to_remove:
# This is the right cell, ignore it
pass
else:
# Subtract the number from the sequence
if i_number_to_remove in a_i_numbers_possible:
a_i_numbers_possible_old = a_i_numbers_possible.copy()
a_i_numbers_possible.remove(i_number_to_remove)
print("X Removed", i_number_to_remove, "From:", i_x_rel, i_y_rel, a_i_numbers_possible_old, "Pending:", a_i_numbers_possible)
# Nota: Here I had a bug and I was "liant-la parda"
# if len(a_i_numbers_possible) == 1:
# # Trigger it again for the number recently discovered
# i_new_number_to_remove = a_i_numbers_possible[0]
# string_ints = [str(int) for int in ints]
# self.o_color.print_success("X Found " + str(i_new_number_to_remove) + " From: " + str(i_x) + "x" + str(i_y) + "[]")
# self.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_new_number_to_remove, i_y=i_y)
# self.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_new_number_to_remove, i_x=i_x)
def check_if_number_possibles_in_quadrant_is_unique(self, i_number_to_check, i_x, i_y):
"""
:param i_number_to_remove:
:param i_x:
:param i_y:
:return: b_found
"""
i_x_quadrant = int(i_x / 3)
i_y_quadrant = int(i_y / 3)
i_x_ini = i_x_quadrant * 3
i_x_end = i_x_ini + 2
i_y_ini = i_y_quadrant * 3
i_y_end = i_y_ini + 2
i_number_of_occurrences_found = 0
i_x_position_number = 0
i_y_position_number = 0
b_unique = False
for i_y_rel in range(i_y_ini, i_y_end + 1):
for i_x_rel in range(i_x_ini, i_x_end + 1):
a_i_numbers_possible = self.a_map[i_y_rel][i_x_rel]
for i_number_in_possibles in a_i_numbers_possible:
if len(a_i_numbers_possible) > 1 and i_number_in_possibles == i_number_to_check:
# This is the right cell, ignore it
i_number_of_occurrences_found += 1
i_x_position_number = i_x_rel
i_y_position_number = i_y_rel
if i_number_of_occurrences_found > 1:
# Unsuccessful
break
if i_number_of_occurrences_found == 1:
# Success!
a_i_numbers_possible = [i_number_to_check]
self.a_map[i_y_position_number][i_x_position_number] = a_i_numbers_possible
b_unique = True
return b_unique, i_x_position_number, i_y_position_number
def check_if_number_possibles_in_row_is_unique(self, i_number_to_check, i_y):
"""
:param i_number_to_check:
:param i_x:
:param i_y:
:return:
"""
i_number_of_occurrences_found = 0
i_x_position_number = 0
i_y_position_number = 0
b_unique = False
for i_x_rel in range(0, 9):
a_i_numbers_possible = self.a_map[i_y][i_x_rel]
for i_number_in_possibles in a_i_numbers_possible:
if len(a_i_numbers_possible) > 1 and i_number_in_possibles == i_number_to_check:
# This is the right cell, ignore it
i_number_of_occurrences_found += 1
i_x_position_number = i_x_rel
i_y_position_number = i_y
if i_number_of_occurrences_found > 1:
# Unsuccessful
break
if i_number_of_occurrences_found == 1:
# Success!
a_i_numbers_possible = [i_number_to_check]
self.a_map[i_y_position_number][i_x_position_number] = a_i_numbers_possible
b_unique = True
return b_unique, i_x_position_number, i_y_position_number
def get_map_drawing_as_string(self, a_map_alternative=None):
s_map = ""
i_counter_y = 0
s_separator_rows = "="
a_map_to_use = self.a_map
if a_map_alternative is not None:
a_map_to_use = a_map_alternative
s_map = s_map + s_separator_rows * 37 + "\n"
for a_row in a_map_to_use:
i_counter_y += 1
if i_counter_y == 3:
i_counter_y = 0
s_separator_rows = "="
else:
s_separator_rows = "-"
s_map = s_map + "|"
i_counter = 0
for a_i_numbers_possible in a_row:
i_counter += 1
if len(a_i_numbers_possible) == 1:
s_number = str(a_i_numbers_possible[0])
else:
s_number = " "
if i_counter == 3:
s_separator = "|"
i_counter = 0
else:
s_separator = "¦"
s_map = s_map + " " + s_number + " " + s_separator
s_map = s_map + "\n"
s_map = s_map + s_separator_rows * 37 + "\n"
# Replace 0 by " "
s_map = s_map.replace("0", " ")
s_map = s_map + "\n\n"
i_total_numbers_found, a_s_numbers_found = self.get_total_numbers_found()
s_map = s_map + "Total numbers found: " + str(i_total_numbers_found) + " Numbers found: " + " ".join(a_s_numbers_found) + "\n"
return s_map
def get_map_drawing_of_possibles_as_string(self, a_map_alternative=None):
s_map = ""
i_counter_y = 0
s_separator_rows = "="
a_map_to_use = self.a_map
if a_map_alternative is not None:
a_map_to_use = a_map_alternative
s_map = s_map + self.o_color.color_blue(s_separator_rows * ((9 * ( 9 + 2 )) + 10)) + "\n"
for a_row in a_map_to_use:
i_counter_y += 1
if i_counter_y == 3:
i_counter_y = 0
s_separator_rows = "="
else:
s_separator_rows = "-"
s_map = s_map + self.o_color.color_blue("|")
i_counter = 0
for a_i_numbers_possible in a_row:
i_counter += 1
if len(a_i_numbers_possible) == 1:
# The right number
s_number = str(a_i_numbers_possible[0]).center(9)
s_number = self.o_color.color_success(s_number)
else:
a_i_numbers_possible_string = []
for i_number in a_i_numbers_possible:
s_number = str(i_number)
# Replace by the color sequence
if i_number == 2:
s_number = self.o_color.color_red(s_number)
if i_number == 3:
s_number = self.o_color.color_yellow(s_number)
if i_number == 4:
self.o_color.color_magenta(s_number)
a_i_numbers_possible_string.append(s_number)
# s_number = "".join(a_i_numbers_possible_string).ljust(9)
s_number = "".join(a_i_numbers_possible_string) + " " * (9-len(a_i_numbers_possible))
if i_counter == 3:
s_separator = self.o_color.color_blue("|")
i_counter = 0
else:
s_separator = self.o_color.color_blue("¦")
s_map = s_map + " " + s_number + " " + s_separator
s_map = s_map + "\n"
s_map = s_map + self.o_color.color_blue(s_separator_rows * ((9 * (9 + 2)) + 10)) + "\n"
# Replace 0 by " "
s_map = s_map.replace("0", " ")
return s_map
def get_total_numbers_found(self):
i_total_numbers_found = 0
a_s_numbers_found = []
for i_y in range(0, self.i_height):
for i_x in range(0, self.i_width):
a_i_numbers_possible = self.a_map[i_y][i_x]
if len(a_i_numbers_possible) == 1:
i_total_numbers_found = i_total_numbers_found + 1
i_number_found = self.a_map[i_y][i_x][0]
s_number_found = str(i_number_found)
if s_number_found not in a_s_numbers_found:
a_s_numbers_found.append(s_number_found)
return i_total_numbers_found, a_s_numbers_found
if __name__ == "__main__":
o_color = ColorUtils()
o_map = SudokuMap(9, 9, o_color=o_color)
o_map.set_number(i_number=1, i_x=1, i_y=0)
o_map.set_number(3, 4, 0)
o_map.set_number(8, 7, 0)
o_map.set_number(8, 0, 1)
o_map.set_number(7, 3, 1)
o_map.set_number(4, 5, 1)
o_map.set_number(6, 8, 1)
o_map.set_number(3, 2, 2)
o_map.set_number(9, 6, 2)
o_map.set_number(2, 1, 3)
o_map.set_number(4, 4, 3)
o_map.set_number(6, 7, 3)
o_map.set_number(5, 0, 4)
o_map.set_number(6, 3, 4)
o_map.set_number(2, 5, 4)
o_map.set_number(8, 8, 4)
o_map.set_number(3, 1, 5)
o_map.set_number(8, 4, 5)
o_map.set_number(7, 7, 5)
o_map.set_number(2, 2, 6)
o_map.set_number(6, 6, 6)
o_map.set_number(9, 0, 7)
o_map.set_number(4, 3, 7)
o_map.set_number(3, 5, 7)
o_map.set_number(2, 8, 7)
o_map.set_number(8, 1, 8)
o_map.set_number(6, 4, 8)
o_map.set_number(1, 7, 8)
# Extra
# o_map.set_number(2, 0, 0)
# Speculative
o_map.set_number(7, 0, 3)
# Another map
o_map2 = SudokuMap(9, 9, o_color=o_color)
o_map2.set_number(i_number=5, i_x=0, i_y=0)
o_map2.set_number(i_number=9, i_x=5, i_y=0)
o_map2.set_number(i_number=7, i_x=2, i_y=1)
o_map2.set_number(i_number=2, i_x=7, i_y=1)
o_map2.set_number(i_number=2, i_x=0, i_y=2)
o_map2.set_number(i_number=3, i_x=4, i_y=2)
o_map2.set_number(i_number=1, i_x=5, i_y=2)
o_map2.set_number(i_number=9, i_x=7, i_y=2)
o_map2.set_number(i_number=7, i_x=0, i_y=3)
o_map2.set_number(i_number=1, i_x=2, i_y=3)
o_map2.set_number(i_number=6, i_x=3, i_y=3)
o_map2.set_number(i_number=9, i_x=4, i_y=3)
o_map2.set_number(i_number=4, i_x=8, i_y=3)
o_map2.set_number(i_number=1, i_x=4, i_y=4)
o_map2.set_number(i_number=6, i_x=0, i_y=5)
o_map2.set_number(i_number=7, i_x=4, i_y=5)
o_map2.set_number(i_number=4, i_x=5, i_y=5)
o_map2.set_number(i_number=3, i_x=6, i_y=5)
o_map2.set_number(i_number=1, i_x=8, i_y=5)
o_map2.set_number(i_number=5, i_x=1, i_y=6)
o_map2.set_number(i_number=3, i_x=3, i_y=6)
o_map2.set_number(i_number=6, i_x=4, i_y=6)
o_map2.set_number(i_number=8, i_x=8, i_y=6)
o_map2.set_number(i_number=6, i_x=1, i_y=7)
o_map2.set_number(i_number=7, i_x=6, i_y=7)
o_map2.set_number(i_number=9, i_x=3, i_y=8)
o_map2.set_number(i_number=3, i_x=8, i_y=8)
# Extra help while not implemented the best algorithm
# =============================================================================================================
# | 5 ¦ 148 ¦ 48 | 7 ¦ 2 ¦ 9 | 148 ¦ 3 ¦ 6 |
# -------------------------------------------------------------------------------------------------------------
# | 13489 ¦ 13489 ¦ 7 | 48 ¦ 48 ¦ 6 | 148 ¦ 2 ¦ 5 |
# -------------------------------------------------------------------------------------------------------------
# | 2 ¦ 48 ¦ 6 | 5 ¦ 3 ¦ 1 | 48 ¦ 9 ¦ 7 |
# =============================================================================================================
# | 7 ¦ 38 ¦ 1 | 6 ¦ 9 ¦ 358 | 2 ¦ 58 ¦ 4 |
# -------------------------------------------------------------------------------------------------------------
# | 348 ¦ 2348 ¦ 3458 | 28 ¦ 1 ¦ 358 | 6 ¦ 7 ¦ 9 |
# -------------------------------------------------------------------------------------------------------------
# | 6 ¦ 289 ¦ 589 | 28 ¦ 7 ¦ 4 | 3 ¦ 58 ¦ 1 |
# =============================================================================================================
# | 14 ¦ 5 ¦ 2 | 3 ¦ 6 ¦ 7 | 9 ¦ 14 ¦ 8 |
# -------------------------------------------------------------------------------------------------------------
# | 3489 ¦ 6 ¦ 3489 | 1 ¦ 458 ¦ 58 | 7 ¦ 45 ¦ 2 |
# -------------------------------------------------------------------------------------------------------------
# | 148 ¦ 7 ¦ 48 | 9 ¦ 458 ¦ 2 | 145 ¦ 6 ¦ 3 |
# =============================================================================================================
# By best algorithm I mean that the last in the middle vertical quadrant from the top right horizontally,
# only 5 can be in a column. That clarifies that 5 must go to the other column in last quadrant, first column. Coord 6x8
# o_map2.set_number(i_number=5, i_x=6, i_y=8)
# ERROR traces from a bug fixed to mentioned during the code review
# Surprisingly this fails
# > Leaving scan for 8 in row1
# V Scanning for removing 8 in col 4
# V Removed 8 From: 4 7 [5, 8] Pending: [5]
# Found 5 From: 4 7
#
# > Scanning for removing 5 in row 7
# > Removed 5 From: 5 7 [5, 8] Pending: [8]
# > Found 8 From: 5 7
#
# > Scanning for removing 8 in row 7
# > Removed 8 From: 0 7 [3, 8] Pending: [3]
# > Found 3 From: 0 7
o_map = o_map2
print(o_map.get_map_drawing_as_string())
b_changes_found = True
while b_changes_found is True:
b_changes_found = False
for i_y in range(0, o_map.i_height):
b_found = o_map.detect_and_remove_a_number_from_possibles_from_a_row(i_y=i_y)
if b_found is True:
print(o_map.get_map_drawing_as_string())
for i_y in range(0, o_map.i_height):
o_map.o_color.print_label("Scanning quadrants for row " + str(i_y))
for i_number in range(1, 10):
for i_x in range(0, o_map.i_width):
b_found, i_x_found, i_y_found = o_map.check_if_number_possibles_in_quadrant_is_unique(i_number_to_check=i_number, i_x=i_x, i_y=i_y)
if b_found is True:
# Search again
b_changes_found = True
o_map.remove_a_number_from_possibles_in_a_row(i_number_to_remove=i_number, i_y=i_y_found)
o_map.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number, i_x=i_x_found)
b_found, i_x_found, i_y_found = o_map.check_if_number_possibles_in_row_is_unique(i_number_to_check=i_number, i_y=i_y)
if b_found is True:
b_changes_found = True
o_map.remove_a_number_from_possibles_in_a_column(i_number_to_remove=i_number, i_x=i_x_found)
if b_changes_found is True:
print(o_map.get_map_drawing_as_string())
# @TODO: Implement check if number in quadrant can only go to a column, to remove the non possible in that column from another quadrant
# @TODO: Implement check if in a line only one number can go to a column.
print(o_map.get_map_drawing_as_string())
print(o_map.get_map_drawing_of_possibles_as_string())