Skip to content

Instantly share code, notes, and snippets.

@kaya3
Created August 23, 2025 09:37
Show Gist options
  • Save kaya3/189ee0b6311c7694a5a3d28ea181427d to your computer and use it in GitHub Desktop.
Save kaya3/189ee0b6311c7694a5a3d28ea181427d to your computer and use it in GitHub Desktop.
Python script to try to construct a single-cage killer kropki-less sudoku, or prove there isn't one.
# See https://puzzling.stackexchange.com/q/132979/99846
# Converts from (x, y) coordinates to an index in `range(81)`.
def xy_to_index(x, y):
return x + 9 * y
# Converts from an index in `range(81)` to (x, y) coordinates.
def index_to_xy(i):
return i % 9, i // 9
# Yields the indices of the neighbours of the given index.
def neighbours(i):
x, y = index_to_xy(i)
if x > 0: yield xy_to_index(x - 1, y)
if y > 0: yield xy_to_index(x, y - 1)
if x < 8: yield xy_to_index(x + 1, y)
if y < 8: yield xy_to_index(x, y + 1)
# Maps each index to its set of neighbouring indices.
all_neighbours = [set(neighbours(i)) for i in range(81)]
# Searches for cages of size `n` which include all of `including` and none of
# `excluding`. The `boundary` is the set of non-excluded indices adjacent to
# some index in `including`. Each cage is yielded as a sorted tuple.
def all_cages_with(including, boundary, excluding, n):
''''''
if len(including) == n:
yield tuple(sorted(including))
else:
excluding = set(excluding)
boundary = boundary - excluding
for i in boundary:
excluding.add(i)
yield from all_cages_with(
including + (i,),
boundary | all_neighbours[i],
excluding,
n,
)
# Yields all cages of size `n`. This function doesn't find any duplicates, but
# just to be sure, we call `set(all_cages(n))` later anyway.
def all_cages(n):
excluding = set()
for i in range(81):
excluding.add(i)
yield from all_cages_with((i,), all_neighbours[i], excluding, n)
# Returns a dictionary counting how many grids have each possible sum in the
# given cage. Grids with any repeated digits in the cage are not counted.
def cage_histogram(cage, grids):
n = len(cage)
histogram = dict()
for grid in grids:
if len({grid[i] for i in cage}) == n:
s = sum(grid[i] for i in cage)
histogram[s] = histogram.get(s, 0) + 1
return histogram
# Searches for and prints (cage, sum) pairs for which a single-cage killer
# sudoku would have few solutions.
def main(all_solutions):
min_solutions = 4
count = 0
for n in range(2, 10):
cages = set(all_cages(n))
print('Found', len(cages), 'cages of size', n)
for cage in cages:
histogram = cage_histogram(cage, all_solutions)
for cage_sum, num_solutions in histogram.items():
if num_solutions <= min_solutions:
count = (0 if num_solutions < min_solutions else count) + 1
min_solutions = num_solutions
print('Only', num_solutions, 'solutions for sum =', cage_sum, 'in cage', cage)
print('Found', count, 'cages with', min_solutions, 'solutions')
if __name__ == '__main__':
all_solutions = [
tuple(map(int, grid.strip()))
for grid in open('all_solutions.txt').readlines()
]
main(all_solutions)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment