Search code examples
pythonalgorithmencodingdecoding

EntwicklerHeld Transposition Cipher


I'm trying to improve my coding skills on entwicklerheld.de and right now I'm trying to solve the transposition cipher challenge:

We consider a cipher in which the plaintext is written downward and diagonally in successive columns. The number of rows or rails is given. When reaching the lowest rail, we traverse diagonally upwards, and after reaching the top rail, there is a change of direction again. Thus, the alphabets [sic] of the message are written in a zigzag pattern. After each alphabet is written, the individual lines are combined to obtain the cipher text.

Given is the plain text "coding" and the number of rails 2. The plain text is now arranged in a zigzag pattern as described above. The encoded text is obtained by combining the lines one after the other.

enter image description here

Thus, the encrypt() function should return the cipher "cdnoig".

The same procedure is used for entire sentences or texts as for individual words. The only thing to note here is that spaces also count as a single character.

Given is the plain text "rank the code" and the number of rails 2. Your function should return the cipher "rn h oeaktecd". This should work with other examples with 2 rails as well.

The encryption is very easy with a multi dimensional array.

My question

I'm stuck at the decryption part.

My idea is to build an array with 0 and 1 (to show were a character has to be). Then fill every array (line 1... line 2 ... line 3) with the characters in the order of the cipher text.

Then I iterate a third time over the array to read the word in zig-zag.

I don't know, but it feels very strange to iterate 3 times over the array. Maybe there is a zig-zag algorithm or so?


Solution

  • You could first define a generator that gives the mapping for each index to the index where the character has to be taken from during encryption. But this generator would not need to get the plain text input, just the length of it. As this generator just produces the indices, it can be used to decrypt as well.

    It was not clear to me whether the question is only about the case where the number of rails is 2. With a bit of extra logic, this can be made for any greater number of rails also.

    Here is how that could look:

    # This generator can be used for encryption and decryption:
    def permutation(size, numrails):
        period = numrails * 2 - 2
        yield from range(0, size, period)  # top rail
        # Following yield-from statement only needed when number of rails > 2
        yield from (
            index
            for rail in range(1, numrails - 1)            
            for pair in zip(range(rail, size, period),
                            range(rail + period - rail*2, size + period, period))
            for index in pair
            if index < size
        )
        yield from range(numrails - 1, size, period)  # bottom rail
    
    def encrypt(plain, numrails):
        n = len(plain)
        return "".join([plain[i] for i in permutation(n, numrails)])
    
    def decrypt(encrypted, numrails):
        n = len(encrypted)
        plain = [None] * n
        for source, target in enumerate(permutation(n, numrails)):
            plain[target] = encrypted[source]
        return "".join(plain)