Change strings with reference to dictionaries

1

To put my question correctly, I will put some "contextual" code. Unfortunately, I had to go to see the solutions of a couple of exercises because it was not clear to me what had to be done and how the code works. I hope you can guide me. First of all, I create a dictionary that has as keys the numbers from 0 to 26 and as values the letters a-z including the blank space "". Here I had no problems and I think it's simple.

import string
string.ascii_lowercase
alphabet = string.ascii_lowercase + " "
letters= dict(enumerate(alphabet,0))

Here my first question arises: How does the code "reason" for encoding? How do you change keys and values and give them another numbering?

encryption_key=3
encoding = {letters[i]:(encryption_key+ i)%27 for i in range(27)}

Having encoding, we are going to encrypt the following message:

message = "hi my name is caesar"

This function changes the letters by numbers according to the values in encoding:

def caesar(message, encryption_key):
    f_string = "".join(str(encoding.get(i)) for i in message)
    return f_string

encoded_message= caesar(message, encryption_key=3)
print(encoded_message)

For its part, it changes the letters of "message" by the corresponding letters of encoding but taking as reference the dictionary letters:

def caesar(message, encryption_key): 
    f_string = "".join([letters[encoding[i]] for i in message])
    return f_string


encoded_message= caesar(message, encryption_key=3)
print(encoded_message)

Basically, I am interested in knowing which logic follows the code in both functions to achieve the desired result, since in both cases I tried to make approximations using "for", "while" "if" and even "in" without no success I appreciate any guidance very much.

    
asked by Alejandro Carrera 16.09.2017 в 10:44
source

1 answer

1

Let's go step by step:

  • alphabet = string.ascii_lowercase + " "

    We get a string with the lowercase letters from "a" to "z" (English alphabet) + space.

  • letters= dict(enumerate(alphabet,0))

    Create a dictionary from an object enumerate , for each item (character) of the string returns a tuple with a counter starting from 0 in this case and the corresponding character. The first element of the tuple (counter) acts as the key and the second (character) of the value of the dictionary.

  • encryption_key=3 encoding = {letters[i]:(encryption_key+ i)%27 for i in range(27)}

    First, with encoding = {letters[i]:i for i in range(27)} we would create a dictionary equal to letters but investing keys and values (we'll go through the keys that go from 0 to 26).

    At this point we must remember how the César cipher works, it is a substitution cipher that is achieved by simply moving each letter a certain number of spaces in the alphabet. In this example, a displacement of three spaces is used.

    We must rotate the alphabet, in this case the "a" will be the third character, the "b" the fourth, etc. This can be done simply added 3 to the value, but ... What about the last letters ?. In this case (3 displacements) the 'y' must become the first character (value 0), the 'z' the second (value 1) and the space the third (value 2). If we add 3 they would simply have values 29, 30 and 31 respectively, which we do not want.

    At this point is where the module operator or rest of the division comes in ( % ). The modular arithmetic allows us to create a ring. Imagine a traditional clock, the hours are always between 1 and 12, when we arrive at 12 we start again with the 1. That is, if it is 3 o'clock and we add 26 hours moving the clock hand we will get 5 hours , not "29". This is because modular arithmetic is applied without knowing it, what we do is not only add, but get the rest of the division between 12 of that sum:

      
    • 3 +26 = 29
    •   
    • 29 divided by 12 = 2 and rest 5.
    •   

    I do not know if with the example the concept is caught, but with the César cipher we do the same only that our "clock" is not 12 hours, but of 27. What we get is to rotate all the letters but keeping their position within the range 0-26 at all times. This is the only really important thing about the code. The rest is simply getting dictionary values using your passwords.

  • First function:

     def caesar(message, encryption_key):
         f_string = "".join(str(encoding.get(i)) for i in message)
         return f_string
    

    It simply takes as arguments the message and the encryption key, which in reality is no more than the number of displacements that are applied to the alphabet.

    (str(encoding.get(i)) for i in message) is a generator that returns for each character of the message (keys in encoding ) its corresponding value in the dictionary encoding . For the "h" it returns its value, "10", for the "i" it returns "11", for the space it returns "2", etc.

    Basically what we get is to get the order that each character has in the alphabet moved 3 positions. str.join is limited to traversing the iterable and returning a string with the string interspersed between each item:

    >>> l = ["1", "2", "3", "4"]
    >>> s = "-".join(l)
    >>> s
    "1-2-3-4"
    

    The function would be equivalent to:

    def caesar(message,  encriptation_key):
        f_string = ""
        for char in message:
            f_string =  f_string + str(encoding.get(char))
        print(f_string)
    
  • Second function:

        def caesar(message, encryption_key): 
            f_string = "".join([letters[encoding[i]] for i in message])
            return f_string
    

    The first function does not help much, it returns us the places that would occupy the letters of message if we move the alphabet 3 positions but it does not return the encrypted message. This function is an extension of the previous one. Simply do what the previous one, only that once obtained the position in the new alphabet, use that position to find the corresponding letter in the original alphabet without moving (dictionary letters ). For the first letter is the "h", it occupies position 8 in the real alphabet, we move it 3 positions, so it happens to occupy position 11. The letter 11 in the alphabet without moving is the "k". This is the process that follows this function, get the position of each character in the displaced alphabet ( encodig ) and use that position to get the corresponding letter in the alphabet without moving ( letters ).

    The function is equivalent to:

    def caesar2(message,  encryption_key):
        f_string = ""
        for char in message:
            posicion =  encoding.get(char)
            f_string = f_string + letters[posicion]
        return f_string
    
  • This as always has been very long ... I think you understand everything, modular arithmetic may be the most complicated, but if you look at the link and look for information on Google you can clarify if you have any doubts.

        
    answered by 16.09.2017 / 12:35
    source