Exemple #1
0
 def _find_sashimi_fish(by_row):
     for candidate in SUDOKU_VALUES_LIST:
         lines = get_2_upto_n_candidates(board, candidate, n + 1, by_row)
         if len(lines) >= n:
             for x_ids in combinations(lines, n):
                 cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
                 houses = {cell for x_id in x_ids for cell in cells[x_id]}
                 all_y_ids = set(idy for idx in x_ids for idy in lines[idx])
                 if len(all_y_ids) in (n, n + 1):
                     if fins := _get_fins(lines, x_ids):
                         impacted_cells = set()
                         for fin_x in fins:
                             fin_ys = list(fins[fin_x])
                             fin_box = _get_fin_box(Fin(fin_x, fin_ys[0]),
                                                    by_row)
                             if len(fin_ys) == 1 or len(fin_ys) == 2 and \
                                     _get_fin_box(Fin(fin_x, fin_ys[1]), by_row) == fin_box:
                                 y_ids = {
                                     y_id
                                     for y_id in all_y_ids
                                     if y_id not in fin_ys
                                 }
                                 if _check_if_sashimi(lines, x_ids, y_ids):
                                     cells = CELLS_IN_COL if by_row else CELLS_IN_ROW
                                     for y_id in y_ids:
                                         if fin_box.intersection(
                                                 cells[y_id]):
                                             impacted_cells = impacted_cells.union(
                                                 fin_box.intersection(
                                                     cells[y_id]))
                                     impacted_cells = impacted_cells.difference(
                                         houses)
                         to_eliminate = {(candidate, cell)
                                         for cell in impacted_cells
                                         if candidate in board[cell]}
                         if to_eliminate:
                             if window:
                                 solver_status.capture_baseline(
                                     board, window)
                                 impacted = {
                                     cell
                                     for cell in impacted_cells
                                     if len(board[cell]) > 1
                                 }
                                 window.options_visible = window.options_visible.union(
                                     houses).union(impacted)
                                 kwargs["house"] = impacted.union(houses)
                             eliminate_options(solver_status, board,
                                               to_eliminate, window)
                             fish_strategies[n][0].clues += len(
                                 solver_status.naked_singles)
                             fish_strategies[n][0].options_removed += len(
                                 to_eliminate)
                             kwargs["solver_tool"] = fish_strategies[n][
                                 0].__name__
                             if window:
                                 kwargs["chain_a"] = _get_fish_nodes(
                                     candidate, x_ids, by_row)
                                 kwargs["eliminate"] = to_eliminate
                             return True
Exemple #2
0
def test_1(solver_status, board, window):
    """ If there is only one cell in the rectangle that contains extra candidates,
    then the common candidates can be eliminated from that cell.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    bi_values = _get_bi_values_dictionary(board, range(81))
    for bi_value, coordinates in bi_values.items():
        if len(coordinates) > 2:
            for triplet in combinations(coordinates, 3):
                rows = {position[0] for position in triplet}
                columns = {position[1] for position in triplet}
                boxes = {position[2] for position in triplet}
                if len(rows) == 2 and len(columns) == 2 and len(boxes) == 2:
                    rectangle = _get_rectangle(rows, columns)
                    if all(bi_value[0] in board[corner] and bi_value[1] in board[corner] for corner in rectangle):
                        for corner in rectangle:
                            if len(board[corner]) > 2:
                                to_eliminate = {(candidate, corner) for candidate in bi_value}
                                other_candidates = set(board[corner]).difference(bi_value)
                                c_chain = _get_c_chain(rectangle, bi_value, other_candidates)
                                solver_status.capture_baseline(board, window)
                                eliminate_options(solver_status, board, to_eliminate, window)
                                if window:
                                    window.options_visible = window.options_visible.union(rectangle)
                                kwargs = {"solver_tool": "uniqueness_test_1",
                                          "c_chain": c_chain,
                                          "eliminate": to_eliminate, }
                                test_1.clues += len(solver_status.naked_singles)
                                test_1.options_removed += len(to_eliminate)
                                return kwargs
    return None
Exemple #3
0
    def _color_wrap():
        """ If in any house (row, column, box) there are two chain cells
         with the same color assigned to a value, then the value can be removed from all cells
         where it is marked with that color """

        conflicted_cells = set()
        conflicting_color = set()

        def _check_houses(houses):
            impacted_nodes = conflicted_cells
            impacting_color = conflicting_color
            for i in range(9):
                house_nodes = component.intersection(houses[i])
                if len(house_nodes) > 1:
                    colors = set(pair[1] for node in house_nodes
                                 for pair in c_chain[node])
                    if len(colors) == 1:
                        impacted_nodes = impacted_nodes.union(house_nodes)
                        impacting_color = impacting_color.union(colors)
                    elif len(house_nodes) > 2:
                        assert (len(colors) == 2)
                        nodes_with_color = defaultdict(set)
                        for node in house_nodes:
                            assert (len(c_chain[node]) == 1)
                            vc_pair = c_chain[node].pop()
                            c_chain[node].add(vc_pair)
                            nodes_with_color[vc_pair[1]].add(node)
                        for color in nodes_with_color:
                            if len(nodes_with_color[color]) > 1:
                                impacted_nodes = impacted_nodes.union(
                                    nodes_with_color[color])
                                impacting_color.add(color)
            return impacted_nodes, impacting_color

        conflicted_cells, conflicting_color = _check_houses(CELLS_IN_ROW)
        conflicted_cells, conflicting_color = _check_houses(CELLS_IN_COL)
        conflicted_cells, conflicting_color = _check_houses(CELLS_IN_BOX)

        if conflicted_cells:
            assert (len(conflicting_color) == 1)
            conflicting_color = conflicting_color.pop()
            to_eliminate = {(value, node)
                            for node in component
                            if (value, conflicting_color) in c_chain[node]}
            for node in conflicted_cells:
                c_chain[node] = {(value, 'red')}
            edges = [edge for edge in graph.edges if edge[0] in component]
            solver_status.capture_baseline(board, window)
            if window:
                window.options_visible = window.options_visible.union(
                    _get_graph_houses(edges))
            eliminate_options(solver_status, board, to_eliminate, window)
            kwargs["solver_tool"] = "color_wrap"
            kwargs["c_chain"] = c_chain
            kwargs["edges"] = edges
            kwargs["eliminate"] = to_eliminate
            simple_colors.options_removed += len(to_eliminate)
            simple_colors.clues += len(solver_status.naked_singles)
            return True
        return False
Exemple #4
0
 def _find_color_wrap(components, c_chains, m_id, s_id):
     color_nodes = _get_color_nodes(c_chains[m_id], value)
     s_edges = {edge for edge in graph.edges if edge[0] in components[s_id]}
     for edge in s_edges:
         for color in color_nodes:
             see_color_nodes_a = color_nodes[color].intersection(
                 ALL_NBRS[edge[0]])
             see_color_nodes_b = color_nodes[color].intersection(
                 ALL_NBRS[edge[1]])
             if see_color_nodes_a and see_color_nodes_b:
                 edges = {
                     edge
                     for edge in graph.edges
                     if edge[0] in components[m_id].union(components[s_id])
                 }
                 to_eliminate = {(value, node)
                                 for node in color_nodes[color]}
                 solver_status.capture_baseline(board, window)
                 if window:
                     window.options_visible = window.options_visible.union(
                         _get_graph_houses(edges))
                 eliminate_options(solver_status, board, to_eliminate,
                                   window)
                 c_chain = {**c_chains[m_id], **c_chains[s_id]}
                 for node in (see_color_nodes_a.pop(),
                              see_color_nodes_b.pop()):
                     c_chain[node] = {(value, 'red')}
                 kwargs["solver_tool"] = "multi_colors-color_wrap"
                 kwargs["c_chain"] = c_chain
                 kwargs["edges"] = edges
                 kwargs["eliminate"] = to_eliminate
                 multi_colors.options_removed += len(to_eliminate)
                 multi_colors.clues += len(solver_status.naked_singles)
                 return True
     return False
Exemple #5
0
 def _color_trap():
     """ two chain cells with different color are pointing at another cell
     outside of the chain that has a candidate equal to the value """
     impacted_cells = [
         cell for cell in range(81) if cell not in component
         and value in board[cell] and len(board[cell]) > 1
     ]
     to_eliminate = []
     for cell in impacted_cells:
         impacting_chain_nodes = component.intersection(set(ALL_NBRS[cell]))
         web_nodes_colors = set(pair[1] for node in impacting_chain_nodes
                                for pair in c_chain[node])
         if len(web_nodes_colors) > 1:
             assert (len(web_nodes_colors) == 2)
             to_eliminate.append((value, cell))
     if to_eliminate:
         edges = [edge for edge in graph.edges if edge[0] in component]
         solver_status.capture_baseline(board, window)
         if window:
             window.options_visible = window.options_visible.union(
                 _get_graph_houses(edges))
         eliminate_options(solver_status, board, to_eliminate, window)
         kwargs["solver_tool"] = "color_trap"
         kwargs["c_chain"] = c_chain
         kwargs["edges"] = edges
         kwargs["eliminate"] = to_eliminate
         kwargs["impacted_cells"] = [pair[1] for pair in to_eliminate]
         simple_colors.options_removed += len(to_eliminate)
         simple_colors.clues += len(solver_status.naked_singles)
         return True
     return False
def _apply_brute_force():
    """ try to resolve the sudoku puzzle by guessing an empty cell clue and then
    calling stack of standard techniques
    The sequence is repeated recursively until the puzzle is solved
    (or critical error occurs - then DeadEndException is being raised)
    """

    next_cell, clue_iterator = _next_cell_to_resolve()
    if next_cell is None:
        return True

    def _restore_board():
        for cell_id, value in enumerate(board_image_stack[-1]):
            board[cell_id] = value

    board_image_stack.append(board.copy())
    iter_stack.append(clue_iterator)
    solver_status_stack.append(copy.deepcopy(solver_status))
    window = data["graph_display"]
    for value in iter_stack[-1]:
        data["iter_counter"] += 1
        solver_status.iteration = data["iter_counter"]
        _restore_board()
        solver_status.restore(solver_status_stack[-1])

        to_eliminate = {(option, next_cell)
                        for option in board[next_cell] if option != value}
        solver_status.capture_baseline(board, window)
        if window:
            window.options_visible.add(next_cell)
        eliminate_options(solver_status, board, to_eliminate, window)

        if config["output_opts"]["iterations"] and data[
                "current_loop"] == config["repeat"] - 1:
            display.iteration(config, data, board, next_cell, value)
        if config["stats"]:
            data["current_path"].append((
                data["current_loop"],
                next_cell // 9 + 1,
                next_cell % 9 + 1,
                value,
                board[next_cell],
            ))
        if window:
            window.draw_board(board,
                              solver_tool="iterate",
                              eliminate=to_eliminate,
                              c_chain={next_cell: {(value, 'lime')}})

        if _try_standard_techniques() and _apply_brute_force():
            iter_stack.pop()
            board_image_stack.pop()
            solver_status_stack.pop()
            return True

    iter_stack.pop()
    _restore_board()
    board_image_stack.pop()
    solver_status_stack.pop()
    return False
Exemple #7
0
 def _type_4_5():
     """ Two wing cells do not belong to the same house (row, column, or box).
     One of the cells has two candidates and the other one has two or three candidates.
     There is one common candidate Z in both cells.
     The other two wing cells form base: together they have three different candidates excluding
     Z (Type 4) or four candidates (Type 5).
     Selecting any base candidate either forces one of the first two cells to take Z value or
     creates XY-Wing (Type 4) or XYZ-Wing (Type 5)
     Rating: 240 (?)
     """
     for wing in wings:
         base_cells = [
             wing[0],
         ]
         for cell_id in wing[1:]:
             if len(set(ALL_NBRS[cell_id]).intersection(wing)) == 3:
                 base_cells.append(cell_id)
         if len(base_cells) == 2:
             tmp = set(wing).difference(base_cells)
             cell_a = tmp.pop()
             cell_b = tmp.pop()
             z = set(board[cell_a]).intersection(board[cell_b])
             conditions = [
                 bool(len(z) == 1),
                 bool(len(board[cell_a]) == 2 or len(board[cell_b]) == 2),
                 bool(len(board[cell_a]) <= 3 and len(board[cell_b]) <= 3),
             ]
             if all(conditions):
                 z = z.pop()
                 impacted_cells = _get_impacted_cells(wing, z)
                 if impacted_cells:
                     to_eliminate = {(z, cell_id)
                                     for cell_id in impacted_cells
                                     if z in board[cell_id]}
                     if to_eliminate:
                         base_candidates = set(''.join(
                             board[cell_id] for cell_id in base_cells))
                         solver_status.capture_baseline(board, window)
                         if window:
                             window.options_visible = window.options_visible.union(
                                 wing).union(impacted_cells)
                         eliminate_options(solver_status, board,
                                           to_eliminate, window)
                         kwargs["solver_tool"] = "wxyz_wing_type_4" if len(base_candidates) == 3 else\
                                                 "wxyz_wing_type_5"
                         kwargs["chain_d"] = _get_chain(
                             board, base_cells, z)
                         kwargs["chain_a"] = _get_chain(
                             board, (cell_a, cell_b), z)
                         kwargs["eliminate"] = to_eliminate
                         kwargs["impacted_cells"] = {
                             a_cell
                             for _, a_cell in to_eliminate
                         }
                         wxyz_wing.options_removed += len(to_eliminate)
                         wxyz_wing.clues += len(solver_status.naked_singles)
                         # print(f'\t{kwargs["solver_tool"]}')
                         return True
     return False
Exemple #8
0
 def _type_1():
     """ Base position may contain 3 candidates (without Z value) or all 4 candidates (including Z).
     Each of the other wing cells contains two candidates, including Z that is common candidate for
     all three cells. Set of the sum of all candidates for the cells is of size 4.
     Rating: 200 (?)
     """
     for wing in wings:
         if len(board[wing[0]]) > 2:
             for cell_id in wing[1:]:
                 if len(set(
                         ALL_NBRS[cell_id]).intersection(wing)) == 3 or len(
                             board[cell_id]) != 2:
                     break
             else:
                 cell_a, cell_b, cell_c = _get_other_cells(wing)
                 if len(_get_cells_candidates(wing[1:], common=False)) == 4:
                     z = _get_cells_candidates(wing[1:])
                     if len(z) == 1:
                         z = z.pop()
                         if len(board[wing[0]]) == 4 or z not in board[
                                 wing[0]]:
                             impacted_cells = _get_impacted_cells(wing, z)
                             if impacted_cells:
                                 to_eliminate = {
                                     (z, cell_id)
                                     for cell_id in impacted_cells
                                     if z in board[cell_id]
                                 }
                                 if to_eliminate:
                                     w = board[cell_c].replace(z, '')
                                     solver_status.capture_baseline(
                                         board, window)
                                     if window:
                                         window.options_visible = window.options_visible.union(
                                             wing).union(impacted_cells)
                                     eliminate_options(
                                         solver_status, board, to_eliminate,
                                         window)
                                     kwargs[
                                         "solver_tool"] = "wxyz_wing_type_1"
                                     kwargs["chain_d"] = _get_chain(
                                         board, (wing[0], ), z, w)
                                     kwargs["chain_a"] = _get_chain(
                                         board, wing[1:], z, w)
                                     kwargs["eliminate"] = to_eliminate
                                     kwargs["impacted_cells"] = {
                                         a_cell
                                         for _, a_cell in to_eliminate
                                     }
                                     wxyz_wing.options_removed += len(
                                         to_eliminate)
                                     wxyz_wing.clues += len(
                                         solver_status.naked_singles)
                                     # print(f'\t{kwargs["solver_tool"]}')
                                     return True
     return False
    def _find_skyscraper(by_row, option):
        cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
        for row_1 in range(8):
            cols_1 = set(col for col in range(9) if option in board[cells[row_1][col]]
                         and len(board[cells[row_1][col]]) > 1)
            if len(cols_1) == 2:
                for row_2 in range(row_1+1, 9):
                    cols_2 = set(col for col in range(9) if option in board[cells[row_2][col]]
                                 and len(board[cells[row_2][col]]) > 1)
                    if len(cols_2) == 2 and len(cols_1.union(cols_2)) == 3:
                        different_cols = cols_1.symmetric_difference(cols_2)
                        cl_1_list = sorted(list(cols_1))

                        cl_2_list = sorted(list(cols_2))
                        corners = list()
                        corners.append((row_1, cl_1_list[0]) if cl_1_list[0] not in different_cols else
                                       (row_1, cl_1_list[1]))
                        corners.append((row_1, cl_1_list[0]) if cl_1_list[0] in different_cols else
                                       (row_1, cl_1_list[1]))
                        corners.append((row_2, cl_2_list[0]) if cl_2_list[0] in different_cols else
                                       (row_2, cl_2_list[1]))
                        corners.append((row_2, cl_2_list[0]) if cl_2_list[0] not in different_cols else
                                       (row_2, cl_2_list[1]))
                        if by_row:
                            corners_idx = [corners[i][0] * 9 + corners[i][1] for i in range(4)]
                        else:
                            corners_idx = [corners[i][1] * 9 + corners[i][0] for i in range(4)]
                        impacted_cells = set(ALL_NBRS[corners_idx[1]]).intersection(ALL_NBRS[corners_idx[2]])
                        for corner in corners_idx:
                            impacted_cells.discard(corner)
                        clues = [cell for cell in impacted_cells if is_digit(cell, board, solver_status)]
                        for clue_id in clues:
                            impacted_cells.discard(clue_id)
                        corners_idx.insert(0, option)
                        to_eliminate = [(option, cell) for cell in impacted_cells if option in board[cell]]  # TODO - check if not set
                        if to_eliminate:
                            solver_status.capture_baseline(board, window)
                            house = set(cells[row_1]).union(set(cells[row_2]))
                            if window:
                                window.options_visible = window.options_visible.union(house).union(impacted_cells)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            kwargs["solver_tool"] = "skyscraper"
                            kwargs["singles"] = solver_status.naked_singles
                            kwargs["skyscraper"] = corners_idx
                            kwargs["subset"] = [option]
                            kwargs["eliminate"] = to_eliminate
                            kwargs["house"] = house
                            kwargs["impacted_cells"] = impacted_cells
                            skyscraper.clues += len(solver_status.naked_singles)
                            skyscraper.options_removed += len(to_eliminate)

                            # print(f'\t{kwargs["solver_tool"]}')

                            return True
        return False
def remote_pairs(solver_status, board, window):
    """ TODO """

    def _find_chain(pair):
        chain_cells = set(pairs_positions[pair])
        ends = [cell_id for cell_id in chain_cells if len(set(ALL_NBRS[cell_id]).intersection(chain_cells)) == 1]
        inner_nodes = [cell_id for cell_id in chain_cells if len(set(ALL_NBRS[cell_id]).intersection(chain_cells)) == 2]
        if len(ends) == 2 and ends[0] not in ALL_NBRS[ends[1]] and len(ends) + len(inner_nodes) == len(chain_cells):
            # print('\n')
            chain = [ends[0]]
            while inner_nodes:
                for node in inner_nodes:
                    if chain[-1] in set(ALL_NBRS[node]):
                        chain.append(node)
                        break
                if chain[-1] in inner_nodes:
                    inner_nodes.remove(chain[-1])
                elif len(chain) % 2 == 0:
                    return []
                else:
                    break
            chain.append(ends[1])
            return chain
        else:
            return []

    set_remaining_candidates(board, solver_status)
    pairs_positions = defaultdict(list)
    for cell in range(81):
        if len(board[cell]) == 2:
            pairs_positions[board[cell]].append(cell)
    pair_chains = [pair for pair in pairs_positions if
                   len(pairs_positions[pair]) > 3 and len(pairs_positions[pair]) % 2 == 0]
    for pair in pair_chains:
        chain = _find_chain(pair)
        if chain:
            impacted_cells = set(ALL_NBRS[chain[0]]).intersection(set(ALL_NBRS[chain[-1]]))
            to_eliminate = [(value, cell) for value in pair for cell in impacted_cells
                            if value in board[cell] and len(board[cell]) > 1]
            if to_eliminate:
                solver_status.capture_baseline(board, window)
                if window:
                    window.options_visible = window.options_visible.union(impacted_cells)
                eliminate_options(solver_status, board, to_eliminate, window)
                kwargs = {
                    "solver_tool": "remote_pairs",
                    "chain": chain,
                    "eliminate": to_eliminate,
                    "impacted_cells": impacted_cells,
                }
                return kwargs

    return {}
Exemple #11
0
def als_xz(solver_status, board, window):
    """  The ALS-XZ rule says that if A and B are Almost Locked Sets (or ALS'es),
    and X is restricted common to A and B, then no other common candidate
    (let's call it Z) can appear outside of A and B in a cell that can see
    all the Z candidates in both A and B.
    Note that it doesn't matter whether or not Z is also restricted common to A and B,
    except that if Z is also restricted common, we can remove X as a candidate
    of any cells outside A and B that can see all the X candidates in both A and B.
    That is, each candidate, in turn, gets to play the role of restricted common,
    giving us the chance to eliminate the other candidate from outside cells.
    Rating: 300-350
    """
    als_es = _get_alses(board)
    for als_a, als_b in combinations(als_es, 2):
        common_values = set(als_a).intersection(als_b)
        cells_a = {cell for candidate in als_a for cell in als_a[candidate]}
        cells_b = {cell for candidate in als_b for cell in als_b[candidate]}
        impacted_cells = {cell
                          for cell in range(81) if len(board[cell]) > 1
                          }.difference(cells_a).difference(cells_b)
        if len(common_values) > 1 and impacted_cells:
            for x in _get_restricted_commons(als_a, als_b, common_values):
                for z in common_values.difference((x, )):
                    common_neighbours = _get_common_neighbours(
                        als_a[z], als_b[z], impacted_cells)
                    if common_neighbours:
                        to_eliminate = {(z, cell)
                                        for cell in common_neighbours
                                        if z in board[cell]}
                        if to_eliminate:
                            chain_a, chain_b, _ = _get_c_chain(als_a=als_a,
                                                               als_b=als_b,
                                                               x=z,
                                                               y=x)
                            solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board,
                                              to_eliminate, window)
                            if window:
                                window.options_visible = window.options_visible.union(
                                    cells_a).union(cells_b).union(
                                        common_neighbours)
                            als_xz.options_removed += len(to_eliminate)
                            als_xz.clues += len(solver_status.naked_singles)
                            return {
                                "solver_tool": "als_xz",
                                "chain_a": chain_a,
                                "chain_b": chain_b,
                                "eliminate": to_eliminate,
                                "impacted_cells":
                                {cell
                                 for _, cell in to_eliminate},
                            }
    return None
 def _find_sue_de_coq_type_2(box, by_rows):
     for cell_1 in CELLS_IN_BOX[box]:
         if len(board[cell_1]) == 2:
             if by_rows:
                 indexes = [row for row in range((box // 3) * 3, (box // 3) * 3 + 3) if row != CELL_ROW[cell_1]]
             else:
                 indexes = [col for col in range((box % 3) * 3, (box % 3) * 3 + 3) if col != CELL_COL[cell_1]]
             for indx in indexes:
                 cells_b = set(CELLS_IN_BOX[box])
                 cells_1 = set(CELLS_IN_ROW[indx]) if by_rows else set(CELLS_IN_COL[indx])
                 cells_2 = [cell for cell in cells_1.difference(cells_b) if len(board[cell]) > 1]
                 cells_3 = [cell for cell in cells_1.intersection(cells_b) if len(board[cell]) > 1]
                 cells_4 = [cell for cell in cells_b.difference(cells_1) if len(board[cell]) > 1]
                 if len(cells_3) == 3:
                     for cell_2 in cells_2:
                         if len(board[cell_2]) == 2 and not set(board[cell_1]).intersection(set(board[cell_2])):
                             options_12 = set(board[cell_1]).union(set(board[cell_2]))
                             options_3 = set(board[cells_3[0]]).union(set(board[cells_3[1]])).union(
                                     set(board[cells_3[2]]))
                             if options_3.issuperset(options_12) and len(options_3.difference(options_12)) == 1:
                                 to_eliminate = []
                                 for opt in board[cell_1]:
                                     for cell in cells_4:
                                         if cell != cell_1 and opt in board[cell]:
                                             to_eliminate.append((opt, cell))
                                 for opt in board[cell_2]:
                                     for cell in cells_2:
                                         if cell != cell_2 and opt in board[cell]:
                                             to_eliminate.append((opt, cell))
                                 opt = options_3.difference(options_12).pop()
                                 for cell in set(cells_2).union(set(cells_4)):
                                     if opt in board[cell]:
                                         to_eliminate.append((opt, cell))
                                 if to_eliminate:
                                     solver_status.capture_baseline(board, window)
                                     house = cells_b.union(cells_1)
                                     pattern = {cell_1, cell_2}.union(cells_3)
                                     impacted_cells = set(cells_2).union(set(cells_3)).union(set(cells_4))
                                     impacted_cells.difference(pattern)
                                     if window:
                                         window.options_visible = window.options_visible.union(house).union(
                                             house)
                                     eliminate_options(solver_status, board, to_eliminate, window)
                                     kwargs["solver_tool"] = "sue_de_coq"
                                     kwargs["singles"] = solver_status.naked_singles
                                     kwargs["sue_de_coq"] = pattern
                                     kwargs["eliminate"] = to_eliminate
                                     kwargs["house"] = house
                                     kwargs["impacted_cells"] = impacted_cells
                                     kwargs["subset"] = [to_eliminate[0][0]]
                                     return True
     return False
Exemple #13
0
 def _find_finned_rccb_mutant_x_wing(box_id):
     pairs_dict = get_house_pairs(CELLS_IN_BOX[box_id], board)
     for pair, cells in pairs_dict.items():
         if len(cells) == 2:
             cells_pos = [(CELL_ROW[cells[0]], CELL_COL[cells[1]]),
                          (CELL_ROW[cells[1]], CELL_COL[cells[0]])]
             values = (pair[0], pair[1])
             for value in values:
                 for row, col in cells_pos:
                     col_2 = [
                         CELL_COL[cell] for cell in CELLS_IN_ROW[row] if
                         value in board[cell] and CELL_BOX[cell] != box_id
                     ]
                     row_2 = [
                         CELL_ROW[cell] for cell in CELLS_IN_COL[col] if
                         value in board[cell] and CELL_BOX[cell] != box_id
                     ]
                     if len(col_2) == 1 and len(row_2) == 1:
                         impacted_cell = set(
                             CELLS_IN_COL[col_2[0]]).intersection(
                                 set(CELLS_IN_ROW[row_2[0]]))
                         cell = impacted_cell.pop()
                         if value in board[cell]:
                             house = set(CELLS_IN_ROW[row]).union(
                                 set(CELLS_IN_COL[col]))
                             impacted_cell = {cell}
                             to_eliminate = [
                                 (value, cell),
                             ]
                             corners = [
                                 value, cells[0], cells[1],
                                 row * 9 + col_2[0], row_2[0] * 9 + col
                             ]
                             solver_status.capture_baseline(board, window)
                             if window:
                                 window.options_visible = window.options_visible.union(
                                     house).union(impacted_cell)
                             eliminate_options(solver_status, board,
                                               to_eliminate, window)
                             kwargs[
                                 "solver_tool"] = "finned_rccb_mutant_x_wing"
                             kwargs["eliminate"] = to_eliminate
                             kwargs["house"] = house
                             kwargs["impacted_cells"] = impacted_cell
                             kwargs["finned_x_wing"] = corners
                             finned_mutant_x_wing.options_removed += len(
                                 to_eliminate)
                             finned_mutant_x_wing.clues += len(
                                 solver_status.naked_singles)
                             return True
     return False
Exemple #14
0
def death_blossom(solver_status, board, window):
    """
    Rating: 360
    """

    unresolved = {cell for cell in range(81) if len(board[cell]) > 1}
    als_es = _get_alses(board)
    for stem_size in (2, 3, 4):
        stems = {cell for cell in range(81) if len(board[cell]) == stem_size}
        for stem in stems:
            all_petals = {}
            for candidate in board[stem]:
                candidate_petals = _get_alses_with_restricted_common(
                    stem, candidate, als_es)
                if candidate_petals:
                    all_petals[candidate] = candidate_petals
            if len(all_petals) == len(board[stem]):
                stem_candidates = board[stem]
                for blossom in _select_2_petals(all_petals, stem_candidates) if stem_size == 2 \
                        else (_select_3_petals(all_petals, stem_candidates) if stem_size == 3
                              else _select_4_petals(all_petals, stem_candidates)):
                    blossom_cells = {
                        cell
                        for petal in blossom for cell in petal
                    }
                    impacted_cells = unresolved.difference(blossom_cells)
                    possible_candidates = set(blossom)
                    to_eliminate = set()
                    for cell in impacted_cells:
                        if possible_candidates.intersection(board[cell]):
                            cell_neighbours = set(ALL_NBRS[cell])
                            for candidate in possible_candidates.intersection(
                                    board[cell]):
                                candidate_cells = blossom[candidate]
                                if candidate_cells.intersection(
                                        cell_neighbours) == candidate_cells:
                                    to_eliminate.add((candidate, cell))
                    if to_eliminate:
                        solver_status.capture_baseline(board, window)
                        eliminate_options(solver_status, board, to_eliminate,
                                          window)
                        if window:
                            window.options_visible = window.options_visible.union(
                                blossom_cells)
                        # print('\tDeath Blossom')
                        return {
                            "solver_tool": "death_blossom",
                            "eliminate": to_eliminate,
                        }
    return None
Exemple #15
0
def test_6(solver_status, board, window):
    """ Suppose exactly two cells in the rectangle contain extra candidates,
    and they are located diagonally across each other in the rectangle.
    Suppose the common candidates are U and V, and none of the other cells
    in the two rows and two columns containing the rectangle contain U.
    Then U can be eliminated from these two cells.
    This is also called a Type 6 Unique Rectangle.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    bi_values = _get_bi_values_dictionary(board, range(81))
    bi_values = {key: value for key, value in bi_values.items() if len(value) > 1}
    for bi_value in bi_values:
        for pair in combinations(bi_values[bi_value], 2):
            if pair[0][0] != pair[1][0] and pair[0][1] != pair[1][1] and pair[0][2] != pair[1][2]:
                node_b = pair[0][0] * 9 + pair[1][1]
                node_d = pair[1][0] * 9 + pair[0][1]
                if len(set(board[node_b]).intersection(bi_value)) == 2 and \
                        len(set(board[node_d]).intersection(bi_value)) == 2:
                    node_a = pair[0][0] * 9 + pair[0][1]
                    node_c = pair[1][0] * 9 + pair[1][1]
                    nodes = [node_a, node_b, node_c, node_d]
                    boxes = {CELL_BOX[node] for node in nodes}
                    if len(boxes) == 2:
                        other_cells = set(CELLS_IN_ROW[pair[0][0]]).union(CELLS_IN_ROW[pair[1][0]]).union(
                            CELLS_IN_COL[pair[0][1]]).union(CELLS_IN_COL[pair[1][1]])
                        other_candidates = ''.join(board[cell] for cell in other_cells)
                        unique_value = None
                        if other_candidates.count(bi_value[0]) == 4:
                            unique_value = bi_value[0]
                        elif other_candidates.count(bi_value[1]) == 4:
                            unique_value = bi_value[1]
                        if unique_value:
                            other_candidates = set(board[node_b]).union(board[node_d]).difference(bi_value)
                            c_chain = _get_c_chain(nodes, bi_value, other_candidates)
                            to_eliminate = {(unique_value, node_b), (unique_value, node_d)}
                            solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            if window:
                                window.options_visible = window.options_visible.union(other_cells)
                            kwargs["solver_tool"] = "uniqueness_test_6"
                            kwargs["c_chain"] = c_chain
                            kwargs["eliminate"] = to_eliminate
                            test_6.clues += len(solver_status.naked_singles)
                            test_6.options_removed += len(to_eliminate)
                            return kwargs
    return None
 def _find_empty_rectangle(idx, by_row):
     cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
     cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
     cells = cells_by_x[idx]
     opts = ''.join(board[cell] for cell in cells if len(board[cell]) > 1)
     for val in SUDOKU_VALUES_LIST:
         if opts.count(val) == 2:
             idy = [j for j in range(9) if val in board[cells[j]]]
             if CELL_BOX[idy[0]] != CELL_BOX[idy[1]]:
                 for i in range(2):
                     for j in range(2):
                         box = by_row_boxes[idx//3][idy[i]//3][j] if by_row else by_col_boxes[idx//3][idy[i]//3][j]
                         central_line = (box // 3) * 3 + 1 if by_row else (box % 3) * 3 + 1
                         box_cells = set(CELLS_IN_BOX[box])
                         central_line_cells = set(cells_by_x[central_line]).intersection(box_cells)
                         cross_cells = box_cells.intersection(central_line_cells.union(set(cells_by_y[idy[i]])))
                         rect_corners = box_cells.difference(cross_cells)
                         corners_values = ''.join(board[cell] for cell in rect_corners)
                         if corners_values.count(val) == 0:
                             hole_cells = list(central_line_cells.difference(set(cells_by_y[idy[i]])))
                             if val in board[hole_cells[0]] or val in board[hole_cells[1]]:
                                 impacted_cell = cells_by_y[idy[(i + 1) % 2]][central_line]
                                 if val in board[impacted_cell]:
                                     to_eliminate = [(val, impacted_cell)]
                                     if to_eliminate:
                                         corners = set(cell for cell in cells_by_x[idx] if val in board[cell])
                                         if val in board[hole_cells[0]]:
                                             corners.add(hole_cells[0])
                                         if val in board[hole_cells[1]]:
                                             corners.add(hole_cells[1])
                                         corners = list(corners)
                                         corners.insert(0, val)
                                         house = set(cells).union(cross_cells)
                                         solver_status.capture_baseline(board, window)
                                         solver_status.capture_baseline(board, window)
                                         if window:
                                             window.options_visible = window.options_visible.union(house)
                                         eliminate_options(solver_status, board, to_eliminate, window)
                                         kwargs["solver_tool"] = "empty_rectangle"
                                         kwargs["house"] = house
                                         kwargs["impacted_cells"] = (impacted_cell,)
                                         kwargs["eliminate"] = [(val, impacted_cell)]
                                         kwargs["nodes"] = corners
                                         empty_rectangle.clues += len(solver_status.naked_singles)
                                         empty_rectangle.options_removed += len(to_eliminate)
                                         return True
     return False
Exemple #17
0
 def _find_color_wing(components, c_chains):
     color_nodes = [
         _get_color_nodes(c_chains[0], value),
         _get_color_nodes(c_chains[1], value)
     ]
     for color_0 in color_nodes[0]:
         for node in color_nodes[0][color_0]:
             for color_1 in color_nodes[1]:
                 if color_nodes[1][color_1].intersection(ALL_NBRS[node]):
                     other_nodes = set(range(81)).difference(
                         components[0].union(components[1]))
                     other_nodes = {
                         node
                         for node in other_nodes
                         if value in board[node] and len(board[node]) > 1
                     }
                     color_a = _get_second_color(color_nodes[0], color_0)
                     color_b = _get_second_color(color_nodes[1], color_1)
                     impacted_cells = set()
                     to_eliminate = []
                     for cell in other_nodes:
                         if color_nodes[0][color_a].intersection(ALL_NBRS[cell]) \
                                 and color_nodes[1][color_b].intersection(ALL_NBRS[cell]):
                             impacted_cells.add(cell)
                             to_eliminate.append((value, cell))
                     if to_eliminate:
                         edges = [
                             edge for edge in graph.edges if edge[0] in
                             components[0].union(components[1])
                         ]
                         solver_status.capture_baseline(board, window)
                         if window:
                             window.options_visible = window.options_visible.union(
                                 _get_graph_houses(edges))
                         eliminate_options(solver_status, board,
                                           to_eliminate, window)
                         kwargs["solver_tool"] = "multi_colors-color_wing"
                         kwargs["c_chain"] = {**c_chains[0], **c_chains[1]}
                         kwargs["edges"] = edges
                         kwargs["eliminate"] = to_eliminate
                         kwargs["impacted_cells"] = impacted_cells
                         multi_colors.options_removed += len(to_eliminate)
                         multi_colors.clues += len(
                             solver_status.naked_singles)
                         return True
     return False
 def _type_1():
     """ Type 1 (Pointing)
     All the candidates for digit X in a box are confined to a single line (row or column).
     The surplus candidates are eliminated from the part of the line that does not intersect with this box.
     Rating: 50
     """
     for house in CELLS_IN_BOX:
         candidates = SUDOKU_VALUES_SET - {
             board[cell]
             for cell in house if len(board[cell]) == 1
         }
         unsolved = {cell for cell in house if len(board[cell]) > 1}
         for possibility in candidates:
             in_rows = set(CELL_ROW[cell] for cell in unsolved
                           if possibility in board[cell])
             in_cols = set(CELL_COL[cell] for cell in unsolved
                           if possibility in board[cell])
             impacted_cells = None
             if len(in_rows) == 1:
                 impacted_cells = set(
                     CELLS_IN_ROW[in_rows.pop()]).difference(house)
             elif len(in_cols) == 1:
                 impacted_cells = set(
                     CELLS_IN_COL[in_cols.pop()]).difference(house)
             if impacted_cells:
                 to_eliminate = {(possibility, cell)
                                 for cell in impacted_cells
                                 if possibility in board[cell]}
                 if to_eliminate:
                     if window:
                         solver_status.capture_baseline(board, window)
                     eliminate_options(solver_status, board, to_eliminate,
                                       window)
                     locked_candidates.clues += len(
                         solver_status.naked_singles)
                     locked_candidates.options_removed += len(to_eliminate)
                     kwargs["solver_tool"] = "locked_candidates_type_1"
                     if window:
                         window.options_visible = window.options_visible.union(
                             house).union(impacted_cells)
                         kwargs["house"] = impacted_cells.union(house)
                         kwargs["eliminate"] = to_eliminate
                         kwargs["chain_a"] = _paint_locked_candidates(
                             house, possibility)
                     return True
     return False
Exemple #19
0
 def _find_fish(by_row):
     for value in SUDOKU_VALUES_LIST:
         lines = get_2_upto_n_candidates(board, value, n, by_row)
         if len(lines) >= n:
             for x_ids in combinations((id_x for id_x in lines), n):
                 y_ids = set(id_y for id_x in x_ids for id_y in lines[id_x])
                 if len(y_ids) == n:
                     cells = CELLS_IN_COL if by_row else CELLS_IN_ROW
                     impacted_cells = {
                         cell
                         for id_y in y_ids for cell in cells[id_y]
                     }
                     cells = CELLS_IN_ROW if by_row else CELLS_IN_COL
                     houses = {
                         cell
                         for id_x in x_ids for cell in cells[id_x]
                     }
                     impacted_cells = impacted_cells.difference(houses)
                     to_eliminate = {(value, cell)
                                     for cell in impacted_cells
                                     if value in board[cell]}
                     if to_eliminate:
                         if window:
                             solver_status.capture_baseline(board, window)
                         kwargs["solver_tool"] = fish_strategies[n][
                             0].__name__
                         eliminate_options(solver_status, board,
                                           to_eliminate, window)
                         fish_strategies[n][0].clues += len(
                             solver_status.naked_singles)
                         fish_strategies[n][0].options_removed += len(
                             to_eliminate)
                         if window:
                             impacted = _get_impacted_lines(
                                 to_eliminate, by_row, board)
                             window.options_visible = window.options_visible.union(
                                 houses).union(impacted_cells)
                             kwargs["chain_a"] = _get_chain(
                                 value, cells, x_ids)
                             kwargs["eliminate"] = to_eliminate
                             kwargs["house"] = impacted.union(houses)
                         return True
     return False
Exemple #20
0
def _naked_subset(solver_status, board, window, subset_size):
    """ Generic technique of finding naked subsets
    A Naked Subset is formed by N cells in a house with candidates for exactly N digits.
    N is the size of the subset, which must lie between 2 and the number of unsolved cells
    in the house minus 2.
    Since every Naked Subset is complemented by a Hidden Subset, the smallest of both sets
    will be no larger than 4 in a standard sized Sudoku.
    """
    set_remaining_candidates(board, solver_status)
    subset_strategies = {
        2: (naked_pair, 60),
        3: (naked_triplet, 80),
        4: (naked_quad, 120),
    }
    for house, subset_dict in get_subsets(board, subset_size):
        subset_cells = {
            cell
            for cells in subset_dict.values() for cell in cells
        }
        impacted_cells = get_impacted_cells(board, subset_cells)
        to_eliminate = {
            (candidate, cell)
            for cell in impacted_cells
            for candidate in set(board[cell]).intersection(subset_dict)
        }
        if to_eliminate:
            kwargs = {}
            if window:
                solver_status.capture_baseline(board, window)
            eliminate_options(solver_status, board, to_eliminate, window)
            subset_strategies[subset_size][0].clues += len(
                solver_status.naked_singles)
            subset_strategies[subset_size][0].options_removed += len(
                to_eliminate)
            kwargs["solver_tool"] = subset_strategies[subset_size][0].__name__
            if window:
                window.options_visible = window.options_visible.union(
                    subset_cells).union(impacted_cells)
                kwargs["chain_a"] = _get_chain(subset_cells, subset_dict)
                kwargs["eliminate"] = to_eliminate
                kwargs["house"] = impacted_cells.union(house)
            return kwargs
    return None
 def _type_2():
     """ Type 2 (Claiming or Box-Line Reduction)
     All the candidates for digit X in a line are confined to a single box.
     The surplus candidates are eliminated from the part of the box that does not intersect with this line.
     Rating: 50 - 60
     """
     for cells in (CELLS_IN_ROW, CELLS_IN_COL):
         for house in cells:
             candidates = SUDOKU_VALUES_SET - {
                 board[cell]
                 for cell in house if len(board[cell]) == 1
             }
             unsolved = {cell for cell in house if len(board[cell]) > 1}
             for possibility in candidates:
                 boxes = {
                     CELL_BOX[cell]
                     for cell in unsolved if possibility in board[cell]
                 }
                 if len(boxes) == 1:
                     impacted_cells = set(
                         CELLS_IN_BOX[boxes.pop()]).difference(house)
                     to_eliminate = {(possibility, cell)
                                     for cell in impacted_cells
                                     if possibility in board[cell]}
                     if to_eliminate:
                         if window:
                             solver_status.capture_baseline(board, window)
                         eliminate_options(solver_status, board,
                                           to_eliminate, window)
                         locked_candidates.clues += len(
                             solver_status.naked_singles)
                         locked_candidates.options_removed += len(
                             to_eliminate)
                         kwargs["solver_tool"] = "locked_candidates_type_2"
                         if window:
                             window.options_visible = window.options_visible.union(
                                 house).union(impacted_cells)
                             kwargs["house"] = impacted_cells.union(house)
                             kwargs["eliminate"] = to_eliminate
                             kwargs["chain_a"] = _paint_locked_candidates(
                                 house, possibility)
                         return True
     return False
Exemple #22
0
def test_5(solver_status, board, window):
    """     Suppose exactly two cells in the rectangle have exactly one extra candidate X,
    and both cells are located diagonally across each other in the rectangle.
    Then X can be eliminated from the cells seen by both of these cells.
    This would be called a Type 5 Unique Rectangle.
    Note that in this case the rectangle does not have a floor or ceiling.
    Rating: 100
    """

    set_remaining_candidates(board, solver_status)
    kwargs = {}
    bi_values = _get_bi_values_dictionary(board, range(81))
    bi_values = {key: value for key, value in bi_values.items() if len(value) > 1}
    for bi_value in bi_values:
        for pair in combinations(bi_values[bi_value], 2):
            if pair[0][0] != pair[1][0] and pair[0][1] != pair[1][1] and pair[0][2] != pair[1][2]:
                node_b = pair[0][0] * 9 + pair[1][1]
                node_d = pair[1][0] * 9 + pair[0][1]
                if board[node_b] == board[node_d] and len(set(board[node_b]).difference(bi_value)) == 1:
                    node_a = pair[0][0] * 9 + pair[0][1]
                    node_c = pair[1][0] * 9 + pair[1][1]
                    nodes = [node_a, node_b, node_c, node_d]
                    boxes = {CELL_BOX[node] for node in nodes}
                    if len(boxes) == 2:
                        other_candidates = set(board[node_b]).difference(bi_value)
                        c_chain = _get_c_chain(nodes, bi_value, other_candidates)
                        z_value = other_candidates.pop()
                        to_eliminate = {(z_value, cell) for cell in set(ALL_NBRS[node_b]).intersection(ALL_NBRS[node_d])
                                        if z_value in board[cell]}
                        if to_eliminate:
                            solver_status.capture_baseline(board, window)
                            eliminate_options(solver_status, board, to_eliminate, window)
                            if window:
                                window.options_visible = window.options_visible.union(c_chain.keys())
                            kwargs["solver_tool"] = "uniqueness_test_4"
                            kwargs["c_chain"] = c_chain
                            kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                            kwargs["eliminate"] = to_eliminate
                            test_5.clues += len(solver_status.naked_singles)
                            test_5.options_removed += len(to_eliminate)
                            return kwargs
    return None
Exemple #23
0
 def _check_rectangles(by_row):
     cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
     cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
     for floor_id_x in range(9):
         bi_values.clear()
         for floor_id_y in range(9):
             cell = cells_by_x[floor_id_x][floor_id_y]
             if len(board[cell]) == 2:
                 bi_values[board[cell]].add((floor_id_x, floor_id_y, CELL_BOX[cell]))
         for bi_value, coordinates in bi_values.items():
             if len(coordinates) == 2:
                 floor_a = coordinates.pop()
                 floor_b = coordinates.pop()
                 x_ids = _get_xyz(7, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                 for x_id in x_ids:
                     ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                     ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                     boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                     if len(boxes) == 2:
                         to_eliminate = None
                         cells = set(ALL_NBRS[ceiling_a]).intersection(ALL_NBRS[ceiling_b])
                         if not _get_candidate_count(cells, bi_value[0]):
                             to_eliminate = {(bi_value[1], ceiling_a), (bi_value[1], ceiling_b)}
                         elif not _get_candidate_count(cells, bi_value[1]):
                             to_eliminate = {(bi_value[0], ceiling_a), (bi_value[0], ceiling_b)}
                         if to_eliminate:
                             rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                             columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                             rectangle = _get_rectangle(sorted(rows), sorted(columns))
                             other_candidates = set(board[ceiling_a]).union(board[ceiling_b]).difference(bi_value)
                             c_chain = _get_c_chain(rectangle, bi_value, other_candidates)
                             solver_status.capture_baseline(board, window)
                             eliminate_options(solver_status, board, to_eliminate, window)
                             if window:
                                 window.options_visible = window.options_visible.union(c_chain.keys()).union(cells)
                             kwargs["solver_tool"] = "uniqueness_test_4"
                             kwargs["c_chain"] = c_chain
                             kwargs["eliminate"] = to_eliminate
                             test_4.clues += len(solver_status.naked_singles)
                             test_4.options_removed += len(to_eliminate)
                             return True
     return False
Exemple #24
0
    def _check_rectangles(by_row):
        cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
        cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW

        for floor_id_x in range(9):
            bi_values = _get_bi_values_dictionary(board, cells_by_x[floor_id_x], by_row)
            for bi_value, coordinates in bi_values.items():
                if len(coordinates) == 2:
                    floor_a = coordinates.pop()
                    floor_b = coordinates.pop()
                    for n in (2, 3, 4):
                        x_ids = _get_xyz(n, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                        for x_id in x_ids:
                            ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                            ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                            boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                            if len(boxes) == 2 and board[ceiling_a] != board[ceiling_b]:
                                z_a = board[ceiling_a].replace(bi_value[0], '').replace(bi_value[1], '')
                                z_b = board[ceiling_b].replace(bi_value[0], '').replace(bi_value[1], '')
                                z_ab = set(z_a).union(z_b)
                                naked_subset, subset_nodes = \
                                    find_naked_subset(n, ceiling_a, ceiling_b, bi_value, z_ab, by_row)
                                if to_eliminate:
                                    rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                                    columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                                    rectangle = _get_rectangle(sorted(rows), sorted(columns))
                                    c_chain = _get_c_chain(rectangle, bi_value, naked_subset, subset_nodes)
                                    solver_status.capture_baseline(board, window)
                                    eliminate_options(solver_status, board, to_eliminate, window)
                                    if window:
                                        window.options_visible = window.options_visible.union(c_chain.keys())
                                    kwargs["solver_tool"] = "uniqueness_test_3"
                                    kwargs["c_chain"] = c_chain
                                    kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                                    kwargs["eliminate"] = to_eliminate
                                    test_3.clues += len(solver_status.naked_singles)
                                    test_3.options_removed += len(to_eliminate)
                                    return True
        return False
 def _reduce_rectangle(a_pair, corners):
     if all(board[corner] == a_pair for corner in corners):
         return False
     to_eliminate = []
     for corner in corners:
         if board[corner] != a_pair:
             subset = [cell for cell in rect if len(board[cell]) == 2]
             if a_pair[0] in board[corner]:
                 to_eliminate.append((a_pair[0], corner))
             if a_pair[1] in board[corner]:
                 to_eliminate.append((a_pair[1], corner))
             if to_eliminate:
                 solver_status.capture_baseline(board, window)
                 eliminate_options(solver_status, board, to_eliminate, window)
                 if window:
                     window.options_visible = window.options_visible.union(set(corners))
                 kwargs["solver_tool"] = "unique_rectangles"
                 kwargs["rectangle"] = rect
                 kwargs["eliminate"] = to_eliminate
                 kwargs["subset"] = subset
                 print('\tUniueness Test 1')
                 return True
     return False
Exemple #26
0
 def _find_xyz_wing(cell_id):
     xyz = set(board[cell_id])
     bi_values = [
         indx for indx in ALL_NBRS[cell_id] if len(board[indx]) == 2
     ]
     for pair in combinations(bi_values, 2):
         xz = set(board[pair[0]])
         yz = set(board[pair[1]])
         if xz != yz and len(xyz.union(xz).union(yz)) == 3:
             z_value = xyz.intersection(xz).intersection(yz).pop()
             impacted_cells = set(ALL_NBRS[cell_id]).intersection(
                 set(ALL_NBRS[pair[0]])).intersection(set(
                     ALL_NBRS[pair[1]]))
             to_eliminate = [
                 (z_value, a_cell) for a_cell in impacted_cells
                 if z_value in board[a_cell] and len(board[cell]) > 1
             ]
             if to_eliminate:
                 solver_status.capture_baseline(board, window)
                 if window:
                     window.options_visible = window.options_visible.union(
                         impacted_cells).union({cell_id, pair[0], pair[1]})
                 eliminate_options(solver_status, board, to_eliminate,
                                   window)
                 kwargs["solver_tool"] = "xyz_wing"
                 kwargs["c_chain"] = _get_c_chain(cell_id, pair[0], pair[1])
                 kwargs["edges"] = [(cell_id, pair[0]), (cell_id, pair[1])]
                 kwargs["eliminate"] = to_eliminate
                 kwargs["impacted_cells"] = {
                     a_cell
                     for _, a_cell in to_eliminate
                 }
                 xyz_wing.options_removed += len(to_eliminate)
                 xyz_wing.clues += len(solver_status.naked_singles)
                 # print('\tXYZ-Wing')
                 return True
     return False
Exemple #27
0
 def _check_rectangles(by_row):
     cells_by_x = CELLS_IN_ROW if by_row else CELLS_IN_COL
     cells_by_y = CELLS_IN_COL if by_row else CELLS_IN_ROW
     for floor_id_x in range(9):
         bi_values = _get_bi_values_dictionary(board, cells_by_x[floor_id_x], by_row)
         for bi_value, coordinates in bi_values.items():
             if len(coordinates) == 2:
                 floor_a = coordinates.pop()
                 floor_b = coordinates.pop()
                 x_ids = _get_xyz(1, board, bi_value, cells_by_y[floor_a[1]], cells_by_y[floor_b[1]])
                 for x_id in x_ids:
                     ceiling_a = x_id * 9 + floor_a[1] if by_row else floor_a[1] * 9 + x_id
                     ceiling_b = x_id * 9 + floor_b[1] if by_row else floor_b[1] * 9 + x_id
                     boxes = {floor_a[2], floor_b[2], CELL_BOX[ceiling_a], CELL_BOX[ceiling_b]}
                     if board[ceiling_a] == board[ceiling_b] and len(boxes) == 2:
                         z_candidate = board[ceiling_a].replace(bi_value[0], '').replace(bi_value[1], '')
                         to_eliminate = set()
                         for cell in set(ALL_NBRS[ceiling_a]).intersection(ALL_NBRS[ceiling_b]):
                             if z_candidate in board[cell]:
                                 to_eliminate.add((z_candidate, cell))
                         if to_eliminate:
                             rows = (floor_a[0], x_id) if by_row else (floor_a[1], floor_b[1])
                             columns = (floor_a[1], floor_b[1]) if by_row else (floor_a[0], x_id)
                             rectangle = _get_rectangle(sorted(rows), sorted(columns))
                             c_chain = _get_c_chain(rectangle, bi_value, {z_candidate, })
                             solver_status.capture_baseline(board, window)
                             eliminate_options(solver_status, board, to_eliminate, window)
                             if window:
                                 window.options_visible = window.options_visible.union(c_chain.keys())
                             kwargs["solver_tool"] = "uniqueness_test_2"
                             kwargs["c_chain"] = c_chain
                             kwargs["impacted_cells"] = {cell for _, cell in to_eliminate}
                             kwargs["eliminate"] = to_eliminate
                             test_2.clues += len(solver_status.naked_singles)
                             test_2.options_removed += len(to_eliminate)
                             return True
     return False
Exemple #28
0
def _hidden_subset(solver_status, board, window, subset_size):
    """ Generic technique of finding hidden subsets
    A Hidden Subset is formed when N digits have only candidates in N cells in a house.
    A Hidden Subset is always complemented by a Naked Subset. Because Hidden Subsets are
    sometimes hard to find, players often prefer to look for Naked Subsets only,
    even when their size is greater.
    In a standard Sudoku, the maximum number of empty cells in a house is 9.
    There is no need to look for subsets larger than 4 cells, because the complementary
    subset will always be size 4 or smaller.
    """

    set_remaining_candidates(board, solver_status)
    subset_strategies = {
        2: (hidden_pair, 70),
        3: (hidden_triplet, 100),
        4: (hidden_quad, 150),
    }

    for cells in (CELLS_IN_ROW, CELLS_IN_COL, CELLS_IN_BOX):
        for house in cells:
            unsolved = {cell for cell in house if len(board[cell]) > 1}
            if len(unsolved) <= subset_size:
                continue
            candidates = set("".join(board[cell] for cell in unsolved))
            if len(unsolved) != len(candidates):
                raise DeadEndException

            candidates_positions = {
                candidate:
                {cell
                 for cell in unsolved if candidate in board[cell]}
                for candidate in candidates
            }
            for subset in itertools.combinations(candidates, subset_size):
                subset_nodes = {
                    cell
                    for candidate in subset
                    for cell in candidates_positions[candidate]
                }
                if len(subset_nodes) == subset_size:
                    to_eliminate = {(candidate, cell)
                                    for cell in subset_nodes
                                    for candidate in board[cell]
                                    if candidate not in subset}
                    if to_eliminate:
                        kwargs = {}
                        if window:
                            solver_status.capture_baseline(board, window)
                        eliminate_options(solver_status, board, to_eliminate,
                                          window)
                        subset_strategies[subset_size][0].clues += len(
                            solver_status.naked_singles)
                        subset_strategies[subset_size][
                            0].options_removed += len(to_eliminate)
                        kwargs["solver_tool"] = subset_strategies[subset_size][
                            0].__name__
                        if window:
                            window.options_visible = window.options_visible.union(
                                unsolved)
                            kwargs["chain_a"] = _get_chain(
                                subset_nodes, candidates_positions)
                            kwargs["eliminate"] = to_eliminate
                            kwargs["house"] = house
                        return kwargs
    return None
Exemple #29
0
def als_xy(solver_status, board, window):
    """  TODO
    Rating: 300
    """
    als_es = _get_alses(board)
    for als_a, als_b in combinations(als_es, 2):
        if _check_if_overlap(als_a, als_b):
            continue
        common_values = set(als_a).intersection(als_b)
        impacted_cells = {cell
                          for cell in range(81) if len(board[cell]) > 1
                          }.difference(als_a).difference(als_b)
        if len(common_values) > 1 and impacted_cells:
            restricted_commons = _get_restricted_commons(
                als_a, als_b, common_values)
            if len(restricted_commons) == 2:
                to_eliminate = set()
                x = list(restricted_commons)[0]
                y = list(restricted_commons)[1]
                for cell in impacted_cells:
                    if x in board[cell] and set(ALL_NBRS[cell]).intersection(als_a[x]) == als_a[x] and \
                            set(ALL_NBRS[cell]).intersection(als_b[x]) == als_b[x]:
                        to_eliminate.add((x, cell))
                    if y in board[cell] and set(ALL_NBRS[cell]).intersection(als_a[y]) == als_a[y] and \
                            set(ALL_NBRS[cell]).intersection(als_b[y]) == als_b[y]:
                        to_eliminate.add((y, cell))
                # if to_eliminate:
                #     print('\tBingo!')
                for z in set(als_a).difference(restricted_commons):
                    for cell in impacted_cells:
                        if z in board[cell] and set(
                                ALL_NBRS[cell]).intersection(
                                    als_a[z]) == als_a[z]:
                            to_eliminate.add((z, cell))
                for z in set(als_b).difference(restricted_commons):
                    for cell in impacted_cells:
                        if z in board[cell] and set(
                                ALL_NBRS[cell]).intersection(
                                    als_b[z]) == als_b[z]:
                            to_eliminate.add((z, cell))
                if to_eliminate:
                    cells_a = {
                        cell
                        for candidate in als_a for cell in als_a[candidate]
                    }
                    cells_b = {
                        cell
                        for candidate in als_b for cell in als_b[candidate]
                    }
                    c_chain, d_chain, _ = _get_c_chain(
                        als_a=als_a,
                        als_b=als_b,
                        x=restricted_commons.pop(),
                        z=restricted_commons.pop())
                    solver_status.capture_baseline(board, window)
                    eliminate_options(solver_status, board, to_eliminate,
                                      window)
                    if window:
                        window.options_visible = window.options_visible.union(
                            cells_a).union(cells_b).union(impacted_cells)
                    # print(f'\n{cells_a = }, \n{cells_b = }, \n{restricted_commons = }')
                    als_xy.options_removed += len(to_eliminate)
                    als_xy.clues += len(solver_status.naked_singles)
                    return {
                        "solver_tool": "als_xy",
                        "chain_a": c_chain,
                        "chain_b": d_chain,
                        "eliminate": to_eliminate,
                        "impacted_cells": {
                            cell
                            for _, cell in to_eliminate
                            if cell not in cells_a.union(cells_b)
                        },
                    }
    return None
Exemple #30
0
def als_xy_wing(solver_status, board, window):
    """  Say we have three Almost Locked Sets A, B and C.
    Suppose:
        A and C share a restricted common Y,
        B and C share a restricted common Z, and
        Y and Z are different digits.
    Then for any digit X that is distinct from Y and Z and is a common candidate for A and B,
    we can eliminate X from any cell that sees all cells belonging to either A or B and having X as a candidate.
    Rating: 320-350
    """
    als_es = _get_alses(board)
    als_cells = {}
    unresolved = {cell for cell in range(81) if len(board[cell]) > 1}
    for idx_0, idx_1, idx_2 in combinations(range(len(als_es)), 3):
        common_values = set(als_es[idx_0]).intersection(
            als_es[idx_1]).intersection(als_es[idx_2])
        if not common_values:
            continue
        if idx_0 not in als_cells:
            als_cells[idx_0] = {
                cell
                for candidate in als_es[idx_0]
                for cell in als_es[idx_0][candidate]
            }
        if idx_1 not in als_cells:
            als_cells[idx_1] = {
                cell
                for candidate in als_es[idx_1]
                for cell in als_es[idx_1][candidate]
            }
        if idx_2 not in als_cells:
            als_cells[idx_2] = {
                cell
                for candidate in als_es[idx_2]
                for cell in als_es[idx_2][candidate]
            }
        for idx_a, idx_b, idx_c in ((idx_0, idx_1, idx_2),
                                    (idx_1, idx_2, idx_0), (idx_0, idx_2,
                                                            idx_1)):
            als_a = als_es[idx_a]
            cells_a = als_cells[idx_a]
            als_b = als_es[idx_b]
            cells_b = als_cells[idx_b]
            als_c = als_es[idx_c]
            cells_c = als_cells[idx_c]
            impacted_cells = unresolved.difference(cells_a).difference(cells_b)
            if impacted_cells:
                for y in _get_restricted_commons(
                        als_a, als_c,
                        set(als_a).intersection(als_c)):
                    for z in _get_restricted_commons(
                            als_b, als_c,
                            set(als_b).intersection(als_c)):
                        if y != z:
                            for x in set(als_a).intersection(als_b).difference(
                                {y, z}):
                                common_neighbours = _get_common_neighbours(
                                    als_a[x], als_b[x], impacted_cells)
                                if common_neighbours:
                                    to_eliminate = {
                                        (x, cell)
                                        for cell in common_neighbours
                                        if x in board[cell]
                                    }
                                    if to_eliminate:
                                        chain_a, chain_b, chain_c = _get_c_chain(
                                            als_a=als_a,
                                            als_b=als_b,
                                            als_c=als_c,
                                            x=x,
                                            y=y,
                                            z=z)
                                        impacted_cells = {
                                            cell
                                            for _, cell in to_eliminate
                                        }.difference(cells_a).difference(
                                            cells_b).difference(cells_c)
                                        solver_status.capture_baseline(
                                            board, window)
                                        eliminate_options(
                                            solver_status, board, to_eliminate,
                                            window)
                                        if window:
                                            window.options_visible = window.options_visible.union(
                                                cells_a).union(cells_b).union(
                                                    cells_c).union(
                                                        common_neighbours)
                                        als_xy_wing.options_removed += len(
                                            to_eliminate)
                                        als_xy_wing.clues += len(
                                            solver_status.naked_singles)
                                        return {
                                            "solver_tool": "als_xy_wing",
                                            "chain_a": chain_a,
                                            "chain_b": chain_b,
                                            "chain_c": chain_c,
                                            "eliminate": to_eliminate,
                                            "impacted_cells": impacted_cells,
                                        }
    return None