1
\$\begingroup\$

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
\$\endgroup\$
4
  • 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\$ Commented 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\$ Commented 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\$ Commented 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\$ Commented Oct 9, 2016 at 21:02

2 Answers 2

4
\$\begingroup\$

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>]
\$\endgroup\$
6
  • \$\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\$ Commented 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
    Commented 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\$ Commented Oct 9, 2016 at 16:22
  • \$\begingroup\$ @SamuelShifterovich probably you would like to use or statement instead. \$\endgroup\$
    – outoftime
    Commented 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
    Commented Oct 10, 2016 at 10:06
5
\$\begingroup\$

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
\$\endgroup\$
3
  • \$\begingroup\$ I don't understand the and and or statements here, could you please explain them? \$\endgroup\$ Commented 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\$ Commented Oct 9, 2016 at 17:00
  • \$\begingroup\$ Yes, but only if string contains only letters from letters. \$\endgroup\$ Commented Oct 9, 2016 at 17:19

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.