I'm writing a "crypto library" and gradually adding algortihms. Now it's just ancient cryptography, but I'm going to expand the library. It doesn't depend on any external libraries.
class Crypto():
def __init__(self, rotInt=3, letters=list("abcdefghijklmnopqrstuvwxyz")):
self.letters = letters
self.length = len(self.letters)
self.rotInt = rotInt
def rot(self, string, rotInt, letters, length): # To decrypt, use a negative number
result = []
for letter in string:
if letter == " ": # Skip spaces
result.append(" ")
else:
letterIndex = letters.index(letter)
resultIndex = letterIndex + rotInt
resultIndex = resultIndex % length-1
result.append(letters[resultIndex])
return "".join(result) # Return result as a string
def getRotArr(self, string, negative=False): # Negative is for decryption
result = []
for char in string:
if negative:
result.append(0-(self.letters.index(char)+1))
else:
result.append(self.letters.index(char)+1)
return result
def polyalphabetic(self, string, rotArr, letters, length):
result = []
rotArrIndex = 0
for letter in string:
if letter == " ": # Skip spaces
result.append(" ")
else: # Spaces would mess up rotArrIndex
if rotArrIndex > len(rotArr)-1: # Repeat when end of rotArr is reached
rotArrIndex = 0
rotInt = rotArr[rotArrIndex]
letterIndex = letters.index(letter)
resultIndex = letterIndex + rotInt
resultIndex = resultIndex % length
result.append(letters[resultIndex])
rotArrIndex += 1
return "".join(result) # Return result as a string
-
4\$\begingroup\$ I feel like the obligatory "You should not roll your own crypto libraries" warning should be put here, because using your own implementation of preestablished crypto while fun or good for a quick exercise of skills usually results in some 'holes' in the mix. So, obligatory warning about rolling your own crypto. \$\endgroup\$Thomas Ward– Thomas Ward2016年10月09日 20:10:50 +00:00Commented Oct 9, 2016 at 20:10
-
2\$\begingroup\$ @ThomasWard This isn't supposed to be a crypto lib to use. Just a personal challenge, "can I code a crypto lib that can .....". \$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 21:00:01 +00:00Commented Oct 9, 2016 at 21:00
-
\$\begingroup\$ Yes, I know, but that's why I said "while it can be fun or good for a skills exercise" in my previous post. It's just an obligatory warning to ship with everything (so that others who may think "This guy's cool for writing his own crypto library for things" won't go using it.) It's a standard 'obligatory warning' to the world, is all, wasn't implying you're going to be using it in production :) \$\endgroup\$Thomas Ward– Thomas Ward2016年10月09日 21:00:58 +00:00Commented Oct 9, 2016 at 21:00
-
1\$\begingroup\$ @ThomasWard Or worse, "Hey, this guy can write his own crypto lib and it looks simple, what if I wrote my own for this website I was assigned, too" :P \$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 21:02:35 +00:00Commented Oct 9, 2016 at 21:02
2 Answers 2
Handling exceptions
If you create library user should be aware what is going wrong if he uses it in the wrong way.
def rot(self, string, rotInt, letters, length): # To decrypt, use a negative number
result = []
for letter in string:
if letter == " ": # Skip spaces
result.append(" ")
else:
letterIndex = letters.index(letter)
Here, you will receive IndexError
which is not very informative, probably you would like to make your exceptions for such situations.
Performance of string concatenations
As you can see from this post, most efficient methods are using list
, cStringIO
and list comprehension with str.join
. I recommend you to rewrite your rot
method to use list comprehension and make all required checks in the beginning of the method.
Sanity defaults
def rot(self, string, rotInt, letters, length)
length
parameter is just a length of the letters
, so it can be computed in the method.
Because rot
method is not the static method, you already have initialized class instance so you can use letters
array received in __init__
method in case if the user does not provide letters
to the rot
method itself.
For an example, you can write something like:
def rot(self, string, rotation_index, letters=None):
letters = letters or self.letters
length = len(letters)
Data structures for characters set
Storing characters set in the form of the list
in order to use index
method every time you what to figure out a position of the element is not the best choice in case of performance. You should probably use additional dict
for excample:
index_of = dict([(e, i) for i, e in enumerate(letters)])
You will get dict
with keys of your letters and values - their positions in the letters iterable. So you can find out position and find is character found in your characters set with
<character> in index_of
index_of[<character>]
-
\$\begingroup\$ Exceptions: Good point Performance: I'll take a look on that, thanks Defaults: I tried
def rot(self, string, rotInt, letters=self.letters, length)
and it didn't work (obviously, I just tried it) and forgot to tweak the args.index_of
: Cool trick. Thanks a lot. \$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 16:13:40 +00:00Commented Oct 9, 2016 at 16:13 -
\$\begingroup\$ @SamuelShifterovich it does not require to pass defaults direct into method declaration string. You can just leave them with
None
by default and make all the checks in the method body. \$\endgroup\$outoftime– outoftime2016年10月09日 16:19:26 +00:00Commented Oct 9, 2016 at 16:19 -
\$\begingroup\$ Yeah, what I was going to do.
, letters=None, .....): letters=self.letters if letters == None else letters
\$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 16:22:02 +00:00Commented Oct 9, 2016 at 16:22 -
\$\begingroup\$ @SamuelShifterovich probably you would like to use
or
statement instead. \$\endgroup\$outoftime– outoftime2016年10月09日 16:25:06 +00:00Commented Oct 9, 2016 at 16:25 -
1\$\begingroup\$ Dictionary comprehensions are a thing, so there is no need for an intermediate list. You can either use
dict((e, i) for i, e in enumerate(letters))
or{e: i for i, e in enumerate(letters)}
. \$\endgroup\$Graipher– Graipher2016年10月10日 10:06:20 +00:00Commented Oct 10, 2016 at 10:06
rotInt
, letters
, length
are defined in class. Use it.
Instead of for loop
use map
.
def rot(self, string, letters=None, rotInt=None):
if letters is None:
letters = self.letters
length = len(letters)
if rotInt is None:
rotInt = self.rotInt
return ''.join(map(lambda letter: letter in letters and letters[(letters.index(letter) + rotInt) % length] or letter, string))
and or
It's like bool ? a : b
in C
/ C++
. It's equivalent to:
if bool:
return a
else:
return b
-
\$\begingroup\$ I don't understand the
and
andor
statements here, could you please explain them? \$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 16:32:52 +00:00Commented Oct 9, 2016 at 16:32 -
\$\begingroup\$ So in this context it's equivalent to
return letters[(letters.index(letter) + self.rotInt) % self.length] if letters[(letters.index(letter) + self.rotInt) % self.length] else letter
? \$\endgroup\$Samuel Shifterovich– Samuel Shifterovich2016年10月09日 17:00:51 +00:00Commented Oct 9, 2016 at 17:00 -
\$\begingroup\$ Yes, but only if
string
contains only letters fromletters
. \$\endgroup\$Tomasz Jakub Rup– Tomasz Jakub Rup2016年10月09日 17:19:35 +00:00Commented Oct 9, 2016 at 17:19