multilevel caesar cipher - python

Hey, I'm trying to decode a multilevel Caesar cipher. By that I mean a string of letters could have been shifted several times, so if I say apply_shifts[(2,3),(4,5)], that means I shift everything from the 2nd letter by 3 followed by everything from the 4th letter by 5. Here's my code so far.
def find_best_shifts_rec(wordlist, text, start):
"""
Given a scrambled string and a starting position from which
to decode, returns a shift key that will decode the text to
words in wordlist, or None if there is no such key.
Hint: You will find this function much easier to implement
if you use recursion.
wordlist: list of words
text: scambled text to try to find the words for
start: where to start looking at shifts
returns: list of tuples. each tuple is (position in text, amount of shift)
"""
for shift in range(27):
text=apply_shifts(text, [(start,-shift)])
#first word is text.split()[0]
#test if first word is valid. if not, go to next shift
if is_word(wordlist,text.split()[0])==False:
continue
#enter the while loop if word is valid, otherwise never enter and go to the next shift
i=0
next_index=0
shifts={}
while is_word(wordlist,text.split()[i])==True:
next_index+= len(text.split()[i])
i=i+1
#once a word isn't valid, then try again, starting from the new index.
if is_word(wordlist,text.split()[i])==False:
shifts[next_index]=i
find_best_shifts_rec(wordlist, text, next_index)
return shifts
My problems are
1) my code isn't running properly and I don't understand why it is messing up (it's not entering my while loop)
and
2) I don't know how to test whether none of my "final shifts" (e.g. the last part of my string) are valid words and I also don't know how to go from there to the very beginning of my loop again.
Help would be much appreciated.

I think the problem is that you always work on the whole text, but apply the (new) shifting at some start inside of the text. So your check is_word(wordlist,text.split()[0]) will always check the first word, which is - of course - a word after your first shift.
What you need to do instead is to get the first word after your new starting point, so check the actually unhandled parts of the text.
edit
Another problem I noticed is the way you are trying out to find the correct shift:
for shift in range(27):
text=apply_shifts(text, [(start,-shift)])
So you basically want to try all shifts from 0 to 26 until the first word is accepted. It is okay to do it like that, but note that after the first tried shifting, the text has changed. As such you are not shifting it by 1, 2, 3, ... but by 1, 3, 6, 10, ... which is of course not what you want, and you will of course miss some shifts while doing some identical ones multiple times.
So you need to temporarily shift your text and check the status of that temporary text, before you continue to work with the text. Or alternatively, you always shift by 1 instead.
edit²
And another problem I noticed is with the way you are trying to use recursion to get your final result. Usually recursion (with a result) works the way that you keep calling the function itself and pass the return values along, or collect the results. In your case, as you want to have multiple values, and not just a single value from somewhere inside, you need to collect each of the shifting results.
But right now, you are throwing away the return values of the recursive calls and just return the last value. So store all the values and make sure you don't lose them.

Pseudo-code for recursive function:
coded_text = text from start-index to end of string
if length of coded_text is 0, return "valid solution (no shifts)"
for shift in possible_shifts:
decoded_text = apply shift of (-shift) to coded_text
first_word = split decoded_text and take first piece
if first_word is a valid word:
rest_of_solution = recurse on (text preceding start-index)+decoded_text, starting at start+(length of first_word)+1
if rest_of_solution is a valid solution
if shift is 0
return rest_of_solution
else
return (start, -shift mod alphabet_size) + rest_of_solution
# no valid solution found
return "not a valid solution"
Note that this is guaranteed to give an answer composed of valid words - not necessarily the original string. One specific example: 'a add hat' can be decoded in place of 'a look at'.

Related

Palindrome vs Symmetry and how to deal with 2 word character

Can we say that a word with 2 characters are palindrome? like "oo" is palindrome and "go" is not?
I am going through a program which is detecting a palindrome from GeeksForGeeks, but it detects go as palindrome as well, though it is not:
# Function to check whether the
# string is plaindrome or not def palindrome(a):
# finding the mid, start
# and last index of the string
mid = (len(a)-1)//2
start = 0
last = len(a)-1
flag = 0
# A loop till the mid of the
# string
while(start<mid):
# comparing letters from right
# from the letters from left
if (a[start]== a[last]):
start += 1
last -= 1
else:
flag = 1
break;
# Checking the flag variable to
# check if the string is palindrome
# or not
if flag == 0:
print("The entered string is palindrome")
else:
print("The entered string is not palindrome")
# ... other code ...
# Driver code
string = 'amaama'
palindrome(string)
Is there any particular length or condition defined for a word to be a palindrome? I read the Wikipedia article, but did not find any particular condition on the length of a palindrome.
The above program detects "go" as palindrome because the midpoint is 0, which is "g" and the starting point is 0, which is also "g", and so it determines it is a palindrome. But I am still confused about the number of characters. Can a 2 number word be a palindrome? If yes, then do we need to just add a specific condition for it: if word[0] == word[1]?
Let's take a look at the definition of palindrome, according to Merriam-Webster:
a word, verse, or sentence (such as "Able was I ere I saw Elba") or a number (such as 1881) that reads the same backward or forward
Therefore, two-character words (or any even-numbered character words) can also be palindromes. The example code is simply poorly written and does not work correctly in the case of two-character strings. As you have correctly deduced, it sets the mid variable to 0 if the length of the string is 2. The loop, while (start < mid), is then instantly skipped, as start is also initialised as 0. Therefore, the flag variable (initialised as 0, corresponding to 'is a palindrome') is never changed, so the function incorrectly prints that go is a palindrome.
There are a number of ways in which you can adapt the algorithm; the simplest of which would be to simply check up to and including the middle character index, by changing the while condition to start <= mid. Note that this is only the simplest way to adapt the given code, the simplest piece of Python code to check whether a string is palindromic is significantly simpler (as you can easily reverse a string using a[::-1], and compare this to the original string).
(Edit to add: the other answer by trincot actually shows that the provided algorithm is incorrect for all even-numbered character words. The fix suggested in this answer still works.)
Your question is justified. The code from GeeksForGeeks you have referenced is not giving the correct result. In fact it also produces wrong results for longer words, like "gang".
The above program detects "go" as palindrome because the midpoint is 0, which is "g" and the starting point is 0, which is also "g", and so it determines it is a palindrome.
This is indeed where the algorithm goes wrong.
...then do we need to just add a specific condition for it: if word[0] == word[1]?
Given the while condition is start<mid, the midpoint should be the first index after the first half of the string that must be verified, and so in the case of a 2-letter word, the midpoint should be 1, not 0.
It is easy to correct the error in the program. Change:
mid = (len(a)-1)//2
To:
mid = len(a)//2
That fixes the issue. No extra line of code is needed to treat this as a separate case.
I did not find any particular condition on the length of a palindrome.
And right you are: there is no such condition. The GeeksForGeeks code made you doubt, but you were right from the start, and the code was wrong.

Palindrome Coding issue

Writing a program:
Input string from the user
print out whether this string is a palindrome or not
Also, I found a few other codes online but want to work with this code only.m Please let me know the error
i = str(input())
for item in i:
print(item)
if int(i[item]) == int(i[-item]):
print('yes')
else:
print('no')
Use a String slice (The i[::-1] will reverse the string):
i = input()
if i == i[::-1]:
print("Yes")
else:
print("No")
This will take the input from the user and compare it against the same input in reverse.
try this:
word="TOT"
i=word[::-1]
if i==word:
print("palandrom")
Although for item in i: loops through every character in the string, there are several problems with your code line if int(i[item]) == int(i[-item]):. First of all, item is going to be a character from your string. So if the user types "hello", then i[item] first looks for i['h']. Since 'h' is a character and not a number, this makes Python think that i is a dictionary and not a string, and thus tells Python to look for a dictionary named i and return the value where the key is h. That won't work since i is your original string, not a dictionary.
It looks like what you meant to do here is compare i[0] (the first character in the string) to i[-1] (the last character in the string), then i[1] to i[-2], and so on. But even you if looped through the position numbers, i[-item] doesn't mathematically give you what you want.
Yet another issue here is that you're checking each character one at a time and returning "yes" or "no". What you ultimately want though is to output one simple answer: whether your string is a palindrome or not.
Also, there's no need to put str() around input(), since input returns a string anyway, even if the user enters only numerals. By the way, even though you're using i as your string variable, the usual convention in programming is to use i to denote some sort of integer, such as one you're iterating through in a for loop. But that's OK for now.
As some of the other answers have shown, i[::-1] is a quick way to return the reverse of a string itself. So if you're OK with seeing the output return True if the string is a palindrome and False if it isn't, then here's an extremely simple way to do it:
i = input()
print(i == i[::-1])
If the string i is identical to itself reversed, then i == i[::-1] returns True. If not, it returns False. The print statement then prints whichever the answer is.
However, if you really do want to do it the long way, testing character by character in a loop, then here's one way to do it. You could make a function that takes in a string and does the work:
def is_palindrome(mystring):
# The "//2" here divides by 2 and ignores the remainder. So if
# there are an even number of letters, we'll test each pair. If
# It's an odd number, then we don't care about the middle character
# anyway. Compare [0] to [-1], then [1] to [-2], [2] to [-3], and so on.
for position in range(0, len(mystring)//2):
# If we've found a mismatched pair of letters, then we can
# stop looking; we know it's not a palindrome.
if mystring[position] != mystring[(-1 * position) - 1]:
print("This is NOT a palindrome")
return # This breaks you out of the entire function.
# If we've gotten this far, then the word must be a palindrome.
print("This is a palindrome")
# Here's where we run the command to input the string, and run the function
mystring = input("Enter your string: ")
is_palindrome(mystring)

Caesar Cipher algorithm with strings and for loop Python

The assignment is to write a Caesar Cipher algorithm that receives 2 parameters, the first being a String parameter, the second telling how far to shift the alphabet. The first part is to set up a method and set up two strings, one normal and one shifted. I have done this. Then I need to make a loop to iterate through the original string to build a new string, by finding the original letters and selecting the appropriate new letter from the shifted string. I've spent at least two hours staring at this one, and talked to my teacher so I know I'm doing some things right. But as for what goes in the while loop, I really don't have a clue. Any hints or pushes in the right direction would be very helpful so I at least have somewhere to start would be great, thank you.
def cipher(x, dist):
alphabet = "abcdefghijklmnopqrstuvwxyz"
shifted = "xyzabcdefghijklmnopqrstuvw"
stringspot = 0
shiftspot = (x.find("a"))
aspot = (x.find("a"))
while stringspot < 26:
aspot = shifted(dist)
shifted =
stringspot = stringspot + 1
ans =
return ans
print(cipher("abcdef", 1))
print(cipher("abcdef", 2))
print(cipher("abcdef", 3))
print(cipher("dogcatpig", 1))
Here are some pushes and hints:
You should validate your inputs. In particular, make sure that the shift distance is "reasonable," where reasonable means something you can handle. I recommend <=25.
If the maximum shift amount is 25, the letter 'a' plus 25 would get 'z'. The letter 'z' plus 25 will go past the end of the alphabet. But it wouldn't go past the end of TWO alphabets. So that's one way to handle wrap-around.
User #zondo, in his solution, handles upper-case letters. You didn't mention if you want to handle them or not. You may want to clarify that with your teacher.
If you know about dictionaries, you might want to build one to make it easy to map the old letters to the new letters.
You need to realize that strings are treated as tuples or lists - you can index them. I don't see you doing that in your code.
You can get an "ASCII code" number for a letter using ord(). The numbers are arbitrary, but both upper and lower case numbers are packed together tightly in ranges of 26. This means you can do math with them. (For example, ord('a') is 97. Not super useful. But ord('b') - ord('a') is 1, which might be good to know.)
alphabet and shifted are supposed to be a mapping between the original stream and the ciphertext. The loop's job is to iterate over all letters in the stream substitute them. More specifically, the letter in alphabet and the substitute letter in shifted reside at the same index, hence the mapping. In pseudocode:
ciphertext = empty
for each letter in x
i = index of letter in alphabet
new_letter = shifted[i]
add new_letter to ciphertext
The whole loop can be simplified to a comprehension list, but this shouldn't be your primary concern.
For more direct mapping than doing as in the pseudocode above, look into dictionaries.
Another thing that stands out in your code is the generation of shifted, which should depend on the argument dist so it can't just be hardcoded. So, if dist is 5, the first letter in shifted should be whatever lies at the 0+5 in alphabet, and so on. Hint: modulo operator.

My Python code is only selecting half of a list's contents?

I'm very new to Python, and I'm going through some example projects I found online but I'm stuck on my palindrome checker at the moment.
Right now, my code takes a word as an input, splits it in half, saves each part into separate variables, makes both of the variables lists, and from there it SHOULD reverse the second list so I can compare it to the first, but from what I've gathered trying to fix it, it's only appending half of the selection to the new list.
For example, if I enter "racecar", it'll split it into "race" and "ecar" just fine, but then when I go to reverse "ecar" it only gives me back "['c', 'e']". (Also, if I switch the variables around to reverse the first half, I get the same error)
I've been trying to figure it out for quite a while now and I'm not making any progress so some help would be very much appreciated!
Ninja Edit: If there's an easier way to do this (which I'm sure there is) I'd love to know, but I still want to figure out what I've done wrong in the code I already have so I can try to learn from it
Here's my code so far:
print "Please enter a word you want to check is a palindrome"
input = raw_input('> ')
#Gets lengths of input
full_length = len(input)
split_length = len(input) / 2
#If word has an even length split like this
if full_length % 2 == 0:
first_half = input[0: split_length]
second_half = input[split_length:full_length]
#If word does not have even length split like this
else:
first_half = input[0:split_length+1]
second_half = input[split_length:full_length]
#Make both halves lists
first_half_list = list(first_half)
print first_half_list
second_half_list = list(second_half)
print second_half_list
# Reverse second half
rev_second_half = []
for x in second_half_list:
current_letter = second_half_list[0]
second_half_list.remove(second_half_list[0])
rev_second_half.insert(0, current_letter)
print rev_second_half
"""
#Check to see if both lists are identical
#If they are identical
print "This word is a palindrome!"
#If they are not identical
print "This word is not a palindrome."
"""
And this is the output I get when I enter 'racecar':
racecar
['r','a','c','e']
['e','c','a','r']
['c', 'e']
There's a lot of unnecessary work going on. No need to convert to lists; the interpreter can manage this all for you. No need to manually reverse a string; use slicing. No need to manually declare the indices of the first and last characters in your string; the interpreter knows where they are. Here's a fixed version of the code; you can view a demo at IDE One:
input = 'racecar'
#Gets lengths of input
full_length = len(input)
split_length = len(input) / 2
#If word has an even length split like this
if full_length % 2 == 0:
first_half = input[:split_length]
second_half = input[split_length:]
#If word does not have even length split like this
else:
first_half = input[:split_length+1]
second_half = input[split_length:]
print first_half
print second_half
rev_second_half = second_half[::-1]
print rev_second_half
race
ecar
race
Notice the way that the second half is getting reversed, by using a slice with a negative iteration step? You can just do that once, to your source string, and compare the result to the original. Now you have a one line method to check if a string is a palindrome: input == input[::-1]
A bit more on slicing syntax (you might like to check out this question). input[::-1] is exactly the same as input[0:len(input):-1]. The colons separate the three arguments, which are start : end : step. The first two create a range which includes start and everything between it and end, but not end itself. Not specifying start or end causes the interpreter to assume you mean "use 0" and "use len", respectively. Not specifying step causes an assumption of 1. Using a negative step means "start at end and go backwards by magnitude of step".
If you want to omit arguments and specify a range with a slice, you need to include the colons, so the interpreter can tell which arguments are omitted. For example, input[-1] will return the last element of input, because no colons means you're specifying an index, and negative means "go backwards from the end", so print input[:-1] would yield "raceca" if your input was "racecar".
As for what was going wrong with your code, the problem is in your reversing loop.
for x in second_half_list:
current_letter = second_half_list[0]
second_half_list.remove(second_half_list[0])
rev_second_half.insert(0, current_letter)
You're removing items from the list you're iterating through. Don't do that, it's a great way to cause problems; it's why you're only getting half the list in this case. There's also needless copying going on, though that won't cause incorrect results. Finally, you're not using your iterated variable at all, which is a sure sign of some sort of problem with your loop code. Here, if you fixed the list mutation but continued using second_half_list[0], you'd get that letter repeated len(second_half_list) times. If you really need to actually reverse a list, you can do it like this instead:
for x in second_half_list:
rev_second_half.insert(0, x)
But you should only actually iterate the list if you need some sort of side effects during the iteration. For a pure reversal in python, you want this, which will perform better:
rev_second_half = [reversed(second_half_list)]
To reverse the string (not in place):
rev_second_half = second_half_list[::-1]
To extend:
I'd suggest keeping the halves as strings, as you can then just compare them with:== and the above reversing technique also works on strings.
The reason you're only getting two values is you're mutating your list while you iterate on it -- you just shouldn't do this, if only because it's a pain to reason about. As an example:
In [34]: nums = range(5) # [0, 1, 2, 3, 4]
In [35]: for num in nums:
....: print "num", num
....: print "nums", nums
....: nums.remove(nums[0])
....:
num 0
nums [0, 1, 2, 3, 4]
num 2
nums [1, 2, 3, 4]
num 4
nums [2, 3, 4]
Notice that this only looped three times. The first time through, everything's dandy, but you remove the first element. However, Python's looping logic thinks it has to go to the second item -- but you removed the first item! Does that mean the second item now, or the second item when things started? For Python's internals, it means the second item now -- which is the third item when things started (i.e. the value 2). From there, stuff just snowballs.
The lesson here is don't mutate a list while you iterate on it. Just use the other means for reversing folks have mentioned here.

Python recursive function seems to lose some variable values

I have 4x4 table of letters and I want to find all possible paths there. They are candidates for being words. I have problems with the variable "used" It is a list that includes all the places where the path has been already so it doesn't go there again. There should be one used-list for every path. But it doesn't work correctly. For example I had a test print that printed the current word and the used-list. Sometimes the word had only one letter, but path had gone through all 16 cells/indices.
The for-loop of size 8 is there for all possible directions. And main-function executes the chase-function 16 times - once for every possible starting point. Move function returns the indice after moving to a specific direction. And is_allowed tests for whether it is allowed to move to a certain division.
sample input: oakaoastsniuttot. (4x4 table, where first 4 letters are first row etc.)
sample output: all the real words that can be found in dictionary of some word
In my case it might output one or two words but not nearly all, because it thinks some cells are used eventhough they are not.
def chase(current_place, used, word):
used.append(current_place) #used === list of indices that have been used
word += letter_list[current_place]
if len(word)>=11:
return 0
for i in range(3,9):
if len(word) == i and word in right_list[i-3]: #right_list === list of all words
print word
break
for i in range(8):
if is_allowed(current_place, i) and (move(current_place, i) not in used):
chase(move(current_place, i), used, word)
The problem is that there's only one used list that gets passed around. You have two options for fixing this in chase():
Make a copy of used and work with that copy.
Before you return from the function, undo the append() that was done at the start.

Categories