Implementing the Caesar cipher algorithm in Python - python

So I'm trying to do a code that will shift every letter in a word back by a number of letters in the alphabet (wrapping around for the end). For example, if I want to shift by 2 and I input CBE, I should get AZC. or JOHN into HMFL. I got a code to work for only one letter, and I wonder if there's a way to do a nested for loop for python (that works?)
def move(word, shift):
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"
original = ""
for letter in range(26, len(alphabet)):
if alphabet[letter] == word: #this only works if len(word) is 0, I want to be able to iterate over the letters in word.
original += alphabet[letter-shift]
return original

You could start like this
def move(word, shift):
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return "".join([alphabet[alphabet.find(i)-shift] for i in word])
Basically, this list comprehension constructs a list of the single letters. Then, the index of the letter in the alphabet is found by the .find method. The (index - shift) is the desired new index, which is extracted from alphabet. The resulting list is joined again and returned.
Note that it does obviously not work on lowercase input strings (if you want that use the str.upper method). Actually, the word should only consist of letters present in alphabet. For sentences the approach needs to treat whitespaces differently.

Don't find the letter in the alphabet that way -- find it with an index operation. Let char be the letter in question:
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
...
char_pos = alphabet.index(char)
new_pos = (char_pos - shift) % len(alphabet)
new_char = alphabet[new_pos]
Once you understand this, you can collapse those three lines to a single line.
Now, to make it operate on an entire word ...
new_word = ""
for char in word:
# insert the above logic
new_word += new_char
Can you put all those pieces together?
You'll still need your check to see that char is a letter. Also, if you're interested, you can build a list comprehension for all the translated characters and the apply ''.join() to get your new word.
For instance ...
If the letter is in the alphabet (if char in alphabet), shift the given distance and get the new letter, wrapping around the end if needed (% 26). If it's not a capital letter, then use the original character.
Make a list from all these translations, and then join them into a string. Return that string.
def move(word, shift):
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
return ''.join([alphabet[(alphabet.find(char) - shift) % 26]
if char in alphabet else char
for char in word])
print move("IBM", 1)
print move("The 1812 OVERTURE is COOL!", 13)
Output:
HAL
Ghe 1812 BIREGHER is PBBY!

A_VAL = ord('a')
def move(word, shift):
new_word = ""
for letter in word:
new_letter = ord(letter) - shift
new_word += chr(new_letter) if (new_letter >= A_VAL) else (26 + new_letter)
return new_word
Note that this will only work for lowercase words. As soon as you start mixing upper and lowercase letters you'll need to start checking for them. But this is a start. I discarded your nested loop idea because you should avoid those if at all possible.

You could use : chr() give you the character for a ascii number, ord() give you the ascii number for the matching character.
Here is an old Vigenere project :
def code_vigenere(ch,cle):
text = ch.lower()
clef = cle.lower()
L = len(cle)
res = ''
for i,l in enumerate(text):
res += chr((ord(l) - 97 + ord(cle[i%L]) - 97)%26 +97)
return res

Related

Odd uppercase letters in string after using .upper() in a for loop

As the title says, the output contains some uppercase letters, but for the life of me I can't figure out why. <=== Read the entire post to understand the problem. This is the code snippet which I'm assuming causes the 'error'.
def translate(s):
i = 0
word = ""
for letter in s:
word += letter.upper() + (letter * i) + "-"
i += 1
The function takes a string as input and returns a string with first letter being capital and the following letters being multiplied by 1 += 1 (+1 for each set of different letters), followed by "-".
Example:
Input and Output
Input: "ZpglnRxqenU"
Expected Output: "Z-Pp-Ggg-Llll-Nnnnn-Rrrrrr-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-Uuuuuuuuuuu"
Actual Output: "Z-Pp-Ggg-Llll-Nnnnn-RRRRRR-Xxxxxxx-Qqqqqqqq-Eeeeeeeee-Nnnnnnnnnn-UUUUUUUUUUU"
The problem
As you can see, the R:s are all uppercase and so is the U:s. My question is: Why are they uppercase? I know why the first letter is, and that's intended but there should never be more than one uppercase (the first letter) per section (a section being within the bounderies of "-" and "-").
For further refrence: https://www.codewars.com/kata/5667e8f4e3f572a8f2000039/
OBS: I'm not doing this to get an answer to the codewars challenge.
This is the entire code.
def translate(s):
i = 0
word = ""
for letter in s:
word += letter.upper() + (letter * i) + "-"
i += 1
eq1 = list(word)
eq1.reverse()
eq1.remove("-")
eq1.reverse()
word = ""
for y in eq1:
word += y
return word
Lines 7-12 are just my way of dealing with removing the last "-". The output would have an "-" at the end if it wasn't there. I know it's a bad way of dealing with it but I just wanted to finish it as fast as possible.
The R's and U's are uppercase because in the initial string, they are uppercase. They will stay uppercase unless you try to change them to lowercase.
Here is your updated code:
def translate(s):
i = 0
word = ""
for letter in s:
word += letter.upper() + (letter.lower() * i) + "-"
i += 1
Notice the .lower() after letter in (letter * i).
The R:s and U:s are uppercase because we multiply the first letter of the given character. It totally slipped my mind that the character may be uppercase and therefor the multiplies of the letter will too be uppercase.

Create a function that contains only the first occurrence of a letter from the original phrase

The function needs to behave as follow:
The first letter occurrence can be upper or lower case (newPhrase).
Non-alpha characters - are left unchanged.
So far I thought of :
def keepFirstLetter(phrase):
'''Returns a new string that contains only the first occurrence of a
letter from the original phrase.
letterSeenSoFar = ''
newPhrase = ''
if (letterSeenSoFar == '' or letterSeenSoFar[-1] != letterSeenSoFar):
letterSeenSoFar += c
for letter in letterSeenSoFar:
if letter.isalpha:
newPhrase += char
else:
newPhrase += letter
return newPhrase
You seem to be on the right track. If you want to improve you efficiency, you can store the seen letters as a set. Searching a set is O(1).
def unique_first(s):
letters = set()
out = ''
for x in s:
if not x.isalpha():
out += x
continue
if not x.lower() in letters:
out += x
letters.add(x.lower())
return out
The most straightforward, sure-to-have-learned solution is probably:
def keepFirstLetter(phrase):
output = ''
for letter in phrase:
if (letter.lower() not in output.lower()) or (not letter.isalpha()):
output += letter
return output
print(keepFirstLetter('Amy says, "Me?"')) # Amy s, "e?"
(the parens around the two if clauses are optional)
James's solution still gets my vote though.
My name was in the question so let me take a try.
I learned set.add() from James for the first time. (Thank you, James.) James' code is shorter and runs faster (3.48us vs. 3.76us on my PC).
def keepFirstLetter(phrase):
phrase = list(phrase)
'''Returns a new string that contains only the first occurrence of a
letter from the original phrase.'''
letterSeenSoFar = []
newPhrase = ''
for char in phrase:
# if char is not an alphabet, add char to the newPhrase as is
if not char.isalpha():
newPhrase += char
# if char is an alphabet and not seen so far, add char to the newPhrase and append it to letterSeenSoFar
elif char.lower() not in letterSeenSoFar:
letterSeenSoFar.append(char.lower())
newPhrase += char
return newPhrase
print(keepFirstLetter('Amy says, " Me?"'))
This outputs:
Amy s, " e?"

Comparing variables to all items in list in Python

I need to write a function for an edx Python course. The idea is figure out the number of letters and words in series of strings and return the average letter count while only using for/while loops and conditionals. My code comes close to being able to do this, but I cannot. For the life of me. Figure out why it doesn't work. I've been bashing my head against it for two days, now, and I know it's probably something really simple that I'm too idiotic to see (sense my frustration?), but I do not know what it is.
If I'm looking at line 14, the logic makes sense: if i in the string is punctuation (not a letter) and the previous character (char, in this case) is not punctuation (therefore a letter), it should be a word. But it's still counting double punctuation as words. But not all of them.
def averageWordLength(myString):
char = ""
punctuation = [" ", "!", "?", ".", ","]
letters = 0
words = 0
if not myString == str(myString):
return "Not a string"
try:
for i in myString:
if i not in punctuation:
letters += 1
elif i in punctuation:
if char not in punctuation:
words += 1
elif char in punctuation:
pass
char = i
if letters == 0:
return "No words"
else:
average = letters / (words + 1)
return letters, words + 1, average
except TypeError:
return "No words"
print(averageWordLength("Hi"))
print(averageWordLength("Hi, Lucy"))
print(averageWordLength(" What big spaces you have!"))
print(averageWordLength(True))
print(averageWordLength("?!?!?! ... !"))
print(averageWordLength("One space. Two spaces. Three spaces. Nine spaces. "))
Desired output:
2, 1, 2.0
6, 2, 3.0
20, 6, 4.0
Not a string
No words
38, 8, 4.75
What in blazes am I doing wrong?!
٩๏̯͡๏۶
Final correction:
for i in myString:
if i not in punctuation:
letters += 1
if char in punctuation:
words += 1
char = i
else:
average = letters / (words + 1)
return letters, words + 1, average
You're adding 1 to words by default... this is not valid in all cases: "Hi!" being a good example. This is actually what is putting off all of your strings: Anytime a string does not end in a word your function will be off.
Hint: You only want to add one if there is no punctuation after the last word.
A problem happens when the string begins with a punctuation character: the previous character is still "" and not considered as a punctuation character, so an non-existent word in counted.
you could add "" in the list of symbols, or do :
punctuation = " !?.,"
because testing c in s return true if c is a substring of s, aka if c is a character of s. And the empty string is contained in every string.
A second problem occurs at the end, if the string terminate with a word, it is not counted (were your word+1 a way to fix that ?), but if the string terminate with a punctuation, the last word is counted.
Add this just after the for loop :
if char not in punctuation:
words += 1
And now there will be no need to add 1, just use
average = letters / words
This is made more difficult since I assume you're not allowed to use inbuilt string functions like split().
The way I would approach this is to:
Split the sentence into a list of words.
Count the letters in each word.
Take the average amount of letters.
def averageWordLength(myString):
punctuation = [" ", "!", "?", ".", ","]
if not isinstance(myString, str):
return "Not a string"
split_values = []
word = ''
for char in myString:
if char in punctuation:
if word:
split_values.append(word)
word = ''
else:
word += char
if word:
split_values.append(word)
letter_count = []
for word in split_values:
letter_count.append(len(word))
if len(letter_count):
return sum(letter_count)/len(letter_count)
else:
return "No words."

IndexError: string index out of range. Pig Latin

Sorry if I'm being really ignorant, I've started learning to code Python recently (first language) and have been working on this task on codewars.com to create a single word pig latin programme. It is pretty messy, but it seems to work aside from the fact that the message:
Traceback:
in
in pig_latin
IndexError: string index out of range
...comes up. I have looked online and I sort of gather it is likely some piece of code that is just out of line or i need a -1 somewhere or something. I was wondering if anyone could help me identify where this would be. It's not helped of course by the fact that I have made this difficult for myself with my inefficiency :P thanks
def pig_latin(s):
word = 'ay'
word2 = 'way'
total=0
total2=0
lst = []
val = None
#rejecting non character strings
for c in s:
if c.isalpha() == False:
return None
#code for no vowels and also code for all consonant strings
for char in s:
if char in 'aeiou':
total+=1
if total==0:
return s + 'ay'
else:
pass
elif char not in 'aeiou':
total2+=1
if total2 == len(s):
answer_for_cons = s + word
return answer_for_cons.lower()
#first character is a vowel
if s[0] in 'aeiou':
return s + word2
#normal rule
elif s[0] not in 'aeiou':
for c in s:
if c in 'aeiou':
lst.append(s.index(c))
lst.sort()
answer = s[lst[0]:len(s)] + str(s[:lst[0]]) + word
return answer.lower()
The only point where an index is implicated is when you call s[0]. Have you maybe tried running pig_latin with an empty string?
Also, the formatting of your code makes no sense. I am assuming it was lost in the pasting? Everything below val = None should be at least one indent further right.
Now that the indentation is fixed, the code seems to run, but it does raise
IndexError: string index out of range
if we pass pig_latin an empty string. That's because of
if s[0] in 'aeiou':
That will fail if s is the empty string because you can't do s[0] on an empty string. s[0] refers to the first char in the string, but an empty string doesn't have a first char. And of course pig_latin returns None if we pass it a string that contains non-alpha characters.
So before you start doing the other tests, you should check that the string isn't empty, and return something appropriate if it is empty. The simplest way to do that is
if not s:
return ''
I suggest returning s or the empty string if you get passed an invalid string, rather than returning None. A function that returns different types depending on the value of the input is a bit messy to work with.
There are various simplifications and improvements that can be made to your code. For example, there's no need to do elif char not in 'aeiou' after you've already done if char in 'aeiou', since if char in 'aeiou' is false then char not in 'aeiou' must be true. However, we can simply that whole section considerably.
Here's your code with a few other improvements. Rather than using index to find the location of the first vowel we can use enumerate to get both the letter and its index at the same time.
def pig_latin(s):
word = 'ay'
word2 = 'way'
#return empty and strings that contain non-alpha chars unchanged
if not s or not s.isalpha():
return s
#code for no vowels
total = 0
for char in s:
if char in 'aeiou':
total += 1
if total == 0:
return s.lower() + word
#first character is a vowel
if s[0] in 'aeiou':
return s.lower() + word2
#normal rule. This will always return before the end of the loop
# because by this point `s` is guaranteed to contain at least one vowel
for i, char in enumerate(s):
if char in 'aeiou':
answer = s[i:] + s[:i] + word
return answer.lower()
# test
data = 'this is a pig latin test string aeiou bcdf 123'
s = ' '.join([pig_latin(w) for w in data.split()])
print(s)
output
isthay isway away igpay atinlay esttay ingstray aeiouway bcdfay 123

Return Boolean depending on whether or not string is in alphabetical order

This is what I have so far in python:
def Alphaword():
alphabet = "abcdefghijklmnopqrstuvwxyz"
x = alphabet.split()
i = 0
word = input("Enter a word: ").split()
I'm planning on using a for loop for this problem but not sure how to start it.
Think about it this way - a word containing letters (if they're in alphabetical order) should be equal to itself when forced to be in alphabetical order, so:
def alpha_word():
word = list(input('Enter a word: '))
return word == sorted(word)
That's the naive approach anyway... if you had massive iterables of sequences, there are other techniques, but for strings typed via input, it's practical enough.
There are two ways:
You just loop over each char in the string, use a test like if a[i]<a[i+1]. This works because 'a' < 'b' is true.
You can split string to char list, sort it and compare it to the original list.
Python supports direct comparison of characters. For instance, 'a' < 'b' < 'c' and so on. Use a for loop to go through each letter in the word and compare it to the previous letter:
def is_alphabetical(word):
lowest = word[0]
for letter in word:
if letter >= lowest:
lowest = letter
else:
return False
return True

Categories