Python - Conditionals based around character location within a string - python

I have a nested dictionary that I'm using to pull values. I'm using letters as keys with values dependent on the letter location within the string.
I'm trying to code it so that as the letter is found within the string, it pulls the value that is accurate for its position within the string. I have 3 locations - "starter" and "finisher" are the first and last 4 characters respectively. Anything else is "mid".
I've tried getting the character's position within the string to be taken as an integer that I can easily input into a conditional sequence.
def Calc(letterstring, data_table):
Places= len(string)
Score = 0.0
for i in range(Places):
letter= string[i]
if [i] >= int(3) and [i] <= ((Places)-4):
position_in_string = "Starter"
elseif [i] >= ((Places)-4):
position_in_string = "Finisher"
else:
position_in_string = "Mid"
position = (position_in_string)
Score += data_table[letter][position]
return Score
string = input("Insert your line here: ") # Something like ABCDEFGHIJKLMNOP
total_score= (Calc((string), (data_table)))
print (total_score)
And I would expect an output of an integer.
But if I try to do it this way, I end up with:
TypeError: unorderable types: list() >= int()
Any advice or observations would be welcome!

Some changes I'd recommend:
Python has an enumerate builtin that will take a sequence (including a string) and return both the index and the value for each element in the sequence
Use letterstring consistently in your function, and in general, using "string" as a variable name is frowned upon
I'm assuming data_table is a dict of dictionaries, such that the outer dictionary has keys = to 'A-Z' (note the .upper() to ensure that 'a' gets converted to 'A' etc. You should probably also add some error checking in case a user puts in ABC1), and the inner dictionary has the 3 strings you set (Starter, Finisher, and Mid)
no idea why this is getting dorked up formatting
def Calc(letterstring, data_table):
Score = 0.0
ls = len(letterstring)
for i,letter in enumerate(letterstring):
if i < 4:
position_in_string = "Starter"
elif i >= (ls-4):
position_in_string = "Finisher"
else:
position_in_string = "Mid"
Score += data_table[letter][position_in_string]
return Score
instring = input("Insert your line here: ") # Something like ABCDEFGHIJKLMNOP
total_score= Calc(instring.upper(), data_table)
print (total_score)

Related

SNHU Python 'Simon Says': compare two strings for equality character-wise, without using an index?

I was given a prompt to solve and was able to write code that passed, but my question is, is there a more simplified way I could write this without having to create a new named variable (s_index = 0)? The code works just fine but I'm not sure if I solved it the way I was expected to and am open to suggestions for improvement :)
Please note that this section in the work book has us focusing on using continue and break within loops
"Simon Says" is a memory game where "Simon" outputs a sequence of 10 characters (R, G, B, Y)
and the user must repeat the sequence. Create a for loop that compares the two strings.
For each match, add one point to user_score. Upon a mismatch, end the game.
Sample output with inputs: 'RRGBRYYBGY' 'RRGBBRYBGY'
User score: 4
user_score = 0
simon_pattern = input()
user_pattern = input()
s_index = 0
for letter in user_pattern:
if letter == simon_pattern[s_index]:
user_score += 1
s_index += 1
else:
break
print('User score:', user_score)
using functions to encapsulate small specific parts of your logic is often helpful
def do_score(user_pattern="1234",simon_pattern="1235"):
# using enumerate you can get the indices
for s_index,a_char in enumerate(simon_pattern):
if s_index >= len(user_pattern) or a_char != user_pattern[s_index]:
# the index should always match the "sum" so no need to track or compute the sum
return s_index
return len(simon_pattern)
this method takes 2 strings and "scores" them based on the "simon_pattern" returning the score
then just
print(do_score(user_entered_input,simon_pattern))
I will rewrite this to this way: (this way you can completely eliminate the variable index, and simon_pattern[index] to get the letter)
Note - in Python a word is just a sequence of character/letters, you can iterate it directly, no need to use index.
simon = 'RRGBRYYBGY'
user = 'RRGBBRYBGY' # User score: 4
user_score = 0
for user_char, simon_char in zip(user, simon):
if user_char == simon_char: # continue to check/and match...
user_score += 1
else: # break, if no match!
break
print('User score:', user_score)
Strictly, you never need to know the index or index into the strings, you can just use zip() to combine tuples of respective characters from the two (possibly different-length) strings:
def do_score(user_pattern='RRGBRYYBGY', simon_pattern='RRGBBRYBGY'):
score = 0
for uc,sc in zip(user_pattern, simon_pattern):
if uc == sc:
score += 1
else:
break
return score
assert do_score('', '') == 0
assert do_score('RRG', '') == 0
assert do_score('', 'RRG') == 0
assert do_score('RRG', 'RRGB') == 3

Checks for a Path in a List of Strings Given a Starting and Ending Point

We are tasked to create a program that will check if there is a possible way of going from a starting string to an end string, given a list of strings with the same length. There is a catch, we can only go from the current string to the adjacent string if both strings only have one character that is different. If there is no possible path from the starting string to the end string, just print not possible but if there is, output the number of steps from the start to end.
Example:
li = ["booster", "rooster", "roaster", "coaster", "coasted"]
start = "roaster"
end = "booster"
Output: 3
li = ["booster", "rooster", "roaster", "coastal", "coasted"]
start = "roaster"
end = "coasted"
Output: none
I made an approach which manually checks if adjacent strings only have 1 character differences and returns the result based on these differences. Kinda slow if you ask me, given that the length of the list could be at most 100. Can you demonstrate a faster approach?
def checker(str1, str2, change = 0):
for index, character in enumerate(str1): # Traverses the whole
if character != str2[index]: # string and checks for
change+=1 # character differences
return True if change == 1 else False # Only 1 character is different
li = ["booster", "rooster", "roaster", "coaster", "coasted"]
m = len(li)
for j in range(m): li.append(input())
start, end = input().split()
if end in li: endI = li.index(end) # Gets end string index in the list
else:
print("not possible")
break
if start in li: startI = li.index(start) # Gets start string index in the list
else:
print("not possible")
break
if startI < endI: # If start string comes first before
# the end string, keep incrementing.
while li[startI] != end and startI < m-1:
if not checker(li[startI], li[startI+1]):
print("not possible")
break
startI += 1
print(abs(startI-(li.index(start)+1)))
else: # Otherwise, keep decrementing.
while li[startI] != end and startI > 0:
if not checker(li[startI], li[startI-1]):
print("not possible")
break
startI -= 1
print(abs(startI-(li.index(start)+1)))
If my approach is the fastest (which I highly doubt), I want to know if there are loopholes in my approach. Assume that the start and end strings can also be absent in the list given. Just print not possible if they are not in the list.
I hope that looks better:
import regex
def word_path(words, start, end):
if end in words and start in words:
endI = words.index(end)
startI = words.index(start)
else:
return "not possible"
step = 1 if startI <= endI else -1
for index in range(startI, endI, step):
if not regex.match("(%s){e<=1}" %li[index], li[index + step]):
return "not possible"
return abs(startI - endI) + 1
li = ["booster", "rooster", "roaster", "coastal", "coasted"]
start, end = input().split()
print(word_path(li, start, end))
It is supposed to be regex. Regex provides additional regular expression features, e.g. it makes it possible to check for a number of errors {e<=1}. The re- module doesn't provide these features. I guess that is the reason for the wrong result. You may need to install the regex-module with pip install regex first, but then it should work:
regex_module

even and uneven letter, TypeError: not all arguments converted during string formatting

def ul(a):
if a %2 == 0:
print(a.upper())
else:
print(a.lower())
ul(input())
I get following error:
TypeError: not all arguments converted during string formatting
I want to write every even letter in uppercase and every uneven letter in lowercase
What I'm doing wrong?
What you are now doing with this function is check whether the input (a string) is divisible by 2. This is not really what you wanted to do and it raises an error because strings are not modulo divisible.
You should better loop through the indices of the input string to fill a second string with upper and lower case letters:
def ul(s):
outstring = ''
for i in range(len(s)): ## For each index of the string
if i%2==0:
outstring += s[i].upper() ## Even indices become upper case
else:
outstring += s[i].lower() ## Odd indices become lower case
print(outstring)
ul(input("Enter a string: "))
You are trying to get mode of string as I understand. You should use len(a).
def ul(a):
if len(a) %2 == 0:
print(a.upper())
else:
print(a.lower())
ul(input())
Try this
def ul(a):
a = a.lower()
if ord(a) %2 == 0:
print(a.upper())
else:
print(a.lower())
if you want user to enter only Alphabets, then try this
def ul(a):
if ord(a) >= 110 and ord(a) < 123:
a = a.lower()
if ord(a) %2 == 0:
print(a.upper())
else:
print(a.lower())
else:
print("Please enter Alphabets only")
Input will give you a string. You want to use the modulo operator %, which is exactly how you usually find the evenness of a number. So, you are on the right track!
What actually happens is that Python interprets your % as the format operator (probably has a fancy name).
number = 2
print("Number %d is this" % number)
>>> Number 2 is this
Strings in Python are immutable, so you can't just change the string that easily.
Try this Replacing every 2nd character in a string , where you construct a new string by adding all the individual characters together.
def ul(word):
new = []
for i in range(len(word)):
if i%2 == 0:
new += word[i].upper()
else:
new += word[i].lower()
print("".join(new))
This will go through the string character by character, transform the individual character based on if it is even or not, and then construct a list "new", that holds all the individual characters. In the end, just join it, so create a new string out of the list.
There are more pythonic ways to do this using list comprehension, but for now this is easier to read and understand.

How do I go about ending this loop?

I am trying to count the longest length of string in alphabetical order
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
else:
if current > longest:
longest = current
current = 0
i += 1
print longest
For this specific string, 'Current' ends up at the correct length, 4, but never modifies longest.
EDIT: The following code now runs into an error
s = 'abcv'
current = 1
biggest = 0
for i in range(len(s) - 1):
while s[i] <= s[i+1]:
current += 1
i += 1
if current > biggest:
biggest = current
current = 0
print biggest
It seems my logic is correct , but I run into errors for certain strings. :(
Although code sources are available on the internet which print the longest string, I can't seem to find how to print the longest length.
break will jump behind the loop (to sam indentation as the for statement. continue will jump to start of loop and do the next iteration
Your logic in the else: statement does not work - you need to indent it one less.
if s[i] <= s[i+1]:
checks for "is actual char less or equal then next char" - if this is the case you need to increment your internal counter and set longest if it is longer
You might get into trouble with if s[i] <= s[i+1]: - you are doing it till len(s)-1. "jfjfjf" is len("jfjfjf") = 6 - you would iterate from 0 to 5 - but the if accesses s[5] and s[6] which is more then there are items.
A different approach without going over explicit indexes and split into two responsibilities (get list of alphabetical substring, order them longest first):
# split string into list of substrings that internally are alphabetically ordered (<=)
def getAlphabeticalSplits(s):
result = []
temp = ""
for c in s: # just use all characters in s
# if temp is empty or the last char in it is less/euqal to current char
if temp == "" or temp[-1] <= c:
temp += c # append it to the temp substring
else:
result.append(temp) # else add it to the list of substrings
temp = "" # and clear tem
# done with all chars, return list of substrings
return result
# return the splitted list as copy after sorting reverse by length
def SortAlphSplits(sp, rev = True):
return sorted(sp, key=lambda x: len(x), reverse=rev)
splitter = getAlphabeticalSplits("akdsfabcdemfjklmnopqrjdhsgt")
print(splitter)
sortedSplitter = SortAlphSplits(splitter)
print (sortedSplitter)
print(len(sortedSplitter[0]))
Output:
['ak', 's', 'abcdem', 'jklmnopqr', 'dhs']
['jklmnopqr', 'abcdem', 'dhs', 'ak', 's']
9
This one returns the array of splits + sorts them by length descending. In a critical environment this costs more memory then yours as you only cache some numbers whereas the other approach fills lists and copies it into a sorted one.
To solve your codes index problem change your logic slightly:
Start at the second character and test if the one before is less that this. That way you will ever check this char with the one before
s = 'abcvabcdefga'
current = 0
biggest = 0
for i in range(1,len(s)): # compares the index[1] with [0] , 2 with 1 etc
if s[i] >= s[i-1]: # this char is bigger/equal last char
current += 1
biggest = max(current,biggest)
else:
current = 1
print biggest
You have to edit out the else statement. Because consider the case where the current just exceeds longest, i.e, from current = 3 and longest =3 , current becomes 4 by incrementing itself. Now here , you still want it to go inside the if current > longest statement
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
#else:
if current > longest:
longest = current
current = 0
i += 1
longest = current
print longest
Use a while condition loop, then you can easy define, at what condition your loop is done.
If you want QualityCode for longterm:
While loop is better practice than a break, because you see the Looping condition at one place. The simple break is often worse to recognize inbetween the loopbody.
At the end of the loop, current is the length of the last substring in ascending order. Assigning it to longest is not right as the last substring in ascending is not necessarily the longest.
So longest=max(current,longest) instead of longest=current after the loop, should solve it for you.
Edit: ^ was for before the edit. You just need to add longest=max(current,longest) after the for loop, for the same reason (the last ascending substring is not considered). Something like this:
s = 'abcv'
longest = 1
current = 1
for i in range (len(s) - 1):
if s[i] <= s[i+1]:
current += 1
else:
if current > longest:
longest = current
current = 0
i += 1
longest=max(current,longest) #extra
print longest
The loop ends when there is no code after the tab space so technically your loop has already ended

Python Vigenere working, but I can't account for the spaces and non alphabetical characters using functions

I am currently working on a cipher program for a beginners python course. We first were told to create a function that would return the position of a given letter, using a string of the alphabet as a reference (that is my alphabet_position function.) Next, we were told to make a function that would allow for a single letter to be rotated by a chosen number(that is my rotate_character function). Third, we were tasked with creating a basic caesar cipher using the previous two functions. All of those I was able to make work as demonstrated by my code below.
The vigenere, however, is proving much more difficult. I was actually able to find a snippet of code that I was able to modify with my first function (alphabet_position) to make work if only alphabetic characters are used, but as soon as i enter any non alphabetical character (such as ! or ?) I get a return of ValueError: Substring Not found. When the program encounters these non alphabetical characters, the key is supposed to skip over them and carry the Nth character of the key to the next alphabetical character.
I am guessing the answer lies in somehow incorporating my rotate_character function into my Encrypt function, but I am unsure how to do this, as the rotate_character function expects a alphabetical character, and the vigenere function turns that parameter into an int before running it through.
Any advice? And as I am a new programmer, I will gladly take any other helpful criticism on my coding practices you may want to instill!`
> #Create function alphabet_position(letter) to turn letter into number
> #such as a=0 or e=4, using lowercase to make sure case doesnt matter.
alphabet = "abcdefghijklmnopqrstuvwxyz"
def alphabet_position(letter):
> lower_letter = letter.lower() #Makes any input lowercase.
> return alphabet.index(lower_letter) #Returns the position of input
as a number.
>
> def rotate_character(char, rot):
> if char.isalpha():
> a = alphabet_position(char);
> a = (a + rot) % (int(len(alphabet))); #needs modulo
> a = (alphabet[a]);
> if char.isupper():
> a = a.title()
> return a
> else:
> return char
>
> def caesar(text, rot):
> list1 = ""
> for char in text:
> list1 += rotate_character(char, rot)
> return list1
>
> def vigenere(text,key):
m = len(key)
>
> newList = ""
>
> for i in range(len(text)):
text_position = alphabet_position(text[i])
key_position = alphabet_position(key[i % m])
value = (text_position + key_position) % 26
newList += alphabet[value]
return newList
>
> def main():
> x = input("Type a message: ")
> y = input("Rotate by: ")
> result = vigenere(x, y)
> print (result)
>
> if __name__ == '__main__':
main()
No, you don't need the rotate function anymore. You just need to directly add any character that is not in the alphabet to newlist and then skip the encryption part.
Now a sub-optimal way of doing this is to use if ... in ...:
if text[i] in alphabet:
# do your thing
else:
newList += text[i]
Of course more optimal is to only go through the alphabet once and use a variable:
pt_c = text[i]
pt_i = alphabet.find(pt_c) # returns -1 instead of an error when not found
if pt_i == -1:
newList += pt_c
else:
newList += pt_c
# do your thing *with the given index*
This won't make any difference in the runtime for a Vigenère cipher of course. But it shows you how to think of efficient programming for later: there is no need to search twice.
You could also continue the loop instead of having an else statement:
pt_c = text[i]
pt_i = alphabet.find(pt_c) # returns -1 instead of an error when not found
if pt_i == -1:
continue
# do your thing with the given index
this will make the indentation depth of your loop (the amount of scopes) less, with the unfortunate side effect of making your loop more complex (creating a local exit point).

Categories