4
\$\begingroup\$

This is my attempt at the mirror encryption challenge posted on r/dailyprogrammer/. The idea is that each character is to be replaced by its counterpart in the mirror field, as if a laser pointer were reflected off of all of the diagonal barriers in the grid.

Since I am relatively new to python, I'd like for someone to just have a look and point out some things that I need to look out for and where I can improve please.

class Encrypt(object):
 """
 String encryption using a 13x13 mirror field
 """
 mirrorField = (
 ('', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', ''),
 ('A', '', '', '', '\\', '\\', '', '', '/', '\\', '', '', '', '', 'n'),
 ('B', '', '', '', '', '', '', '', '', '', '', '', '', '\\', 'o'),
 ('C', '', '', '', '/', '', '', '', '', '', '', '', '', '', 'p'),
 ('D', '', '', '', '', '', '', '\\', '', '', '', '', '', '\\', 'q'),
 ('E', '', '', '', '', '\\', '', '', '', '', '', '', '', '', 'r'),
 ('F', '', '', '/', '', '', '', '', '', '', '/', '', '', '', 's'),
 ('G', '\\', '', '', '/', '', '', '', '', '', '', '\\', '', '', 't'),
 ('H', '', '', '', '', '', '\\', '', '', '', '', '', '', '', 'u'),
 ('I', '\\', '/', '', '', '', '', '', '', '', '', '', '', '', 'v'),
 ('J', '/', '', '', '', '', '', '', '', '', '', '', '', '', 'w'),
 ('K', '', '', '', '', '', '', '', '', '', '', '\\', '', '', 'x'),
 ('L', '', '', '', '', '\\', '/', '', '', '', '', '', '', '', 'y'),
 ('M', '', '', '', '/', '', '', '', '', '', '', '', '/', '', 'z'),
 ('', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '')
 )
 def __init__(self):
 self.direction = ''
 self.encrypted = ''
 self.running = False
 def start(self, string):
 """
 Starting point for encryption
 Args:
 string (string): String to be encrypted/decrypted
 """
 for char in string:
 y, x = self.find_char_in_mirror(char)
 self.running = True
 # Set direction based on the starting position
 if y == 0:
 self.direction = 'down'
 elif y == 14:
 self.direction = 'up'
 elif x == 14:
 self.direction = 'left'
 else:
 self.direction = 'right'
 self.encrypt(y, x)
 print self.encrypted
 def encrypt(self, y, x):
 """
 Main function for checking direction and updating positions
 Args:
 y (int): y position in the mirrorField
 x (int): x position in the mirrorField
 """
 while(self.running):
 if self.direction == 'left':
 x -= 1
 self.check_mirror_position(y, x, 'down', 'up')
 if self.direction == 'right':
 x += 1
 self.check_mirror_position(y, x, 'up', 'down')
 if self.direction == 'up':
 y -= 1
 self.check_mirror_position(y, x, 'right', 'left')
 if self.direction == 'down':
 y += 1
 self.check_mirror_position(y, x, 'left', 'right')
 def check_mirror_position(self, y, x, fm_direction, bm_direction):
 """
 Checks the corresponding positions for existing mirrors and adjusts the direction accordingly
 Args:
 y (int): y position in the mirrorField
 x (int): x position in the mirrorField
 fm_direction (string): Updated direction if a / mirror is found
 bm_direction (string): Updated direction if a \\ mirror is found
 """
 if Encrypt.mirrorField[y][x] == '/':
 self.direction = fm_direction
 elif Encrypt.mirrorField[y][x] == '\\':
 self.direction = bm_direction
 elif Encrypt.mirrorField[y][x] != '':
 self.running = False
 self.encrypted += Encrypt.mirrorField[y][x]
 else:
 pass
 def find_char_in_mirror(self, char):
 """
 Find the x and y position of a character in the mirrorField
 Args:
 char (string): character to be searched for in mirrorField
 """
 for y, row in enumerate(Encrypt.mirrorField):
 for x, col in enumerate(Encrypt.mirrorField[y]):
 if Encrypt.mirrorField[y][x] == char:
 return y, x
encrypt = Encrypt()
encrypt.start('TpnQSjdmZdpoohd')
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Jun 10, 2016 at 22:54
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Your execution flow is very convoluted and unconventional. (The technical term for that is "spaghetti".) At first glance, it appears that you have defined a useful class and methods. However, on closer inspection, those "functions" are communicating with each other through variables (direction, encrypted, and running) that are somewhat global in nature.

Let's consider the instance variables.

encrypted is cleared in __init__(), built in check_mirror_position(), and output in start(). What if you want to encrypt another string using the same mirror field — what happens if you call start() again?

direction is initialized in __init__() (unnecessarily), reinitialized in start(), consulted in encrypt(), and reassigned in check_mirror_position().

running is initialized to False in __init__() (unnecessarily), reinitialized to True in start(), and tested in encrypt(), until it is set to False in check_mirror_position().


Consider the main code:

encrypt = Encrypt()
encrypt.start('TpnQSjdmZdpoohd')

What does "start" do? If there is a start, where does it finish? (Actually, start() does all of the work from start to finish!) Does it print anything, and if so, where is the print statement? What if you wanted to write unit tests or incorporate the encryption routine into a web application? You couldn't easily do it, since it's hard-wired to print its output to stdout. A well designed class would be used more like this:

encryptor = MirrorFieldEncryptor(grid)
print(encryptor.encrypt('TpnQSjdmZdpoohd'))

As you can see, I would have expected that the encrypt() method would be the one to call to encrypt a string. But that's not what your encrypt() method does. Rather, it accepts a (y, x) position. Huh? Encrypting a string or a character makes sense, but what does it mean to encrypt a position?

check_mirror_position() is unintuitively named, since I would expect a method named check_something to validate rather than build the result.

find_char_in_mirror() makes reasonable sense — it is the only method that works by accepting a parameter and returning its result, with no side-effects! It's not very efficient, though — it potentially scans the entire mirrorField to find the character. Also, since it could be considered a "private" method, it would be conventional to name it _find_char_in_mirror().


What should be done to reorganize the code?

  • Rename start() to encrypt().
  • Rename private methods to more meaningful names, with a leading underscore.
  • Change all instance variables to local variables.
  • Change mirrorField from a static variable to an instance variable, and rename it mirror_field to comply with PEP 8.
  • Change all methods to return useful values.
class MirrorEncrypt(object):
 """
 String encryption using a 13x13 mirror field
 """
 def __init__(self):
 # TODO: read mirror_field from somewhere
 self.mirror_field = (...)
 def encrypt(self, string):
 """Encrypt the string with the mirror field as the key, returning the
 encrypted string."""
 encrypted = ''
 for char in string:
 y, x = self._find_char_in_mirror(char)
 # Set direction based on the starting position
 direction = ('down' if y == 0 else
 'up' if y == 14 else
 'left' if x == 14 else
 'right')
 encrypted += self._follow(y, x, direction)
 return encrypted
 def _follow(self, y, x, direction):
 """Return the resulting character from tracing the beam from the given
 position and direction."""
 while True:
 if direction == 'left':
 x -= 1
 direction = self._turn(y, x, direction, 'down', 'up')
 if direction == 'right':
 x += 1
 direction = self._turn(y, x, direction, 'up', 'down')
 if direction == 'up':
 y -= 1
 direction = self._turn(y, x, direction, 'right', 'left')
 if direction == 'down':
 y += 1
 direction = self._turn(y, x, direction, 'left', 'right')
 if self.mirror_field[y][x] and self.mirror_field[y][x] not in '\\/':
 return self.mirror_field[y][x]
 def _turn(self, y, x, direction, direction_slash, direction_backslash):
 """Determine the new direction of the beam."""
 if self.mirror_field[y][x] == '/':
 return direction_slash
 elif self.mirror_field[y][x] == '\\':
 return direction_backslash
 else:
 return direction
 def _find_char_in_mirror(self, char):
 """..."""
 for y, row in enumerate(self.mirror_field):
 for x, col in enumerate(self.mirror_field[y]):
 if self.mirror_field[y][x] == char:
 return y, x
encrypt = MirrorEncrypt()
print(encrypt.encrypt('TpnQSjdmZdpoohd'))

There is more that could be done. For example, I would analyze the grid to produce a character-to-character mapping, then the text can be encrypted with a simple lookup for each character. (See this question for how I would do it.)

answered Jun 11, 2016 at 7:16
\$\endgroup\$
2
\$\begingroup\$

Without analysing your algorithm, I would change a few things in how the code is arranged.

First, your input is wrong, as the problem states that you only get \13ドル \times 13\$ grid (no letters on the sides of the grid, and your empty strings would become single-space strings). Related to that, the mirror can be given as a list or a tuple of strings, i.e.,

mirrorField = [
 r" \\ /\ ",
 r" \",
 r" / ",
 r" \ \",
 r" \ ",
 r" / / ",
 r"\ / \ ",
 r" \ ",
 r"\/ ",
 r"/ ",
 r" \ ",
 r" \/ ",
 r" / / ",
]

Second, since you're making a class, I'd make a function set_mirror that would accept a mirror as the above list or a single string with newlines, and make a dictionary later used by encrypt() to map the letters. There is no need to translate every letter of the text by parsing the mirror, because that could be slow and lengthy for bigger texts.

Third, using strings "up", "down", etc. is hardly practical. You can use a single variable direction that can be a tuple of two integers, each of them -1, 0, or 1. For example, (0, -1) would mean "up" because (x+0, y+(-1)) is "up" from (x, y). Then your move doesn't need if's. Instead, you can have (x, y) = (x+dir[0], y+dir[1]) or something similar.

I think it would also be a good idea to use raw string literals (r"...") instead of ordinary ones, as I did above, to avoid the need for escaping your hard-coded backslashes.

answered Jun 10, 2016 at 23:49
\$\endgroup\$
5
  • 1
    \$\begingroup\$ Raw strings sound like an appealing idea at first, but they turn out to be not that useful at all, because raw strings aren't completely raw! In particular, the lines that end with a backslash are illegal. In the end, the raw strings create more confusion than benefit. \$\endgroup\$ Commented Jun 11, 2016 at 4:54
  • 1
    \$\begingroup\$ You are right, but given that the grid has fixed size \13ドル \times 13\,ドル one can always add an additional space (or any other character) that wouldn't affect the algorithm, leaving the hardcoded grid easier to view than if each backslash mirror was escaped. \$\endgroup\$ Commented Jun 11, 2016 at 10:42
  • 1
    \$\begingroup\$ Raw strings are weird, though. See what happens with the two consecutive backslashes on the first row. \$\endgroup\$ Commented Jun 11, 2016 at 17:12
  • \$\begingroup\$ 'r" \\ /\ " -> ' \\\\ /\\ ' They remain as 2 backslashes. Why is that a problem? \$\endgroup\$ Commented Jun 11, 2016 at 18:26
  • \$\begingroup\$ My mistake. I was playing around with raw strings embedded inside a docstring, and got confused. \$\endgroup\$ Commented Jun 11, 2016 at 20:55

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.