How to increment alphabetical characters without getting special characters after z? - python

I have to create a program that gets a string, and an integer n; it will increment each character of the string by n characters; for example, if the string is "abc" and n=1, the output would be "bcd", if n=2, it'd be "cde".
So far I have written this code
string = list( input( "Insert a string, it will be codified: " ) )
n = int( input( "Insert an integer, each string's character will be increased by that number: " ) )
for characterIndex in range( len( string ) ):
string[characterIndex] = chr( ord( string[characterIndex] ) + n )
print( ''.join( string ) )
Nonetheless, if I input "xyz" and n=1, I get "yz{", which makes sense since ascii's next character to "z" is "{". You can imagine that for a higher n, it gets worse; I have been trying to solve this problem for any n using modulo, tried to take advantage from the fact that there are 26 letters, but I'm still unable to find a mathematical increment that detects when the string has been incremented further than "z", so it "gets back" to "a".
Any recommendations? Thanks in advance.

It's kind of cheating, but here's the approach I would take:
def string_bump(s):
letter_list = "abcdefghijklmnopqrstuvwxyza" #note the extra 'a' at the end
old_positions = []; new_positions = []
for character in s:
old_positions.append(letter_list.find(character))
for pos in old_positions:
new_positions.append(pos+1)
new_string = ""
for pos in new_positions:
new_string += letter_list[pos]
return new_string
for s in ["abc", "bcd", "xyz"]:
print("before:", s, "after:", string_bump(s))
prints:
before: abc after: bcd
before: bcd after: cde
before: xyz after: yza
Basically, I scan the string to convert the characters to positions in the alphabet string; add 1 to each position; and rebuild the string from those positions. The "cheat" is adding an extra 'a' so a position-25 (counting from 0) 'z' translates to that extra position-26 'a'.
If that offends you, you could leave off the extra 'a' and instead just take another pass at the list of positions and when you see "26" (which would be past the end of the letter_list without the 'a'), knock it down to zero.
This is just a proof-of-concept for your example; to support an arbitrary shift, you'd extend the letter_list out for the full alphabet, and use modulo on the input (e.g. n = n%26) to ensure the input stayed in range.
Also, I would actually use list expressions in place of the for loops, but you may not have encountered those yet, so I used the more explicit for loops instead above.

Let's break it down so that individual steps with named variables make it clear what you're dealing with:
asciiValue = ord(string[characterIndex])
alphabetIndex = asciiValue - ord('a')
alphabetIndex = (alphabetIndex + n) % 26
asciiValue = alphabetIndex + ord('a')
string[characterIndex] = chr(asciiValue)
Note that the above assumes that your input string is composed only of lowercase ASCII letters. For uppercase characters, you'd need to subtract (and re-add) ord('A') instead.
Integrating it into your existing code:
def shift_letter(letter, n):
asciiValue = ord(letter)
alphabetIndex = asciiValue - ord('a')
alphabetIndex = (alphabetIndex + n) % 26
asciiValue = alphabetIndex + ord('a')
return chr(asciiValue)
string = list( input( "Insert a string, it will be codified: " ) )
n = int( input( "Insert an integer, each string's character will be increased by that number: " ) )
for characterIndex in range( len( string ) ):
string[characterIndex] = shift_letter(string[characterIndex], n)
print( ''.join( string ) )

I thought since we needed to encrypt only the alphabets I created a list of alphabets and looped them as below. This way when you input alphabets only alphabets shifted by the given number comes out even if the shift is large.
print("welcome to encryptor!!")
print("Do you want to encrypt or decrypt")
s = int(input("Press 1 for encrypt and 2 for decrypt: "))
alpha = list("abcdefghijklmnopqrstuvwxyz")
print(alpha)
def encrypt():
string = list(input("Enter your string without space to encrypt: "))
print(string)
string1 = []
n = int(input("Enter the value key for caesar crypt: "))
for characterIndex in range(len(string)):
for i in range(len(alpha)):
if string[characterIndex] == alpha[i]:
if((i + n)+1) >= 26:
string1.append(alpha[(i+n) % 26])
else:
string1.append(alpha[i+n])
else:
continue
print(''.join(string1))
This code does produce the desired ouput..

Here is the modified answer from the comments:
c = chr((ord(a) - 97) % 25 + 97)

Related

String incrementation

I've just started to learn Python and I'm doing some exercises in codewars. The instructions are simple: If the string already ends with a number, the number should be incremented by 1.
If the string does not end with a number. the number 1 should be appended to the new string.
I wrote this:
if strng[-1].isdigit():
return strng.replace(strng[-1],str(int(strng[-1])+1))
else:
return strng + "1"
return(strng)
It works sometimes (for example 'foobar001 - foobar002', 'foobar' - 'foobar1'). But in other cases it adds 1 to each number at the end (for example 'foobar11' - 'foobar22'), I would like to achieve a code where the effect is to add only +1 to the ending number, for example when 'foobar99' then 'foobar100', so the number has to be considered as a whole. I would be grateful for advices for beginner :)!
First, you have to make some assumptions
Assuming that the numerical values are always at the end of string and the first character from the right that is not numeric would mark the end of the non-number string, i.e.
>>> input = "foobar123456"
>>> output = 123456 + 1
Second, we need to assume that number exists at the end of the string.
So if we encounter a string without a number, we need to decide if the python code should throw an error and not try to add 1.
>>> input = "foobar"
Or we decide that we automatically generate a 0 digit, which would require us to do something like
input = input if input[-1].isdigit() else input + "0"
Lets assume the latter decision for simplicity of the explanation.
Next we will try to read the numbers from the right until you get to a non-digit
Lets use reversed() to flip the string and then a for-loop to read the characters until we reach a non-number, i.e.
>>> s = "foobar123456"
>>> output = 123456
>>> for character in reversed(s):
... if not character.isdigit():
... break
... else:
... print(character)
...
6
5
4
3
2
1
Now, lets use a list to keep the digits characters
>>> digits_in_reverse = []
>>> for character in reversed(s):
... if not character.isdigit():
... break
... else:
... digits_in_reverse.append(character)
...
>>> digits_in_reverse
['6', '5', '4', '3', '2', '1']
Then we reverse it:
>>> ''.join(reversed(digits_in_reverse))
'123456'
And convert it into an integer:
>>> int(''.join(reversed(digits_in_reverse)))
123456
Now the +1 increment would be easy!
How do we find the string preceding the number?
# The input string.
s = "foobar123456"
s = s if s[-1].isdigit() else s + "0"
# Keep a list of the digits in reverse.
digits_in_reverse = []
# Iterate through each character from the right.
for character in reversed(s):
# If we meet a character that is not a digit, stop.
if not character.isdigit():
break
# Otherwise, keep collecting the digits.
else:
digits_in_reverse.append(character)
# Reverse, the reversed digits, then convert it into an integer.
number_str = "".join(reversed(digits_in_reverse))
number = int(number_str)
print(number)
# end of string preceeding number.
end = s.rindex(number_str)
print(s[:end])
# Increment +1
print(s[:end] + str(number + 1))
[output]:
123456
foobar
foobar123457
Bonus: Can you do it with a one-liner?
Not exactly one line, but close:
import itertools
s = "foobar123456"
s = s if s[-1].isdigit() else s + "0"
number_str = "".join(itertools.takewhile(lambda ch: ch.isdigit(), reversed(s)))[::-1]
end = s.rindex(number_str)
print(s[:end] + str(int(number_str) + 1))
Bonus: But how about regex?
Yeah, with regex it's pretty magical, you would still make the same assumption as how we started, and to make your regex as simple as possible you have to add another assumption that the alphabetic characters preceding the number can only be made up of a-z or A-Z.
Then you can do this:
import re
s = "foobar123456"
s = s if s[-1].isdigit() else s + "0"
alpha, numeric = re.match("([a-zA-z]+)(\d.+)", s).groups()
print(alpha + str(int(numeric) + 1))
But you have to understand the regex which might be a steep learning, see https://regex101.com/r/9iiaCW/1
One simple solution would be:
Have two empty variables head (=non-numeric prefix) and tail (numeric suffix). Iterate the string normally, from left to right. If the current character is a digit, add it to tail. Otherwise, join head and tail, add the current char to head and empty tail. Once complete, increment tail and return head + tail:
def foo(s):
head = tail = ''
for char in s:
if char.isdigit():
tail += char
else:
head += tail + char
tail = ''
tail = int(tail or '0')
return head + str(tail + 1)
Leading zeroes (x001 -> x002), if needed, left as an exercise ;)
In your string, you need to check if it is alpha numeric or not. if it is alpha numeric, then you need to check the last character, whether it is digit or not.
now if above condition satisfy then you need to get the index of first digit in the string which make a integer number in last of string.
once you got the index then, seperate the character and numeric part.
once done, convert numerical string part to interger and add 1. after this join both character and numeric part. that is your answer.
# your code goes here
string = 'randomstring2345'
index = len(string) - 1
if string.isalnum() and string[-1].isdigit():
while True:
if string[index].isdigit():
index-=1
else:
index+=1
break
if index<0:
break
char_part = string[:index]
int_part = string[index:]
integer = 0
if int_part:
integer = int(''.join(int_part))
modified_int = integer + 1
new_string = ''.join([char_part, str(modified_int)])
print(new_string)
output
randomstring2346
Regex can be a useful tool in python~ Here I make two groups, the first (.*?) is as few of anything as possible, while the second (\d*$) is as many digits at the end of the string as possible. For more in depth explanation see regexr.
import re
def increment(s):
word, digits = re.match('(.*?)(\d*$)', s).groups()
digits = str(int(digits) + 1).zfill(len(digits)) if digits else '1'
return word + digits
print(increment('foobar001'))
print(increment('foobar009'))
print(increment('foobar19'))
print(increment('foobar20'))
print(increment('foobar99'))
print(increment('foobar'))
print(increment('1a2c1'))
print(increment(''))
print(increment('01'))
Output:
foobar002
foobar010
foobar20
foobar21
foobar100
foobar1
1a2c2
1
02
Source
def solve(data):
result = None
if len(data) == 0 or not data[-1].isdigit():
result = data + str(1) #appending 1
else:
lin = 0
for index, ch in enumerate(data[::-1]):
if ch.isdigit():
lin = len(data) - index -1
else:
break
result = data[0 : lin] + str(int(data[lin:]) + 1) # incrementing result
return result
pass
print(solve("Hey123"))
print(solve("aaabbbzzz"))
output :
Hey124
aaabbbzzz1

Shifting all the alphabets of a string by a certain step

input: ['baNaNa', 7] # string and step size
required output : 'utGtGt' # every character of string shifted backwards by step size
import ast
in_string = input()
lis = ast.literal_eval(in_string)
st = lis[0]
step = lis[1]
alphabets = 'abcdefghijklmnopqrstuvwxyz'
password = ''
for letter in st:
if letter in alphabets:
index_val = alphabets.index(letter) - (step)
password += alphabets[index_val]
print(password)
Output i am getting is 'utgtgt'. I want 'utGtGt'. Help on this would be appreciated a lot.
The string module has methods to create a transformation dictionary and a translate method to do exactly what you want:
st = "baNaNa"
step = 7
alphabets = 'abcdefghijklmnopqrstuvwxyz'
alph2 = alphabets.upper()
# lower case translation table
t = str.maketrans(alphabets, alphabets[-step:]+alphabets[:-step])
# upper case translation table
t2 = str.maketrans(alph2, alph2[-step:]+alph2[:-step])
# merge both translation tables
t.update(t2)
print(st.translate(t))
Output:
utGtGt
You give it the original string and an equal long string to map letters to and apply that dictionary using str.translate(dictionary).
The sliced strings equate to:
print(alphabets)
print(alphabets[-step:]+alphabets[:-step])
abcdefghijklmnopqrstuvwxyz
tuvwxyzabcdefghijklmnopqrs
which is what your step is for.
See Understanding slice notation if you never saw string slicing in use.
by processing each charater and checking it's cardinal no and making calculation accordingly help you to reach the result
def func(string, size):
if size%26==0:
size=26
else:
size=size%26
new_str = ''
for char in string:
if char.isupper():
if ord(char)-size<ord('A'):
new_str+=chr(ord(char)-size+26)
else:
new_str+=chr(ord(char)-size)
elif char.islower():
if ord(char)-size<ord('a'):
new_str+=chr(ord(char)-size+26)
else:
new_str+=chr(ord(char)-size)
return new_str
res =func('baNaNa', 7)
print(res)
# output utGtGt
Here's a simple solution that makes use of the % modulo operator to shift letters backwards.
It basically collects all of the letters in a reverse index lookup dictionary, so looking up letter positions is O(1) instead of using list.index(), which is linear O(N) lookups.
Then it goes through each letter and calculates the shift value from the letter index e.g. for the letter a with a shift value of 7, the calculation will be (0 - 7) % 26, which will give 19, the position of u.
Then once you have this shift value, convert it to uppercase or lowercase depending on the case of the original letter.
At the end we just str.join() the result list into one string. This is more efficient than doing += to join strings.
Demo:
from string import ascii_lowercase
def letter_backwards_shift(word, shift):
letter_lookups = {letter: idx for idx, letter in enumerate(ascii_lowercase)}
alphabet = list(letter_lookups)
result = []
for letter in word:
idx = letter_lookups[letter.lower()]
shifted_letter = alphabet[(idx - shift) % len(alphabet)]
if letter.isupper():
result.append(shifted_letter.upper())
else:
result.append(shifted_letter.lower())
return ''.join(result)
Output:
>>> letter_backwards_shift('baNaNa', 7)
utGtGt
I would probably go with #Patrick Artner's pythonic solution. I just showed the above implementation as a learning exercise :-).

Going back to begining of a string

I am coding a Caesar cipher. The key is an integer from 1 to 25. This cipher rotates the letters of the alphabet (A to Z). The encoding replaces each letter
with the 1st to 25th next letter in the alphabet (wrapping Z to A). So key 2 encrypts “HI” to “JK”, but key 20 encrypts “HI” to “BC”.
But If I put in
"I am super" it will output "k kc oouwrgt" when it should be "k co uwrgt" with a key of 2. It will also not go back to the beginning of the alphabet e.g 'x' will not go to 'a' with a key of 2. I use python 3.4.1
encode = []
a = "abcdefghijklmnopqrstuvwyxz"
a = list(a)
print(a)
e = input("encode or decode --->")
text = input("Sentence -->").lower()
text = list(text)
print(text)
Key = int(input("Key -->"))
if Key > 25:
print("Too high")
else:
print(Key)
if e == "encode":
for i, item in enumerate(text):
if item == " ":
encode.append(letter)
else:
num = a.index(item)
num = num + int(Key)
letter = a[num]
encode.append(letter)
for i in range(len(encode)):
print(encode[i])
When you encounter a space, you append the last letter again, instead of item:
if item == " ":
encode.append(letter)
This causes k and o to appear twice when the key is 2; you re-appended the encoded i -> k and m -> o results.
You need to use the % modulo operator to make your index 'wrap round':
num = (num + Key) % 26
I removed the int() call, you already turned Key to an integer earlier.
Other tips:
You don't need to turn a into a list; strings are sequences too and support indexing and the .index()method directly. The same applies to text; just loop over the string itself.
You are not using i in the for i, item in enumerate(text): loop; drop enumerate altogether: for item in text:.
You could just print your encoded characters directly in that loop, no need to use an encode list and a separate loop.
The str.join() method would let you print your encoded text all on one line: print(''.join(encode)) instead of your last for loop.
The absolute fastest method of encoding a string is to use a translation table, a dictionary mapping input characters to output characters, and the str.translate() method. You can use the str.maketrans() function to make that table:
import string
a = string.ascii_lowercase # why type yourself when the stdlib has these?
text = input("Sentence -->").lower()
Key = int(input("Key -->"))
mapping = str.maketrans(a, a[Key:] + a[:Key]) # letters to rotated letters
print(text.translate(mapping))
The trick lies in creating the second string for str.maketrans(); using slicing it is easy to create a rotated string, by taking everything from position Key onwards, and the first Key characters at the end:
>>> a[Key:] + a[:Key]
'cdefghijklmnopqrstuvwyxzab'
One obvious solution would be to use modulo for the alphabet index:
The % (modulo) operator yields the remainder from the division of the
first argument by the second.
>>> 12 % 26
12
>>> 26 % 26
0
>>> 28 % 26
2
As a bonus, you wouldn't need to check the key is lower than 25, and you'll never get an IndexError: list index out of range.
encode = []
a = "abcdefghijklmnopqrstuvwyxz"
e = input("encode or decode --->")
text = input("Sentence -->").lower()
key = int(input("Key -->"))
if e == "encode":
for i, item in enumerate(text):
if item == " ":
encode.append(item)
else:
num = a.index(item)
num = num + int(key)
letter = a[num % 26]
encode.append(letter)
for letter in encode:
print(letter)
A few notes:
a string is already an iterable of characters. No need to convert it to a list
letter wasn't defined in if item == " "
to iterate over a list, you don't need the length or the index.
to decode the message, just change the sign of the key. It should work just like encode, thanks to modulo : -2 % 26 # => 24
As an example:
encode or decode --->encode
Sentence -->i am super
Key -->2
k
c
o
u
w
r
g
t
and
encode or decode --->encode
Sentence -->k co uwrgt
Key -->-2
i
a
m
s
u
p
e
r

why is my caeser cipher only printing the last letter of string? python

def caesar_cipher(offset, string):
words = string.replace(" ", " ")
cipher_chars = "abcdefghijklmnopqrstuvwxyz"
word_i = 0
while word_i < len(words):
word = words[word_i]
letter_i = 0
while letter_i < len(word):
char_i = ord(word[letter_i]) - ord("c")
new_char_i = (char_i + offset) % 26
value = chr(new_char_i + ord("c"))
letter_i += 1
word_i += 1
return words.join(value)
print caesar_cipher(3, "abc")
Hey everyone, for some reason my ceasar cipher is only printing the last letter in my string, when I want it to cipher the whole string, for example, if i print an offset of 3 with string "abc" it should print def, but instead is just printing the f. Any help is greatly appreciated!
value is overwritten in the loop. You want to create a list passed to join (ATM you're joining only 1 character):
value = []
then
value.append(chr(new_char_i + ord("c")))
the join statement is also wrong: just do:
return "".join(value)
Note that there are other issues in your code. It seems to intent to process several words, but it doesn't, so a lot of loops don't loop (there's no list of words, it's just a word), so what you are doing could be summarized to (using a simple list comprehension):
def caesar_cipher(offset, string):
return "".join([chr((ord(letter) - ord("c") + offset) % 26 + ord("c")) for letter in string])
and for a sentence:
print(" ".join([caesar_cipher(3, w) for w in "a full sentence".split()]))
As a nice commenter noted, using c as start letter is not correct since it trashes sentences containing the 3 last letters. There's no reason not to start by a (the result are the same for the rest of the letters):
def caesar_cipher(offset, string):
return "".join([chr((ord(letter) - ord("a") + offset) % 26 + ord("a")) for letter in string])
Aside: a quick similar algorithm is rot13. Not really a cipher but it's natively supported:
import codecs
print(codecs.encode("a full sentence","rot13"))
(apply on the encoded string to decode it)

Selecting specific int values from list and changing them

I have been playing with Python and came across a task from MIT, which is to create coded message (Julius Cesar code where for example you change ABCD letters in message to CDEF). This is what I came up with:
Phrase = input('Type message to encrypt: ')
shiftValue = int(input('Enter shift value: '))
listPhrase = list(Phrase)
listLenght = len(listPhrase)
ascii = []
for ch in listPhrase:
ascii.append(ord(ch))
print (ascii)
asciiCoded = []
for i in ascii:
asciiCoded.append(i+shiftValue)
print (asciiCoded)
phraseCoded = []
for i in asciiCoded:
phraseCoded.append(chr(i))
print (phraseCoded)
stringCoded = ''.join(phraseCoded)
print (stringCoded)
The code works but I have to implement not shifting the ascii value of spaces and special signs in message.
So my idea is to select values in list in range of range(65,90) and range(97,122) and change them while I do not change any others. But how do I do that?
If you want to use that gigantic code :) to do something as simple as that, then you keep a check like so:
asciiCoded = []
for i in ascii:
if 65 <= i <= 90 or 97 <= i <= 122: # only letters get changed
asciiCoded.append(i+shiftValue)
else:
asciiCoded.append(i)
But you know what, python can do the whole of that in a single line, using list comprehension. Watch this:
Phrase = input('Type message to encrypt: ')
shiftValue = int(input('Enter shift value: '))
# encoding to cypher, in single line
stringCoded = ''.join(chr(ord(c)+shiftValue) if c.isalpha() else c for c in Phrase)
print(stringCoded)
A little explanation: the list comprehension boils down to this for loop, which is easier to comprehend. Caught something? :)
temp_list = []
for c in Phrase:
if c.isalpha():
# shift if the c is alphabet
temp_list.append(chr(ord(c)+shiftValue))
else:
# no shift if c is no alphabet
temp_list.append(c)
# join the list to form a string
stringCoded = ''.join(temp_list)
Much easier it is to use the maketrans method from the string module:
>>import string
>>
>>caesar = string.maketrans('ABCD', 'CDEF')
>>
>>s = 'CAD BA'
>>
>>print s
>>print s.translate(caesar)
CAD BA
ECF DC
EDIT: This was for Python 2.7
With 3.5 just do
caesar = str.maketrans('ABCD', 'CDEF')
And an easy function to return a mapping.
>>> def encrypt(shift):
... alphabet = string.ascii_uppercase
... move = (len(alphabet) + shift) % len(alphabet)
... map_to = alphabet[move:] + alphabet[:move]
... return str.maketrans(alphabet, map_to)
>>> "ABC".translate(encrypt(4))
'EFG'
This function uses modulo addition to construct the encrypted caesar string.
asciiCoded = []
final_ascii = ""
for i in ascii:
final_ascii = i+shiftValue #add shiftValue to ascii value of character
if final_ascii in range(65,91) or final_ascii in range(97,123): #Condition to skip the special characters
asciiCoded.append(final_ascii)
else:
asciiCoded.append(i)
print (asciiCoded)

Categories

Resources