Source code for sudokutools.analyze

"""Rate and check sudokus.

Functions defined here:
 * find_conflicts(): Check sudoku for conflicting fields.
 * is_solved(): Check, if a sudoku is solved.
 * is_unique(): Check if a sudoku has exactly one solution.
 * rate(): Return an integer representation of the difficulty of a sudoku.
 * score(): Return an integer representation of the work required to solve
            a sudoku.
"""

from sudokutools.solve import dlx
from sudokutools.solvers import CalculateCandidates, \
    NakedSingle, NakedPair, NakedTriple, NakedQuad, NakedQuint, \
    HiddenSingle, HiddenPair, HiddenTriple, HiddenQuad, HiddenQuint, \
    PointingPair, PointingTriple, \
    XWing, Swordfish, Jellyfish, \
    Bruteforce, \
    solve


RATINGS = {
    CalculateCandidates: 0,
    NakedSingle: 1,
    HiddenSingle: 1,
    NakedPair: 2,
    HiddenPair: 2,
    NakedTriple: 2,
    HiddenTriple: 2,
    NakedQuad: 3,
    HiddenQuad: 3,
    NakedQuint: 3,
    HiddenQuint: 3,
    PointingPair: 4,
    PointingTriple: 4,
    XWing: 5,
    Swordfish: 6,
    Jellyfish: 7,
    Bruteforce: 10
}


[docs]def rate(sudoku): """Rate the difficulty of a sudoku and return 0 <= rating <= 10. Args: sudoku (Sudoku): The sudoku to rate. Returns: (int): The rating (a value inclusive between 0 and 10). Note: Only completely solved sudokus get a rating of 0. """ steps = [] solve(sudoku, steps.append) # max() raises a ValueError, if the list is empty. try: return max([RATINGS[step.__class__] for step in steps]) except ValueError: return 0
[docs]def score(sudoku): """Return a score for the given sudoku. The score depends on the number of empty field as well as which solve methods must be used to solve the sudoku. Args: sudoku (Sudoku): The sudoku to score. Returns: (int): The score (a value between 0 and empty * 10, where empty is the number of empty fields in the sudoku). """ steps = [] solve(sudoku, steps.append) return sum([RATINGS[step.__class__] for step in steps])
[docs]def is_solved(sudoku): """Check, if the sudoku is solved. Args: sudoku (Sudoku): The :class:`Sudoku` instance to check. Returns: bool: Whether or not the sudoku is solved. """ return not list(sudoku.empty()) and not list(find_conflicts(sudoku))
[docs]def is_unique(sudoku): """Check if sudoku has exactly one solution. Args: sudoku (Sudoku): The :class:`Sudoku` instance to check. Returns: bool: Whether or not the sudoku is unique. """ solutions = dlx(sudoku) # If we have no solutions return False. try: next(solutions) except StopIteration: return False # If we have two (or more solutions return False # otherwise return True. try: next(solutions) return False except StopIteration: return True
[docs]def find_conflicts(sudoku, *coords): """Yield conflicts in sudoku at coords. If coords is empty all possible coordinates will be searched. Args: sudoku (Sudoku): The :class:`Sudoku` instance to check. coords (iterable of (int, int)): The coordinates to search within. Yields: ((int, int), (int, int), int): tuple of coordinate pairs and the offending value. E.g.: ((2, 3), (2, 6), 2) indicates, that there is a conflict for the fields (2, 3) and (2, 6) because both of them contain a 2. """ if not coords: coords = list(sudoku) for row, col in coords: value = sudoku[row, col] if not value: continue else: for (i, j) in sudoku.surrounding_of(row, col, include=False): if sudoku[i, j] == value: yield ((row, col), (i, j), value)