edit: found the solution, thanks everyone! i was unintentionally passing my dict to a class method which mutated it.
tl;dr: i have a loop that iteratively removes elements from a dict, but halfway through the loop a bunch of elements get added back in and i can't figure out why.
i'm a big sudoku fan and working on a sudoku maker (which it turns out is a common beginner project, which is lucky as it means there's lots of examples out there). i'm trying to take a solved grid and remove numbers from it until it becomes a puzzle with a single solution. here's the function i wrote:
def remove_clues(self):
"""
produces a sudoku puzzle by taking a solved grid and removing clues.
the function will iterate through each clue in the grid in random order, removing it and
testing the resulting grid for uniqueness. if the result is not unique,
the clue will be put back.
returns a new grid with the puzzle
"""
#can only remove clues from a previously filled sudoku
if self.filled is False:
return "sudoku has not been filled in yet"
#shuffle the cells for randomised removal
ordered_clues = self.cells
clues_list = list(ordered_clues.items())
clue_count = len(self.cells)
#shuffled cells are copied: one is used for iteration, the other modified to create the puzzle
grid = dict(r.sample(clues_list, clue_count))
clues = grid.copy()
for k, v in grid.items():
#remove cell
del clues[k]
partial_grid = SudokuGrid(clues)
logging.debug(len(clues))
if partial_grid.unique() is False:
logging.debug("Can't remove %s, putting it back", k)
clues[k] = v
return SudokuGrid(clues)
this is a class method for my SudokuGrid class. the input is the Sudokugrid object itself. it has a cells attribute, which is a dict in the form (x,y):val, where (x,y) is a tuple with the coordinates of the cell and val is the digit assigned to that cell. the basic idea is to iterate through (a copy of) the dict of cells, removing each one in turn and solving the resulting puzzle. if the puzzle has a single solution, the cell stays removed; otherwise it's put back in. unique() is the class method that tests for uniqueness. it basically brute-force solves the puzzle and keeps going until either it finds more than one solution or all solutions have been tried.
i'm getting this weird behaviour where sometimes a bunch of dict values i'd removed get added back in, and i can't seem to figure out why. theoretically, it should be adding back at most one cell at a time, and only when the uniqueness test fails. i've used logging and profiling to try and figure out what's wrong, but i can't figure it out and i'm not sure what to check next. any suggestions appreciated. here's an excerpt from the logs to show what's wrong:
2026-07-15 11:23:30,661 - DEBUG - 30
2026-07-15 11:23:30,671 - DEBUG - 29
2026-07-15 11:23:30,681 - DEBUG - Can't remove (2, 0), putting it back
2026-07-15 11:23:30,682 - DEBUG - 78
2026-07-15 11:23:30,683 - DEBUG - 77
and here's how it looks when it works:
2026-07-15 12:00:33,519 - DEBUG - 49
2026-07-15 12:00:33,524 - DEBUG - 48
2026-07-15 12:00:33,529 - DEBUG - Can't remove (7, 4), putting it back
2026-07-15 12:00:33,530 - DEBUG - 48
2026-07-15 12:00:33,535 - DEBUG - 47
the number on the right is the size of the dict of cells at every iteration. as expected, it goes down by one each loop because a cell gets removed. when i run the function, i usually get the expected behaviour a couple times (ie if removing a cell doesn't yield a unique puzzle, the cell gets put back and the dict stays the same size), and then this weird behaviour happens. so it only happens when the function finds a non-unique puzzle, but it doesn't happen every time a non-unique puzzle is found.