Missing letter from encryption - python

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.

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.

going from word to number as in keys in a phone using a dictonary

Hello I have already done a through search before asking this question as I always do. I am trying to do use a dictionary to go from a written word to each individual letters corresponding letter on a phone key board using a dictionary in python. This is easy to do without a dictionary, but using a dictionary although faster to code is quite confusing to me. Help would be appreciated. My code so far is
def phone (word):
d = {'A''B''C':2,'D''E''F':3,'G''H''I':4,'J''K''L':5}
for i in range (len(word)):
word.split ()
return d[word]
the word I am trying to use is 'ADGJ' just as a test.
my errors that I am getting:
File "<pyshell#13>", line 1, in <module>
phone('ADGJ')
File "C:\Users\Christopher\Desktop\pratice.py", line 195, in phone
return d[word]
KeyError: 'ADGJ'
I have a key error I thought the word.split would take care of any issues but it doesn't. any suggestions?
thank you
I changed the code up a bit: I now have this:
def phone (word):
d = {'A':2, 'B':3}
word = word.split()
return d[word]
but I get a new error:
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
phone ('ABABBAA')
File "C:\Users\Christopher\Desktop\pratice.py", line 194, in phone
return d[word]
TypeError: unhashable type: 'list'
the word I am using just consits of 'ABBABABA' just because that is all I have defined in my dictionary since this is a test of understanding.
I guess I still need something to make the connection for the dictionary function thing to work but still trying to figure out what that is...
This is not valid Python
d = {'A''B''C':2,'D''E''F':3,'G''H''I':4,'J''K''L':5}
You would need to define each key, value pair
d = {
'A':2, 'B':2, 'C':2,
'D':3, 'E':3, ... etc
}
You can then convert the word into the corresponding digits
def getNums(word):
return ''.join(str(d[i]) for i in word)
>>> getNums('ADGJ')
'2345'
Your syntax is a little off; you can't let assignment in a dictionary fall through like in a switch statement.
Try this:
d = {'A':2,'B':2,'C':2,'D':3,'E':3,'F':3,'G':4,'H':4,'I':4,'J':5,'K':5,'L':5}
OK, so let me try to help you.
This is your current code:
def phone (word):
d = {'A':2, 'B':3}
word = word.split()
return d[word]
The first line defines your function signature, that means you've got a function called phone which takes a parameter called word. My first comment is: chose appropriate names for functions and variables. phone is not a "function", since a function is kind of an instruction or a command like thing, but never mind (letters_to_phonenumber would be better I think).
The second line defines a dictionary, which maps 'A' to 2, 'B' to '3'. That's OK for now.
The third line overwrites your word variable with the return value of the split() function, which is a method of the string class. Let's look up the documentation for this: https://docs.python.org/2/library/stdtypes.html#str.split
str.split([sep[, maxsplit]]):
Return a list of the words in the string, using sep as the delimiter string.
Since you obviously did not define a sep(arator), we have to figure out what the function will do. Reading further says:
If sep is not specified or is None, a different splitting algorithm is
applied: runs of consecutive whitespace are regarded as a single
separator, and the result will contain no empty strings at the start
or end if the string has leading or trailing whitespace. Consequently,
splitting an empty string or a string consisting of just whitespace
with a None separator returns [].
So it will look for whitespace within your string. You don't know what a whitespace is? Let's google: http://en.wikipedia.org/wiki/Whitespace_character
In computer science, whitespace is any character or series of
whitespace characters that represent horizontal or vertical space in
typography. When rendered, a whitespace character does not correspond
to a visible mark, but typically does occupy an area on a page.
OK, now we know, that whitespace is like space or tab etc. A string like "ABABBAA" does not contain any whitespace, so split() will obviously return only a list with exactly one item in it: the input string itself.
Let's fire up the python interpreter to check this (this is a common way of debugging):
>>> 'ABABBAA'.split()
['ABABBAA']
The next line in your code is return d[word]. So the function terminates here and returns an output value, namely d[word]. But what is the value of d[word]? Well, d is a dictionary (with the keys 'A' and 'B') and you try to find the value of the key ['ABABBAA']. But there is no such key in your dictionary d, let alone there is no way to create a key for a dictionary, since a key has to be a hashable object. What is a hashable object? Let's google: https://docs.python.org/2/glossary.html
hashable: An object is hashable if it has a hash value which never
changes during its lifetime (it needs a hash() method), and can be
compared to other objects (it needs an eq() or cmp() method).
Hashable objects which compare equal must have the same hash value.
Hashability makes an object usable as a dictionary key and a set
member, because these data structures use the hash value internally.
All of Python’s immutable built-in objects are hashable, while no
mutable containers (such as lists or dictionaries) are. Objects which
are instances of user-defined classes are hashable by default; they
all compare unequal (except with themselves), and their hash value is
their id().
OK, so 'A' would be hashable ;-) and any kind of number of string etc. but not a list, in this sense.
So what now? You have to find a way to somehow separate the letters in your input word. This can be easily done with slicing, or simply iterating over the string (in Python, strings are iterable):
for letter in word:
# this loop will iterate over word and assign each of its letters to
# the variable `letter`, which you can use in this scope
But how do we actually return the phone number? This will not work:
def phone (word):
d = {'A':2, 'B':3}
for letter in word:
return d[letter]
Why? Because it will stop at the first letter and terminate the function (remember the return statement?).
The way to go is to collect all the numbers and when we're done, simply put all together and return them. This is a common way to handle such problems. We first initialise a list, which we can manipulate in each for-iteration:
def phone (word):
d = {'A':2, 'B':3}
digits = []
for letter in word:
digits.append(d[letter])
return digits
Great! Looks better now:
>>> phone('ABA')
[2, 3, 2]
Now try to figure out how to return a real number instead of a list.
This is btw. kind of a basic workflow of a programmer. A lot of research and look-up in (API) documentation, solving puzzles and looking at few lines of code hours long. If you don't love it, you'll never become a programmer.

Slicing strings in a comprehension

I'm I need to slice the leading character off the valued a dictionary - but only if the length of the value is greater than 1. Currently I'm doing this with a dictionary comprehension:
new_dict = {item[0]:item[1][1:] for item in old_dict if item.startswith('1')}
but I don't know how to modify this so that keys of length one are left alone.
The keys are the codewords of a Huffman code, and so start with '0' or '1'.
An example code is:
code = {'a':'0', 'b':'10', 'c':'110', 'd':'111'}
The above code works fine for 'b','c','d' but fails for 'a' (this is intensional - it's a unit test).
How do I correctly modify the above example to pass the test?
The nature of a comprehension is that it builds a new object iteratively, so you if you want every key in the original object old_dict to have a corresponding key in new_dict, you simply have to process every key.
Also, you say "I need to slice the leading character off the keys a dictionary", but the code you give slices the leading characters off the values. I assume you mean values. I suggest the following:
new_dict = {key:(value[:1] if len(value) > 1 else value) for key,value in old_dict.iteritems()}
Apart from using sequence assignment to make the iteration a bit clearer, I've used the if expression (equivalent to ternary operator in c-like languages) to incorporate the condition.
I've also dropped your original if clause, because I don't understand you to want to skip values starting with '1'.
I'm not sure which variable is where but you could do something along these lines.
new_dict = { item[0]:item[1][1] if len(item[1]) > 1 else item[0]:item[1] for item in old_dict if item.startswith('1') }
If I understand your question correctly, you can accomplish it with this:
new_dict = {k:v[len(v)>1:] for k,v in old_dict.items()}
v[len(v)>1] will return the key if it is only 1 character, and it will strip off the leading character if it is more than one character
I'm not sure what you are trying to accomplish with if item.startswith('1') is a qualifier for your list comprehension but if you need it you can add it back on. May need to make it v.startswith('1') though.

Replace characters in string from dictionary mapping

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.

Categories

Resources