0

I am very new to Python.

I am a firm believer of simple, concise and efficient algorithmic design as well as coding style. As I am learning Python, I realize that Python does a lot of things behind the scene so that the language itself is super friendly to programmers. This is nice but I wanted to learn deeply about what optimizations I can do or keep a habit of when coding. And today I ran into trouble simplify my code.

The following function is used to create empty spots on a sudoku board based on difficulty level that is chosen.

Here is my code:

class normalSudoku(Board):
    def __init__(self,difficulties):
        super.__init__()
        self.Create_Empty_Entries(difficulties)


    def Create_Empty_Entries(self,difficulties):
        numbers = list(range(0,9))
        if difficulties == "Easy":
            for x in range(25):
                a,b = choice(numbers),choice(numbers)
                if self.sudoku[a][b] != None:
                    self.sudoku[a][b] = None
                    self.holes += 1
                self.holes += 1
            return None

        elif difficulties == "Medium":
            for x in range(35):
                a,b = choice(numbers),choice(numbers)
                if self.sudoku[a][b] != None:
                    self.sudoku[a][b] = None
                    self.holes += 1
            return None

        elif difficulties == "Hard":
            for x in range(45):
                a,b = choice(numbers),choice(numbers)
                if self.sudoku[a][b] != None:
                    self.sudoku[a][b] = None
                    self.holes += 1
            return None

        else:
            for x in range(65):
                a,b = choice(numbers),choice(numbers)
                if self.sudoku[a][b] != None:
                    self.sudoku[a][b] = None
                    self.holes += 1
            return None

As you can see it is very repetitive. Any idea on simplifying it or a more efficient coding style will be appreciated.

Also, is there a better way of initializing a class in python rather than calling __init__() in terms of performance and memory usage? Just like in C++ there is initialization list where it is cleaner and faster.

Please feel free to point out the mistakes I have made. Any advice will be greatly appreciated. Thanks

Martin Evans
  • 45,791
  • 17
  • 81
  • 97

3 Answers3

1

Since the only thing that is changing is the range of numbers being chosen from, I'd recommend creating a dict where difficulty maps to that number then using it in a single function that sets the numbers.

class normalSudoku(Board):
    def __init__(self,difficulties):
        super.__init__()
        self.Create_Empty_Entries(difficulties)


    def Create_Empty_Entries(self,difficulties):
        numbers = list(range(0,9))
        difficulty_values = {'Easy':25,'Medium':35, 'Hard':45, 'Default':65}

        # check the difficulty level exists in the dict. 
        # If it does, use that value, if it doesn't then use the default value
           difficulty = difficulty_values.get(difficulties, difficulty_values['Default'])

            # now use that difficulty to set the numbers once.
            for x in range(difficulty):
            a,b = choice(numbers),choice(numbers)
            if self.sudoku[a][b] != None:
                self.sudoku[a][b] = None
                self.holes += 1
            self.holes += 1
    return None
MattWBP
  • 376
  • 1
  • 10
  • Is there a performance different using `dict.get` or it's just cleaner? –  Sep 10 '17 at 16:22
  • Not sure about faster , although in this case it is less code. Though I understand it to be safer - as it guarantees to return a value for that dict key even if it doesn't exist (you'll get None as default). This way you don't hit an exception. – MattWBP Sep 10 '17 at 16:25
  • I see. Thanks a lot –  Sep 10 '17 at 16:30
1

You could add a check method to you class:

# add this to the class body
def auto_increment(self, a, b):
    if self.sudoku[a][b] != None:
       self.sudoku[a][b] = None
       self.holes += 1
    self.holes += 1
    return

Then you can just pass you parameters to your method using:

self.auto_increment(choices(number), choices(number))

slots are an effective way to reduce memory usage Usage of __slots__?

Meow
  • 1,207
  • 15
  • 23
  • I just read the docs about `__slots__`. I have learned a lot, Thank you! –  Sep 10 '17 at 16:19
0

One option is to move the parameters from code to data, and then operate on the data:

# You could source these two dicts from elsewhere, like a JSON/YAML/config file
difficulties = {
  Easy: {
    size: 25
  },
  Medium: {
    size: 35
  },
  Hard: {
    size: 45
  }
}

defaultDifficulty = {
  size: 65
}

# ...

def Create_Empty_Entries(self, difficultyName):
  if difficultyName in difficulties:
    difficulty = difficulties[difficultyName]
  else:
    difficulty = defaultDifficulty

  numbers = list(range(0,9))
  for x in range(difficulty.size):
    a,b = choice(numbers),choice(numbers)
    if self.sudoku[a][b] != None:
      self.sudoku[a][b] = None
      self.holes += 1
tavnab
  • 2,594
  • 1
  • 19
  • 26
  • By source it from elsewhere do you mean I could create another module and put the dictionaries there / or creating a .txt file and write them in there? Which keyword should I use when sourcing them, **import** or **with** ?And what is the advantage of sourcing? –  Sep 10 '17 at 16:29
  • Both are valid options; it depends on how you want to generate/maintain this data, and whether you want to be able to reload difficulties without restarting your program. If you're not planning on making it much more complex than the above, you might as well just keep the objects in this same module, which is effectively @MattWBP's answer. The advantage of separating data from code is that both can evolve independently of one another. You can tune range size (or other future parameters) without having to change code. It also makes live reloading of difficulties possible, if needed. – tavnab Sep 10 '17 at 16:43