A simple substitution cipher may be created by shifting or rotating the alphabet by a certain number of places. Using this system with a rotation of 5 gives us the following alphabets:
Plaintext alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Ciphertext alphabet: FGHIJKLMNOPQRSTUVWXYZABCDE
Any plain text message can be translated into the ciphertext by replacing each letter of the plain text alphabet with the letter in the ciphertext alphabet in the equivalent position. Spaces are left unchanged. For example, using the cipher above, the word DOG is enciphered as OBK.
Given a String and a rotation value, return the String translated into the ciphertext using the simple substitution method described above. You may assume that the text contains only spaces or capital letters and that the rotation value is always non-negative
function name: rotate_text
arguments:
text - input text to be encoded
n - an integer value specifying how many characters to rotate the text by
returns: a string containing the text rotated as described above
Testing
I am able to pass the test, but the result said I am u therenable to pass hidden or more test, Could someone help me?
def rotate_text(string1, int1):
loL = ['A','B','C','D','E','F','G','H','I','J','K',
'L','M','N','O','P','Q','R','S','T','U','V',
'W','X','Y','Z']
strtoList = list(string1)
list1 = []
newStr = ""
if int1 == len(loL):
int1 = int1 % 26
for i in range(len(strtoList)):
if strtoList[i] in loL:
loLindex = loL.index(strtoList[i])
list1 += [loL[loLindex + int1]]
elif strtoList[i] == " ":
list1 += [" "]
for i in range(len(list1)):
newStr += list1[i]
return newStr
You need:
list1 += [loL[(loLindex + int1) % len(loL)]]
for whenever the cypher "loops back to the first letters".
And then
if int1 == len(loL):
int1 = int1 % 26
becomes irrelevant as well.
And BTW, you don't need to build a list and then make it a string. You can grow your string directly too...
Related
I'm making a text converter, and I'd like to select random characters in a string that already exists.
When I research it, all that comes up is someone that wants to generate random letters in the alphabet or someone that wants to generate a random string. That's not what I'm looking for.
new_string = ""
index = 0
for letter in input_text:
if letter not in "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM":
new_string = new_string + (letter)
continue
index += 1
if index % 2 == 0:
new_string = new_string + (letter.upper())
else:
new_string = new_string + (letter.lower())
My existing text converter capitalizes every other letter, but I'd like to have it randomly capitalize the letters. Is this possible?
You may want to look at the random.choice and random.choices functions in the random library (built-in), which allows you to randomly select an item from a list:
>>> import random
>>> a = random.choice("ABCDabcd")
'C'
>>> my_text = "".join(random.choices("ABCDabcd", k=10))
'baDdbAdDDb'
In order to randomly capitalize, you can choice from a list of the lower- and upper-case version of a letter:
import random
new_string = ""
for letter in input_text:
if letter not in "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM":
new_string = new_string + (letter)
else:
new_string += random.choice([letter.upper(), letter.lower()])
(Note that random.choices returns a list, not a str, so we need to join() the elements together.)
Finally, you may also want to use the isalpha function:
>>> "A".isalpha()
True
>>> "a".isalpha()
True
>>> "7".isalpha()
False
(Relevant question)
But upper() and lower() functions have no effect on non-alpha characters. So you can completely remove this check from your code:
new_string = ""
for letter in input_text:
new_string += random.choice([letter.upper(), letter.lower()])
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
It's actually a string but I just converted it to a list because the answer is supposed to be returned as a list. I've been looking at this problem for hours now and cannot get it. I'm supposed to take a string, like "Mary had a little lamb" for example and another string such as "ab" for example and search through string1 seeing if any of the letters from string2 occur. So if done correctly with the two example it would return
["a=4","b=1"]
I have this so far:
def problem3(myString, charString):
myList = list(myString)
charList = list(charString)
count = 0
newList = []
newString = ""
for i in range(0,len(myList)):
for j in range(0,len(charList)):
if charList[j] == myList[i]:
count = count + 1
newString = charList[j] + "=" + str(count)
newList.append(newString)
return newList
Which returns [a=5] I know it's something with the newList.append(string) and where it should be placed, anyone have any suggestions?
You can do this very easily with list comprehensions and the count function that strings (and lists!) have:
Split the search string into a list of chars.
For each character in the search string, loop over the input string and determine how much it occurs (via count).
Example:
string = 'Mary had a little lamb'
search_string = 'ab'
search_string_chars = [char for char in search_string]
result = []
for char in search_string_chars:
result.append('%s=%d' % (char, string.count(char)))
Result:
['a=4', 'b=1']
Note that you don't need to split the search_string ('ab') into a list of characters, as strings are already lists of characters - the above was done that way to illustrate the concept. Hence, a reduced version of the above could be (which also yields the same result):
string = 'Mary had a little lamb'
search_string = 'ab'
result = []
for char in search_string:
result.append('%s=%d' % (char, string.count(char)))
Here's a possible solution using Counter as mentioned by coder,
from collections import Counter
s = "Mary had a little lambzzz"
cntr = Counter(s)
test_str = "abxyzzz"
results = []
for letter in test_str:
if letter in s:
occurrances = letter + "=" + str(cntr.get(letter))
else:
occurrances = letter + "=" + "0"
if occurrances not in results:
results.append(occurrances)
print(results)
output
['a=4', 'b=1', 'x=0', 'y=1', 'z=3']
import collections
def count_chars(s, chars):
counter = collections.Counter(s)
return ['{}={}'.format(char, counter[char]) for char in set(chars)]
That's all. Let Counter do the work of actually counting the characters in the string. Then create a list comprehension of format strings using the characters in chars. (chars should be a set and not a list so that if there are duplicate characters in chars, the output will only show one.)
#get string and shift from user
string = input('Please enter a string to be ciphered: ')
shift = input('Please enter a shift amount between 0 and 25: ')
#strings are immutable so it must be converted to a list
s=list(string)
#now this will convert each character based on the shift
for i in range(0,len(s)):
s[i]=chr(ord(s[i]) + int(shift))
print ("".join(s))
You should call the method str.alpha to ensure that the chosen element is an alphabet before shifting
for i in range(0,len(s)):
if elem.isaplha():
s[i]=chr(ord(s[i]) + int(shift))
On a second though, you are doing to much work here. Why not use a comprehension expression?
s = ''.join(chr(ord(elem) + shift) if elem.isalpha() else elem for elem in s)
or if you are adventurous enough
s = ''.join([elem, chr(ord(elem) + shift)][elem.isalpha()] for elem in s)
and finally have you checked the string.makestrans along with str.translate to do the conversion?
from string import maketrans, ascii_alpha
s = s.translate(maketrans(ascii_alpha[shift:] + string.ascii_alpha[:shift])
All you have to do is check if the current character is not one you want to skip.
for i in range(0,len(s)):
#If not a space, cipher this character.
if s[i] != ' ':
s[i]=chr(ord(s[i]) + int(shift))
There is however, a possibility that one of your characters will be ciphered to a space, in which case that character would be skipped when reversing the cipher.
Also, a simple cipher like this should not be considered secure in the least.
This is my assignment:
Write a program which DECRYPTS secret messages.
It should first prompt the user for the scrambled alphabet. It then should ask for the secret message. Finally, it outputs the unscrambled version.
Note that there are exactly 26 characters input for the scrambled alphabet. All alphabetic characters are translated into their decoded equivalents (which will take a WHILE loop), and all other, non-alphabetic characters should be output exactly as they were without translation.
This is my code so far:
decrypt = ["*"] * 26
scram_alphabet = input("Please input the scrambled alphabet in order: ")
while len(scram_alphabet) != 26:
scram_alphabet = input("Please input the scrambled alphabet in order. The alphabet must have 26 characters: ")
num = 0
for each_letter in scram_alphabet:
decrypt[num] = ord(each_letter)
num = num + 1
print()
print("Your scrambled alphabet is: ", end = "")
for num in range (26):
print(chr(decrypt[num]), end = "")
print()
print()
msg = input("Now input your scrambled message: ")
print()
print()
alphabet = 65
for s in range (26):
decrypt[s] = (alphabet)
alphabet = alphabet + 1
print("The unscrambled alphabet is: ", end = "")
for num in range (26):
print(chr(decrypt[num]), end = "")
print()
print()
print("Your unscrambled message reads: ")
for alpha in msg.upper():
if alpha < "A" or alpha > "Z":
print(alpha, end="")
else:
ord_alpha = ord(alpha)
print (chr(decrypt[ord_alpha - 65]), end = "")
Ex: Scrambled Alphabet = XQHAJDENKLTCBZGUYFWVMIPSOR , Scrambled Message = VNKW KW BO 1WV WJHFJV BJWWXEJ!
Everything works fine until I get to the last print statement, where it says that the unscrambled message is the same as the scrambled message. I know that the instructions call for a while loop but I couldn't figure out how to use one to decode the alphabet.
Any helpers?
Well, as has already been noted, you are clobbering your decrypt variable.
However, you are also not building the mapping from the 'scrambled' alphabet to the regular one properly / at all.
Decrypting without using anything more than basic iteration over lists and simple list functions (index()) might look something like this:
cleartext = ""
for c in msg:
if c in alphabet:
pos = alphabet.index(c)
cleartext += string.ascii_uppercase[pos]
else:
cleartext += c
Rather than just patching your code, it could benefit from some improvement. I gather this is probably homework and you are probably not expected to go this far, but IMO there is nothing wrong with learning it anyway.
You are not checking that the input alphabet only contains whatever you consider to be legal values (e.g. probably A-Z in this case), nor are you checking for duplicates. The user could input any old rubbish and break your program otherwise.
Your printing and looping is not very idiomatic.
Functions are good for breaking up your code into more easily readable and maintainable pieces.
This may come across as old school or pedantic, but lines longer than 80 characters are not recommended style for Python. Adjacent string literals (e.g "one""two") will be joined (even across newlines).
If I had to do what you're doing without translate (see below), I might do something like this (just a quick example, could probably be improved with a bit of work):
import string
def validate(alpha):
# Is it exactly 26 characters long?
if len(alpha) != 26: return False
for c in alpha:
# Is every character in [A-Z]?
if c not in string.ascii_uppercase: return False
# Is this character duplicated?
if alpha.count(c) > 1: return False
return True
alphabet = ""
while not validate(alphabet):
alphabet = input("Please input the encryption alphabet in order (only A-Z"
" allowed, with no duplicates): ")
msg = input("Now input your encrypted message: ")
print("Your encrypted alphabet is:", alphabet)
print("Your encrypted message is:", msg)
# Create a mapping from one alphabet to the other using a dictionary
table = dict(zip(alphabet, string.ascii_uppercase))
cleartext = "".join([table[c] if c in table else c for c in msg])
print("Your decrypted message reads:", cleartext)
Lastly, you could also do this using Python's builtin string translation, like so:
import string
# read and validate alphabet
# read message
print(str.translate(message, str.maketrans(alphabet, string.ascii_uppercase)))
You are clobbering decrypt after you wrote all the scrambled letters into it:
crypt = ["*"] * 26
for s in range (26):
val = decrypt[s] - 65
crypt[val] = (s + 65)