Replace characters in string from dictionary mapping - python

I'm pretty new to python, so forgive me if I am missing an obvious built-in function.
I have a dictionary mapping I generated like the following:
dictionary = dict(zip(restAlphaSet,list(item)))
where restAlphaSet it a string and list(item) is list converted iteration
I am trying to use this to replace all the characters in my string. I found a replaceAll function online that looks like the following:
def replace_all(text, dic):
for i, j in dic.iteritems():
if i != j:
text = text.replace(i, j)
return text
Unfortunately, this is flawed as if the mapping has a->b, b->a, then nothing would get changed as the b's would be changed back to the a's.
I found the translate function, but it doesn't accept a dictionary input.

Translations are way faster.
>>> import string
>>> text.translate(string.maketrans("".join(restAlphaSet),"".join(item)))

You are overlooking the translate function. See here for a usage example.

Related

How to create an encryption/decryption function using indexes?

I have been trying to solve this problem for hours now and I have no idea how. I am not going to write down the problem, only the part I am struggling with.
Let's say we have:
letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
key = 'guwyrmqpsaeicbnozlfhdkjxtvGUWYRMQPSAEICBNOZLFHDKJXTV'
message = 'wncodhrlf'
As you can see, w in the message is equivalent to c, n is to o etc so the message spells out 'computer'. I need to define functions that can decode/encrypt a message using a random key, and I have been told that this is solved through indexes.
If you have any idea how to solve this please tell me. I do not need you to write a code, I simply need to be directed to a solution because I keep getting lost. Thank you for your time.
Simple translation:
>>> message.translate(str.maketrans(key, letters))
'computers'
If I understand correctly the actual letter and the encrypted letter have the same index in the letters and key variables respectively.
So to parse an encrypted string, you can just loop through the letters of the string, find each character's index in the key string, then get the letter at that index in the letters string.
For example, calling key.index('w') should return 2. Calling letters[2] will then return 'c'.
To encrypt an unencrypted string, you just do the opposite. E.g. to encrypt the 'c', call letters.index('c'), which returns 2. Then calling key[2] should return 'w'.
First off you need to access each individual letter of the message. One possible way is with indexes:
value = message[0]
From there, you need to see where in the key this letter is. String objects in Python have several helper methods.
index = key.index("a") # Finds 9 as the index
And now you have an index, finding the corresponding value in the list of letters is as simple as using an index:
decoded = letters[9] # Finds "j" in the list of letters
I'll leave it to you to piece this together.
I will note that many of these could have been found in the documentation, it's quite approachable, I recommend trying to read through it.
Explicit Python code using dictionary:
letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
key = 'guwyrmqpsaeicbnozlfhdkjxtvGUWYRMQPSAEICBNOZLFHDKJXTV'
message = 'wncodhrlf'
assert len(letters) == len(key)
key_letter_mapping = {}
for i in range(len(key)):
key_letter_mapping[key[i]] = letters[i]
result = ''
for c in message:
result += key_letter_mapping[c]
print(result)

Python string letter substitution using zip()

I was going through some Python challenges and this particular one has been bugging my mind and thought it would be worth getting some explaining. It reads:
Have the function LetterChanges(str) take the str parameter being passed and modify it using the following algorithm. Replace every letter in the string with the letter following it in the alphabet (ie. c becomes d, z becomes a). Then capitalize every vowel in this new string (a, e, i, o, u) and finally return this modified string.
Example:
Input: "fun times!"
Output: gvO Ujnft!
The code:
def LetterChanges(str):
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW"
changes = "bcdEfghIjklmnOpqrstUvwxyzABCDEFGHIJKLMNOPQRSTUVWZ"
mapping = { k:v for (k,v) in zip(str+letters,str+changes) }
return "".join([ mapping[c] for c in str ])
I understand that it takes two strings, letters and changes. It uses the zip() function that takes iterators and 'zips' them, forming an iterator in the form of a dictionary. k:v for (k,v) It's a dict comprehension.
My doubts are:
What exactly is happening with str+letters,str+changes and why it had to be done?
[ mapping[c] for c in str ] Why is it that by doing this, we accomplish the replacement of every key with its value or has it says in the challenge description: "Replace every letter in the string with the letter following it in the alphabet"
This line:
mapping = { k:v for (k,v) in zip(str+letters,str+changes) }
As you already observed, creates a dictionary using dictionary comprehension syntax. The resulting dictionary will associate each letter with the "new" letter to be used when translating the string. Usually, it would be done like this:
mapping = {k: v for k, v in zip(source, destination)}
Or even shorter:
mapping = dict(zip(source, destination))
However, the next line does the following:
"".join([ mapping[c] for c in str ])
It blindly transforms every single character in str doing a lookup in the dictionary that was just created. If the string contains any character that is not in the mapping, this fails.
To get around this issue, whoever wrote the above code used the silly trick of first adding every single character of the string to the map, associating it with itself, and then adding the corresponding mapping for characters to be replaced.
So here:
mapping = { k:v for (k,v) in zip(str+letters,str+changes) }
The str+ before letters and before changes prepends the whole content of the string to both the originals and the replacements, creating a mapping for each character of the string that is not in letters.
This is the same as:
mapping = {k: k for k in str}
mapping.update({k: v for k, v in zip(letters, changes)})
Which is anyway both awful and slow, so to answer your question:
why it had to be done?
Because whoever wrote the code decided to. There's no need for it, it takes O(len(str)) time to build the mapping, going through the whole string, when there really is no need to. No Python programmer would have wrote it that way.
The 'good' way of doing it would have been:
mapping = dict(zip(source, destination))
return ''.join(mapping.get(c, c) for c in str)
All in all, the above code is pretty awkward and IMHO accomplishes the task in a very messy way.
Easy to spot problems are:
The mapping iterates over the whole string, which is totally unneeded.
A mapping is created to replace characters, but does not take advantage of the already existing str.maketrans() and str.translate() built-in methods available in Python.
The letters X, Y, Z are missing from the letters string, and therefore not transformed.
The list comprehension inside join is totally unneeded, it could be done without the square brackets [].
The variable name str overrides the global type name str, which is bad and should not be done.
A better solution would be:
def LetterChanges(s):
old = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
new = 'bcdEfghIjklmnOpqrstUvwxyzAZABCDEFGHIJKLMNOPQRSTUVWXY'
table = str.maketrans(old, new)
return s.translate(table)
Even better would be to pre-calculate the table only one time and then use the already created one on successive calls:
def LetterChanges(s, table={}):
if not table:
old = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
new = 'bcdEfghIjklmnOpqrstUvwxyzAZABCDEFGHIJKLMNOPQRSTUVWXY'
table.update(str.maketrans(old, new))
return s.translate(table)
Performance:
Original: 1.081s for 100k translations of Hello World!.
Updated: 0.400s for 100k translations of Hello World! (4.5x speedup).
Updated with caching: 0.082s for 100k translations of Hello World! (22.5x speedup).
What exactly is happening with str+letters,str+changes and why it had to be done?
Because the input string "fun times!" doesn't just contain letters from the alphabet; it also contains a space ' ' and an exclamation mark '!'. If these aren't keys in the dictionary mapping, then mapping[c] will raise a KeyError when c is one of those characters.
So the purpose of zip(str + letters, str + changes) is to ensure that every character present in the string is mapped to itself in the dictionary, before adding the actually-required transformations into the dictionary. Note that because it's str + ... with str first, any letters of the alphabet in str will map to themselves first, and then be overwritten by the mapping from letters to changes.
That said, it would be simpler to use mapping.get instead of mapping[...], since the get method allows a default to be returned in case the key is not present. In that case, we don't have to make sure every character in the input string is present as a key in the dictionary.
def letter_changes(string):
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW"
changes = "bcdEfghIjklmnOpqrstUvwxyzABCDEFGHIJKLMNOPQRSTUVWZ"
mapping = { k: v for (k, v) in zip(letters, changes) }
return "".join(mapping.get(c, c) for c in string)
Here mapping.get(c, c) means, "get the mapping associated with the key c, or if c is not a key in the dictionary, just use c itself". This means a symbol like ' ' or '!' which is not in the dictionary will be left unchanged.

Python > passing variables of a string into a method

I'm looking to replace a list of characters (including escaped characters) with another.
for example:
l=['\n','<br>','while:','<while>','for:','<for>']
s='line1\nline2'
s.replace(l[0],[l[1])
but passing the list indices through the method produces no effect.
I've also tried using
s=l[1].join(s.split(l[0]))
How can I replace a list of characters with another without expressing the pairs each time in the function?
As I said in the comments, the problem with your code is that you assumed that the replace works in-place. It does not, you have to assign the value it returns to the variable.
But, there is a better way of doing it that involves dictionaries. Take a look:
d = {'\n': '<br>', 'while:': '<while>', 'for:': '<for>'}
s = 'line1\nline2\nwhile: nothing and for: something\n\nnothing special'
for k, v in d.items():
s = s.replace(k, v)
print(s) # line1<br>line2<br><while> nothing and <for> something<br><br>nothing special
The advantage of using dictionaries in this case is that you make it very straightforward what you want to replace and what with. Playing with the indexes is not something you want to do if you can avoid it.
Finally, if you are wondering how to convert your list to a dict you can use the following:
d = {k: v for k, v in zip(l[::2], l[1::2])}
which does not break even if your list has an odd number of elements.
l=['\n','<br>','while:','<while>','for:','<for>']
s='line1\nline2'
for i in range(0, len(l), 2):
s = s.replace(l[i], l[i+1])
You simply have to iterate over the list containing your desired pairs, stepping over 2 values each time. And then assign the result of the replacement to the variable itself (replace doesn't do inline replacement because strings are inmutable in Python)

new dict record from string in python

I have strings list, and want to compare each string in list a number of its entrance. I try it by this code:
words = dict()
for str in lst:
words[str] += 1
Whan I run it, I have error with the output:
KeyError: 'in'
What is a problem?
Words is an empty dictionary. No matter what str (a very bad variable name btw) would be you will get a KeyError.
You may want to take a look at defaultdict

Missing letter from encryption

My encryption is almost working correctly but not quite:
d1={'H':'Z','E':'P','l':'O','O':'J'}
def encrypt(table,string):
encrypted=""
for k,v in table.iteritems():
k=string[0:]
encrypted+=v
return encrypted
print encrypt(d1,"HELLO")
This returns "ZPOJ." It needs to return "ZPOOJ." How can I make this work?
Thanks.
The following code works. You should loop on string rather than table. Make sure that all character using in the string are defined as keys in table. l is replaced with L in d1.
d1={'H':'Z','E':'P','L':'O','O':'J'}
def encrypt(table,string):
encrypted=""
for c in string:
encrypted+=table[c]
return encrypted
print encrypt(d1,"HELLO")
It looks a bit like homework so I'll try to only give hints…
Problems in your current code :
k=string[0:]
This gets the whole string every loop, where you probably only want one character from it.
In any cases, you are not using k inside the loop.
encrypted+=v
This is the only place where you add to the result, but you are taking the value directly from the dictionary values. You end up with a string containing all values of the dictionary, disregarding the plaintext you are encrypting.
You want to loop over the plaintext string and for each character, look up the corresponding value in the dictionary. Something like this:
for k in string:
encrypted = encrypted + table[k]
Note that you have a lowercase L in your dictionary.

Categories

Resources