Explanation of `ch[:prefix_len%len(ch)]` in python program - python

I am looking at this python program and almost understood its flow but I am unable to understand ch[:prefix_len%len(ch)] in the following part:
else:
prefix = ch * (prefix_len/len(ch)) + ch[:prefix_len%len(ch)]
suffix = ch * (suffix_len/len(ch)) + ch[:suffix_len%len(ch)]
Here is the context:
def banner(text, ch='=', length=78):
if text is None:
return ch * length
elif len(text) + 2 + len(ch)*2 > length:
# Not enough space for even one line char (plus space) around text.
return text
else:
remain = length - (len(text) + 2)
prefix_len = remain / 2
suffix_len = remain - prefix_len
if len(ch) == 1:
prefix = ch * prefix_len
suffix = ch * suffix_len
else:
prefix = ch * (prefix_len/len(ch)) + ch[:prefix_len%len(ch)]
suffix = ch * (suffix_len/len(ch)) + ch[:suffix_len%len(ch)]
return prefix + ' ' + text + ' ' + suffix
Could somebody please help me to understand this. Thank you.

Sure!
ch[:prefix_len % len(ch)] is accessing a slice of the ch sequence starting from the beginning (since there's no value before the : and going to one character before the index defined by prefix_len % len(ch).
This value is prefix_len (defined earlier as the length of the prefix, not surprisingly) modulus the length of ch. (Think of it as the remainder left over after integer division of prefix_len / len(ch).
I ran the function like: print(banner("Hello everyone!", "1234")) and got:
123412341234123412341234123412 Hello everyone! 1234123412341234123412341234123
so you can see it's fitting the ch value (1234 in my case) in the space it has.

They're adding the remainder.
Say prefix = 10, and ch = '#&+'
If you just multiply ch by prefix_len / len(ch), you'll get 9, but you know you need 10.
So ch[:prefix_len % len(ch)] is just indexing into ch string for the remainder.
Make sense?

Related

Python program with letters pyramid

I need help with python. I have to do this and I don't know how can I add letters to * pyramid. Thank you first program second program
This will help you:
length = 7 # length of the string
word = "Python" # string to print
# first program
string = '.' * (length - 1) + word
for i in range(length - 1):
print(string[i:i + length])
# second program
for i in range(1, length):
_str = word[:i] + '.' * (length - i)
print(_str[::-1] + _str)
The output is:
......P
.....Py
....Pyt
...Pyth
..Pytho
.Python
......PP......
.....yPPy.....
....tyPPyt....
...htyPPyth...
..ohtyPPytho..
.nohtyPPython.

Vigenere Cipher Key not working as expected

I have constructed a Vigenere cipher function that works reasonably well, except for the fact that it does not pass the coding test that I'm required to run it through.
This is due to the fact that I'm using the ordinal values, while the test is expecting me to use a function to just rotate through a string letters, I think.
I have already run through the issue of dealing with non-alpha characters and got it to work with both upper case and lower case characters. However, it seems to fall apart if the key has any variance of upper case or lower case characters i.e. the key is lower case but the plain text is upper.
def encrypt(text, key):
cipher_text = []
key = list(key)
if len(text) == len(key):
return(key)
else:
for i in range(len(text) -
len(key)):
key.append(key[i % len(key)])
for i in range(len(text)):
a = text[i]
if a.isalpha() and a.islower():
x = ((ord(text[i]) + ord(key[i])-97) % 26 + 97)
cipher_text.append(chr(x))
elif a.isalpha() and a.isupper():
x = ((ord(text[i]) + ord(key[i])-65) % 26 + 65)
cipher_text.append(chr(x))
else:
cipher_text.append(a)
return("" . join(cipher_text))
def main():
mess = input(str("What is message?"))
key = input("What is key?")
print(encrypt(mess, key))
if __name__ == '__main__':
main()
For vigenere.encrypt('BaRFoo', 'BaZ')
You should have returned this:
'CaQGon'
But you actually returned this:
'PtDTha'
from your single example it seems like the key is case insensitive:
def encrypt_letter(c, k):
if c.isupper():
base = ord('A')
else:
base = ord('a')
return chr((ord(c) - base + ord(k) - ord('a')) % 26 + base)
def encrypt(text, key):
key = key.lower()
return "".join([encrypt_letter(text[i], key[i % len(key)]) for i in range(len(text))])
def main():
print(encrypt('BaRFoo', 'BaZ'))
if __name__ == '__main__':
main()
First of all, I don't program in python, so excuse my form. I however tested everything in online compiler.
Before I answer your question, I'm not sure about this segment:
if len(text) == len(key):
return(key)
else:
for i in range(len(text) -
len(key)):
key.append(key[i % len(key)])
I read that as "If the key has the same length as plaintext, the key is the ciphertext", which obviously isn't true, unless the key is "aaaaaaaaa...". I would expect something like this:
if len(text) > len(key):
for i in range(len(text) -
len(key)):
key.append(key[i % len(key)])
# else:
# I don't care - the key is long enough
From Kfir Dadosh's answer I'd also point out, that you don't actually need this step and might as well directly access the key as key[i % len(key)].
As for the issue you were referring to. You only check whether plaintext (message) is lowercase or uppercase and change the key (let's call it normalization - the act of converting letter to number in the range 0-25 denoting it's position in alphabet) according to that.
if a.isalpha() and a.islower():
x = ((ord(text[i]) + ord(key[i])-97) % 26 + 97)
Here you take raw ^ ^
ascii value instead of | |
normalized (0-25) number |
|
Here you normalize the key according to the
message case
Following this are some heavy spoilers, so if you understand and want to solve the problem yourself, then stop reading here.
I'd suggest to separate the normalization and encryption steps, to avoid the confusion. Let's get rid of the special characters first instead of last, because it's easy to do and we'll only have to worry about letters:
if not (text[i].isalpha()):
cipher_text.append(text[i])
continue; # done with this symbol - skip to the next one
Then normalize the letters, with the help of built in method. Let's use variables p for plaintext, k for key and c for ciphertext (later):
p = ord(text[i].lower()) - ord('a') # converts to lowercase, then to number
k = ord(key[i].lower()) - ord('a') # both key AND plaintext
I used ord('a') instead of 65 as I'm used to that and find it clearer, but it is a matter of preference and language traditions (which I'm not accustomed with).
Then the encryption step:
c = (p + k) % 26;
And now to restore the capitalization. We destroyed it in the variables p and k, but we still have their source array text[] with intact values. We can use that to restore the capitalization (assuming the case should be inherited from plaintext):
if (text[i].islower()):
cipher_text.append(chr(c + ord('a')))
elif (text[i].isupper()):
cipher_text.append(chr(c + ord('A')))
That completes the main loop. Feel free to modify it to be more python compliant.
for i in range(len(text)):
if not (text[i].isalpha()):
cipher_text.append(text[i])
continue;
p = ord(text[i].lower()) - ord('a')
k = ord(key[i].lower()) - ord('a')
# k = ord(key[i % len(key)].lower()) - ord('a')
# if you skipped the key lengthening process
c = (p + k) % 26;
if (text[i].islower()):
cipher_text.append(chr(c + ord('a')))
elif (text[i].isupper()):
cipher_text.append(chr(c + ord('A')))
else:
# not sure if this can happen in python, but if it does, we're probably
# in trouble
# otherwise it might as well be merged with the elif above
return("" . join(cipher_text))

Shortening a string

I have a string:
a = babababbaaaaababbbab
And it needs to be shortened so it looks like this:
(ba)3(b)2(a)5ba(b)3ab
So basically it needs to take all repeating characters and write how many times they are repeating instead of printing them.
I managed to do half of this:
from itertools import groupby
a = 'babababbaaaaababbbab'
grouped = ["".join(grp) for patt,grp in groupby(a)]
solved = [str(len(i)) + i[0] for i in grouped if len(i) >= 2]
but this only does this for characters that are repeating but not patterns. I get it that I could do this by finding 'ab' pattern in string but this needs to be viable for every possible string. Has anyone encountered something similar?
You can easily do this with regex:
>>> repl= lambda match:'({}){}'.format(match.group(1), len(match.group())//len(match.group(1)))
>>> re.sub(r'(.+?)\1+', repl, 'babababbaaaaababbbab')
'(ba)3(b)2(a)5ba(b)3ab'
Not much to explain here. The pattern (.+?)\1+ matches repeating character sequences, and the lambda function rewrites them to the form (sequence)number.
This is what I came up with, the code is a mess, but I just wanted to have a quick fun, so I let it be like this
a = 'babababbaaaaababbbab'
def compress(text):
for i in range(1, len(text) // 2):
for j, c in enumerate(text[:-i if i > 0 else len(text)]):
pattern = text[j:i+j]
new_text = pattern_repeats_processor(pattern, text, j)
if new_text != text:
return compress(new_text)
return text
def pattern_repeats_processor(pattern, text, i):
chunk = pattern
count = 1
while chunk == pattern and i + (count + 1) * len(pattern) < len(text):
chunk = text[i + count * len(pattern): i + (count + 1) * len(pattern)]
if chunk == pattern:
count = count + 1
else:
break
if count > 1:
return text[:i] + '(' + pattern + ')' + str(count) + text[i + (count + 0) * len(pattern):]
return text
print(compress(a))
print(a)
It makes
babababbaaaaababbbab =>
(ba)3(b)2(a)5ba(b)3ab
P.S. Of course answer of Rowing is miles better, pretty impressive even
I'm not sure what exactly you're looking for but here hope this helps.
A=a.count('a')
B=a.count('b')
AB=a.count('ab')
BAB=a.count('bab')
BA=a.count('ba')
print(A,'(a)',B,'(b)',AB,'(ab)',BAB,'(bab)',BA,'(ba)')

Python Simple Encryption program: How to loop Z back to A

I'm trying to make an encryption function that encrypts plaintext messages but the problem is that if i input a key too large that goes past 'Z' then it goes onto greater unicode values.
My code:
def encrypt(var1,var2):
var3 = ""
for i in range(0, len(var1)):
if ord(var1[i])>64 and ord(var1[i])<90:
var3=var3+chr((ord(var1[i])+var2))
elif ord(var1[i])+var2>90:
???
else:
continue
return(var3)
How do I get it to loop 'Z' back to 'A'. I think I have to make an if statement like this but I'm not sure what to put into it.
elif ord(var1[i])+var2>90:
???
Here is my one! Im using the modulus operator to wrap around every 26 numbers (the number of letter between a-z). I also handle upper on lowercase separately.
def encrypt(data, shift):
result = ''
for c in data:
c_num = ord(c)
# is the letter lower case a - z?
if (c_num >= ord('a')) and (c_num <= ord('z')):
# get the letter number from 0 - 26
c_num = c_num - ord('a')
# shift the number
c_num += shift
# wrap the number every 26 numbers
c_num = c_num % 26
# now increase a by the new amount
c_num += ord('a')
result += chr(c_num)
# is the letter upper case A - Z?
elif (c_num >= ord('A')) and (c_num <= ord('Z')):
# get the letter number from 0 - 26
c_num = c_num - ord('A')
# shift the number
c_num += shift
# wrap the number every 26 numbers
c_num = c_num % 26
# now increase a by the new amount
c_num += ord('A')
result += chr(c_num)
return result
encrypt('aAbB', 2)
'cCdD'
encrypt('afZz', 2)
'chBb'
Here is the code golf version using list comprehension just for fun!
def encrypt(data, shift):
return ''.join([chr(((ord(c) - ord('a') + shift) % 26) + ord('a')) if ord(c) in range(ord('a'), ord('z')+1) else chr(((ord(c) - ord('A') + shift) % 26) + ord('A')) for c in data])
A straight-forward way would be to check if you have passed beyond Z, and modify the character in that case:
...
if var1[i] >= 'A' and var1[i] <= 'Z':
translated_char = chr(ord(var1[i])+var2)
if translated_char > 'Z':
# If the resulting character is beyond Z,
# we go 26 characters back
translated_char = chr(ord(translated_char)-26)
# Append the translated character to the output string
var3 += translated_char
...
You may want to consider more descriptive variable names -- you'll thank yourself if you revisit your code after two months :-)
I would recommend using the modulus operator to do what you are wanting. In python that is the % character. In modulus math. X % Y tells us what the remainder of X / Y is. For example. 27 % 26 is 1. Using this you can get your wrap around that you want. Here is a sample bit of code to encrypt a single character
def encrypt_character( valToEncrypt, keyVal ):
# Update the character to be our standard Alphabet mapping
# A -> 0; B->1 ... Z -> 25
x = ord(valToEncrypt) - ord('A')
# Perform the Encryption
retVal = ( x + keyVal ) % 26
# Translate back to the standard ASCII mapping of the character
# for display in python and translate it back into a string
retVal = chr(retVal + ord('A'))
return retVal
# end encrypt_character
Now if we feed the character "A" Into our encryption algorithm with a key of 13 we get "N" as shown:
>>> encrypt_character("A", 13)
'N'
The decrypt algorithm is very similar, except you do subtraction instead of addtion
def decrypt_character( valToDecrypt, keyVal ):
# Update the character to be our standard Alphabet mapping
# A -> 0; B->1 ... Z -> 25
x = ord(valToDecrypt) - ord('A')
retVal = ( x - keyVal ) % 26
# Translate back to the standard ASCII mapping of the character
# for display in python and translate it back into a string
retVal = chr(retVal + ord('A'))
return retVal
To encrypt a string you can use the following function:
from re import sub
def encrypt_message( message, key ):
# Convert the message text into a plain text with all spaces and
# punctuation removed.
plainText = sub(r'[^A-Z]', '', message.upper())
cipherText = ""
charIndex = 0
# Encrypt the message 1 character at a time
while charIndex < len(plainText):
cipherText += \
encrypt_character( plainText[charIndex], key)
charIndex += 1
return cipherText
This function can be called:
>>> encrypt_message("HELLO World!", key=23)
'EBIILTLOIA'
The decryption function is very similar to the encryption function, except it calls the decrypt utility instead of the encrypt utility.

Shift cipher in Python: error using ord

I want to replace each character of a string by a different one, shifted over in the alphabet. I'm shifting by 2 in the example below, so a -> c, b -> d, etc.
I'm trying to use a regular expression and the sub function to accomplish this, but I'm getting an error.
This is the code that I have:
p = re.compile(r'(\w)')
test = p.sub(chr(ord('\\1') + 2), text)
print test
where the variable text is an input string.
And I'm getting this error:
TypeError: ord() expected a character, but string of length 2 found
I think the problem is that I the ord function is being called on the literal string "\1" and not on the \w character matched by the regular expression. What is the right way to do this?
It won't work like this. Python first runs chr(ord('\\') + 2 and then passes that result to p.sub.
You need to put it in a separate function or use an anonymous function (lambda):
p = re.compile(r'(\w)')
test = p.sub(lambda m: chr(ord(m.group(1)) + 2), text)
print test
Or better yet use maketrans instead of regular expressions:
import string
shift = 2
t = string.maketrans(string.ascii_lowercase, string.ascii_lowercase[shift:] +
string.ascii_lowercase[:shift])
string.translate(text, t)
Full version
def shouldShift(char):
return char in string.lowercase
def caesarShift(string, n):
def letterToNum(char):
return ord(char)-ord('a')
def numToLetter(num):
return chr(num+ord('a'))
def shiftByN(char):
return numToLetter((letterToNum(char)+n) % 26)
return ''.join((shiftByN(c) if shouldShift(c) else c) for c in string.lower())
One-liner
If you really want a one-liner, it would be this, but I felt it was uglier:
''.join(chr((ord(c)-ord('a')+n)%26 + ord('a')) for c in string)
Demo
>>> caesarShift(string.lowercase, 3)
'defghijklmnopqrstuvwxyzabc'
Try this, using list comprehensions:
input = 'ABC'
''.join(chr(ord(c)+2) for c in input)
> 'CDE'
It's simpler than using regular expressions.
def CaesarCipher(s1,num):
new_str = ''
for i in s1:
asc_V = ord(i)
if asc_V in range(65, 91):
if asc_V + num > 90:
asc_val = 65 + (num - 1 - (90 - asc_V))
else:
asc_val = asc_V + num
new_str = new_str + chr(asc_val)
elif (asc_V in range(97, 123)):
if asc_V + num > 122:
asc_val = 97 + (num - 1 - (122 - asc_V))
else:
asc_val = asc_V + num
new_str = new_str + chr(asc_val)
else:
new_str = new_str + i
return new_str
print (CaesarCipher("HEllo", 4))
print (CaesarCipher("xyzderBYTE", 2))

Categories