4
\$\begingroup\$

This script is designed to help solve sequences in Sudoku puzzles with variant constraints (ex: Thermometer, Renban, etc.).


Summary

The user specifies the constraints they want applied as well as the sequence length.

All sequences of that length are then generated and the code eliminates the ones which violate any of the constraints.

Finally, the user is presented with a list of valid sequences as well as a breakdown of valid digits for each position in the sequence.


Background

I come from an academic research background where code often ends up as kinda-works-duck-taped-spaghetti, rather than well-organized and maintainable and I'd like to do better.

I'm looking for feedback mainly on code organisation and general architecture, rather than performance (Even in the worst case scenario, the code takes at most ~1s to run, so it's not very critical).

I'm especially interested in the following areas:

  • How constraints are selected and applied at runtime.
  • How to implement a new constraint (and its user input code) more easily.
  • Interactions between the different classes and what they contain.

Any other general comments are more than welcome.


The code

UserInput serves purely to get basic parameters that the other classes use (so the user doesn't have to input them twice). I'm certain there's a better way to do it, but I'm not sure how.

Constraints implements and checks the different constraints that can be applied. The main issues I see here is how the user must know what constraints are available, and that when implementing a new constraint, it's necessary to add user input code in the get_user_constraints() method too, which seems like a source of problems further down the line.

I'd like to have the user input code in UserInput rather than here, but I'm not sure how to do that while avoiding tightly coupling the two classes together (since some constraints require additional parameters rather than just Y/N).

SequenceCandidates generates and holds the potential sequences. I'm not sure there's something wrong here, perhaps it should be split into two (for generating and holding)?

I removed specific implementation details for constraints since they don't really impact the overall code architecture and make it harder to read. The code still runs, but with hardcoded constraint return values.

import itertools
import numpy as np
class UserInput:
 def __init__(self):
 """Gets and holds basic information from the user."""
 print("Please input the following information:")
 self.sequence_length = int(input("- What is the length of the desired sequence? "))
 self.digits = list(range(1, 10)) # Could be user specified if needed for different size puzzles.
class Constraints:
 """Manages the different constraints that can be applied to a sequence."""
 def __init__(self, sequence_length):
 self.sequence_length = sequence_length
 self.active_constraints = []
 self.sequence = None
 # Other parameters relevant to specific constraints removed for simplicity.
 self.get_user_constraints()
 def get_user_constraints(self):
 """Asks the user what constraints should be applied to the sequence."""
 if input("- Position constraints? "):
 self.add_restriction(Constraints.invalid_digit_position)
 # Removed specific implementation details.
 if input("- Thermometer constraint? "):
 self.add_restriction(Constraints.invalid_thermometer)
 if input("- Arrow constraint? "):
 self.add_restriction(Constraints.invalid_arrow)
 # Removed specific implementation details.
 if input("- Renban constraint? "):
 self.add_restriction(Constraints.invalid_renban)
 def add_restriction(self, constraint):
 """Adds a constraint to the list of constraints that will be tested."""
 self.active_constraints.append(constraint)
 def sequence_is_valid(self, sequence):
 """Validate a sequence against the selected constraints."""
 self.sequence = sequence
 for constraint in self.active_constraints:
 if constraint(self):
 return False
 return True
 # Implementation details for most of the constraints removed for simplicity. 
 # They all check self.sequence against the constraint and return a bool.
 def invalid_duplicate_digits(self):
 """Check if a sequence contains duplicates.
 Note: Currently unused since only non-duplicate sequences are generated."""
 return len(set(self.sequence)) != len(self.sequence) # Sets can't contain duplicates.
 def invalid_digit_position(self):
 return False
 def invalid_arrow(self):
 return True
 def invalid_thermometer(self):
 return True
 def invalid_renban(self):
 return True
class SequenceCandidates:
 def __init__(self, digits, sequence_length):
 self.sequence_length = sequence_length
 self.valid = []
 # Generate all sequences. Not very efficient, but even with 362880 (9!) sequences, its quick enough.
 self.unchecked = list(itertools.permutations(digits, sequence_length))
 def add_to_valid(self, sequence):
 self.valid.append(sequence)
 def display_valid_sequences(self):
 """Display valid sequences as well as valid digits for each position in the sequence."""
 self.valid = np.asarray(self.valid)
 if self.valid.size > 0:
 print(f"\n{self.valid.shape[0]} valid candidates: \n{self.valid}")
 for column in range(self.valid.shape[-1]):
 possible_digits = np.unique(self.valid[:, column])
 print(f"Valid digits for position {column + 1}: {possible_digits}")
 else:
 print("No valid candidates found.")
if __name__ == "__main__":
 user_input_parameters = UserInput()
 constraints = Constraints(user_input_parameters.sequence_length)
 candidates = SequenceCandidates(user_input_parameters.digits, user_input_parameters.sequence_length)
 for sequence in candidates.unchecked:
 if constraints.sequence_is_valid(sequence):
 candidates.add_to_valid(sequence)
 candidates.display_valid_sequences()
asked Oct 28, 2021 at 22:06
\$\endgroup\$
2
  • \$\begingroup\$ You should add back in the constraints, at least one as an example, before asking for review. Please include all code, it's needed for review. \$\endgroup\$ Commented Dec 12, 2021 at 20:52
  • \$\begingroup\$ @ZacharyVance As requested, I added back an example constraint. If all are needed for review, I'm happy to add them all back. \$\endgroup\$ Commented Dec 13, 2021 at 17:45

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.