Python string letter substitution using zip() - python

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.

Related

I am trying to implement flames game in python.Not able to remove all matching character?

def flames(l,l1):
for i in l:
if i in l1:
l.remove(str(i))
l1.remove(str(i))
n1=input("Enter first name:")
n2=input("Enter Second name:")
l=list(n1)
l1=list(n2)
flames(l,l1)
print(l)
print(l1)
I didn't completed my code entirely.But i am done with removing matched character.But the problem is its removing only first matched character.It's not removing remaining match characters.What is the problem in my code?
You might find it easier to use a dict instead of a list to keep track of the letters left in each name. For instance, if the name is "Billy", the dict would be: {"b":1, "i":1, "l":2, "y":1}. In this way you can easily remove all instances of the letter because they're all stored in the same location.
Easiest way to create the dictionaries for each name would be dictionary comprehension:
n1 = input("Enter first name: ").lower()
d1 = { char:n1.count(char) for char in n1 }
Of course, the order of these letters doesn't matter and will likely appear in a different order. Also notice that the "B" in "Billy" becomes "b" in the dictionary. This might be a matter of preference for purposes of this game, but generally you would want to convert all characters to lowercase so upper and lower case letters are not treated as distinct.
There's one more thing to consider when using a dict rather than a list to store the characters: you can't modify the dictionary as you're iterating over it, you'll get a RuntimeError. So as you're iterating through d1 (the characters in the first name) and deleting the matching characters in d2, you'll have to remember the matched characters so you can delete them from d1 later.
d1ToDel = []
for k in d1:
if k in d2:
del d2[k]
d1ToDel.append(k)
for k in d1ToDel:
del d1[k]
After that, you just have to sum up the values remaining in each dictionary, which will give you the remaining number of characters after removing all matches. And then do a little modulo arithmetic to figure out which of the "Flames" characters the pair will land on.

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)

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.

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.

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