I want to convert all the even letters using one function and all the odd numbers using another function. So, each letter represents 0-25 correspsonding with a-z, so a,c,e,g,i,k,m,o,q,s,u,w,y are even characters.
However, only my even letters are converting correctly.
def encrypt(plain):
charCount = 0
answer=[]
for ch in plain:
if charCount%2==0:
answer.append(pycipher.Affine(7,6).encipher(ch))
else:
answer.append(pycipher.Affine(3,0).encipher(ch))
return ''.join(answer)
You never change charCount in your loop -- So it starts at 0 and stays at 0 which means that each ch will be treated as "even".
Based on your update, you actually want to check if the character is odd or even based on it's "index" in the english alphabet. Having some sort of mapping of characters to numbers is helpful here. You could build it yourself:
alphabet = 'abcde...' # string.ascii_lowercase?
mapping = {k: i for i, k in enumerate(alphabet)}
OR we can use the builtin ord noticing that ord('a') produces an odd result, ord('b') is even, etc.
def encrypt(plain):
answer=[]
for ch in plain:
if ord(ch) % 2 == 1: # 'a', 'c', 'e', ...
answer.append(pycipher.Affine(7,6).encipher(ch))
else: # 'b', 'd', 'f', ...
answer.append(pycipher.Affine(3,0).encipher(ch))
return ''.join(answer)
Your basic approach is to re-encrypt a letter each time you see it. With only 26 possible characters to encrypt, it is probably worth pre-encrypting them, then just performing a lookup for each character in the plain text. While doing that, you don't need to compute the position of each character, because you know you are alternating between even and odd the entire time.
import string
def encrypt(plain):
# True == 1, False == 0
fs = [pycipher.Affine(3,0).encipher,
pycipher.Affine(7,6).encipher]
is_even = True # assuming "a" is even; otherwise, just set this to False
d = dict()
for ch in string.ascii_lowercase:
f = fs[is_even]
d[ch] = f(ch)
is_even = not is_even
return ''.join([d[ch] for ch in plain])
You can also use itertools.cycle to simplify the alternation for you.
def encrypt(plain):
# again, assuming a is even. If not, reverse this list
fs = itertools.cycle([pycipher.Affine(3,0).encipher,
pycipher.Affine(7,6).encipher])
d = dict((ch, f(ch)) for f, ch in zip(fs, string.ascii_lowercase))
return ''.join([d[ch] for ch in plain])
This are my two cents on that. What #mgilson is proposing also works of course but not in the way you specified (in the comments). Try to debug your code in your head after writing it.. Go through the for loop and perform 1-2 iterations to see whether the variables take the values you intended them to. charCount is never reassigned a value. It is always 0. And, yes charCount += 1 would make it change but not in the way you want it to..
def encrypt(plain):
alphabet = 'abcdefghijklmnopqrwstuvwxyz'
answer = ''
for letter in plain:
try:
if alphabet.index(letter.lower()) % 2 == 0:
answer += pycipher.Affine(7, 6).encipher(letter)
else:
answer += pycipher.Affine(3, 0).encipher(letter)
except:
answer += letter
return answer
my_text = 'Your question was not very clear OP'
encripted_text = encrypt(my_text)
Also, i would not use ord(ch) because ord('a') = 97 and not 0 therefore odd instead of even.
Since your notion of even letter is based on the position of a character in the alphabet, you could use ord(), like this:
if ord(ch)%2==0:
Note that ord('a') and ord('A') are both odd, so that would make a go in the else part. If you want the opposite, then just negate the condition:
if ord(ch)%2!=0:
Related
I am new to coding and try to extract and print the none digits. I've written 2 different codes but cannot combine them. I would appreciate some advices. (i tried using *args but didn't work)
def SumOfDigits(str1):
sum_digit = 0
for x in str1:
if x.isdigit():
z = int(x)
sum_digit += z
print("The sum of digits operation is", sum_digit, end=".")
return
def SumOfDigits(input):
valids = []
for character in input:
if character.isalpha():
valids.append(character)
print("The extracted non-digits are:", ''.join(valids))
return
El. Nik, i believe that those 2 function you not be merged as they are doing different behavior, which lead to a more complex function, that might get a bit confusing.
Anyway, what you want is to merge your two for loop into a single one. Since str.isalpha matches do not collide with str.isdigit ones, you can safely use a if statement followed by an elif to check every charater and apply the wanted behavior.
Then, you simply return the 2 results as a tuple.
def digit_extraction(string):
sum_digits = 0
extracted_alphas = ""
for char in string:
if char.isdigit():
sum_digits += int(char)
elif char.isalpha():
extracted_alphas += char
return sum_digits, extracted_alphas
Here is a quick example:
>>> digit_extraction("1a2b3c4d5e6f7g8h9i")
(45, 'abcdefghi')
If you dont know how multiple return value works in python, i'd suggest to check the following tutorial:
https://datagy.io/python-return-multiple-values/
To get into something more advanced, separating the 2 function would allow for writting something considered as more pythonic:
def sum_of_digits(string):
return sum(int(c) for c in string if c.isalpha())
and
def extract_alphas(string):
return ''.join(filter(str.isalpha, c))
This is a python question. Answer should be with O(n) time complexity and use no additional memory. As input i get a string which should be classified as palindrome or not (palindrome is as word or a phrase that can be read the same from left to right and from right to left, f.e "level"). In the input there can be punctuation marks and gaps between words.
For example "I. did,,, did I????" The main goal is to decide whether the input is a palindrome.
When I tried to solve this question i faced several challenges. When I try to delete non letter digits
for element in string:
if ord(element) not in range(97, 122):
string.remove(element)
if ord(element) == 32:
string.remove(element)
I use O(n^2) complexity, because for every element in the string i use remove function, which itself has O(n) complexity, where n is the length of the list. I need help optimizing the part with eliminating non letter characters with O(n) complexity
Also, when we get rid of spaces as punctuation marks I know how to check whether a word is a palindrome, but my method uses additional memory.
Here is your O(n) solution without creating a new string:
def is_palindrome(string):
left = 0
right = len(string) - 1
while left < right:
if not string[left].isalpha():
left += 1
continue
if not string[right].isalpha():
right -= 1
continue
if string[left] != string[right]:
return False
left += 1
right -= 1
return True
print(is_palindrome("I. did,,, did I????"))
Output:
True
I'm assuming you mean you want to test if a string is a palindrome when we remove all punctuation digits from the string. In that case, the following code should suffice:
from string import ascii_letters
def is_palindrome(s):
s = ''.join(c for c in s if c in ascii_letters)
return s == s[::-1]
# some test cases:
print(is_palindrome('hello')) # False
print(is_palindrome('ra_ceca232r')) # True
Here's a one-liner using assignment expression syntax (Python 3.8+):
>>> s = "I. did,,, did I????"
>>> (n := [c.lower() for c in s if c.isalpha()]) == n[::-1]
True
I mostly showed the above as a demonstration; for readability's sake I'd recommend something more like SimonR's solution (although still using isalpha over comparing to ascii_letters).
Alternatively, you can use generator expressions to do the same comparison without allocating O(n) extra memory:
def is_palindrome(s):
forward = (c.lower() for c in s if c.isalpha())
back = (c.lower() for c in reversed(s) if c.isalpha())
return all(a == b for a, b in zip(forward, back))
Note that zip still allocates in Python 2, you'll need to use itertools.izip there.
Will this help:
word = input('Input your word: ')
word1 = ''
for l in word:
if l.isalnum():
word1 += l
word2=''
for index in sorted(range(len(word1)),reverse=True):
word2+=word1[index]
if word1 == word2:
print('It is a palindrone.')
else:
print('It is not a palindrone.')
I am making a program in python that count up the number of letter pairs.
For example ------> 'ddogccatppig' will print 3, 'a' will print 0, 'dogcatpig' will print 0, 'aaaa' will print 3, and 'AAAAAAAAAA' will print 9.
My teacher told me to use a for loop to get the i and i+1 index to compare. I do not know how to do this, and I am really confused. My code:
def count_pairs( word ):
pairs = 0
chars = set(word)
for char in chars:
pairs += word.count(char + char)
return pairs
Please help me!!! Thank you.
The for loop is only to iterate through the appropriate values of i, not directly to do the comparison. You need to start i at 0, and iterate through i+1 being the last index in the string. Work this out on paper.
Alternately, use i-1 and i; then you want to start i-1 at 0, which takes less typing:
for i in range(1, len(word)):
if word[i] == word[i-1]:
...
Even better, don't use the counter at all -- make a list of equality results and count the True values:
return sum([word[i] == word[i-1] for i in range(1, len(word))])
This is a bit of a "dirty trick" using the fact that True evaluates as 1 and False as 0.
If you want to loop over indices instead of the actual characters, you can do:
for i in range(len(word)):
# do something with word[i] and/or word[i+1] or word[i-1]
Converting the string to a set first is counterproductive because it removes the ordering and the duplicates, making the entire problem impossible for two different reasons. :)
Here is an answer:
test = "ddogccatppig"
def count_pairs(test):
counter = 0
for i in range(0,len(test)-1):
if test[i] == test[i+1]
counter+=1
return counter
print(count_pairs(test))
Here you iterate through the length of the string (minus 1 because otherwise you will get an index out of bounds exception). Add to a counter if the letter is the same as the one in front and return it.
This is another (similar) way to get the same answer.
def charPairs(word):
word = list(word)
count = 0
for n in range(0, len(word)-1):
if word[n] == word[n+1]:
count +=1
return count
print(charPairs("ddogccatppig"))
print(charPairs("a"))
print(charPairs("dogcatpig"))
print(charPairs("aaaa"))
print(charPairs("AAAAAAAAAA"))
I'm working on problem 3(set 1) of the cryptopals challenges (https://cryptopals.com/sets/1/challenges/3)
I've already found the key ('x') and decrypted the message ('Cooking mcs like a pound of bacon')
Here is my code:
from hexToBase64 import hexToBinary
from fixedXOR import xorBuffers
def binaryToChar(binaryString):
asciiValue = 0
for i in range(int(len(binaryString))-1,-1,-1):
if(binaryString[i] == '1'):
asciiValue = asciiValue + 2**(7-i)
return chr(asciiValue)
def decimalToBinary(number):
binaryString = ""
while (number != 0):
bit = number % 2
binaryString = str(bit) + binaryString
number = int(number/2)
while(len(binaryString) < 8):
binaryString = "0" + binaryString
return binaryString
def breakSingleByteXOR(cipherString):
decryptedMess = ""
lowestError = 10000
realKey = ""
for i in range(0,128):
errorChar = 0
tempKey = decimalToBinary(i)
tempMess = ""
for j in range(0,len(cipherString),2):
#Take each byte of the cipherString
cipherChar = hexToBinary(cipherString[j:j+2])
decryptedChar = binaryToChar(xorBuffers(cipherChar,tempKey))
asciiValue = ord(decryptedChar)
if (not ((asciiValue >= 65) and (asciiValue <= 90)) \
or ((asciiValue >= 90) and (asciiValue <= 122)) \
or ( asciiValue == 32 )):
# if the character is not one of the characters ("A-Z" or "a-z"
# or " ") consider it as an "error"
errorChar += 1
tempMess = tempMess + decryptedChar
if(errorChar < lowestError):
lowestError = errorChar
decryptedMess = tempMess
realKey = chr(i)
return (realKey,decryptedMess)
if __name__ == "__main__":
print(breakSingleByteXOR("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"))
The problem is when I use the function breakSingleByteXOR to return one value (decryptedMess), it came out okay "cOOKING mcS LIKE A POUND OF BACON"
But when I return 2 values with the function (as the code above - (key,decryptedMess)), I received a weird result ('x', 'cOOKING\x00mc\x07S\x00LIKE\x00A\x00POUND\x00OF\x00BACON'), can anyboby explain to me why this is the case?
Tbh, I'm learning python as I'm doing the challenges so hopefully I dont trigger anyone with these code.... I'd also really appreciate it if anyone could give me some advices on writing good python code
Thanks guys :D
It's true that the reason for the difference in the printed string is a quirk of the print function.
The deeper problem with that program is that it's not producing the correct answer. That's because the big ugly if that tries to decide whether a decrypted character is in the acceptable range is incorrect.
It's incorrect in two ways. The first is that (asciiValue >= 90) should be (asciiValue >= 97). A better way to write all of those expressions, which would have avoided this error, is to express them as (asciiValue >= ord('a')) and (asciiValue == ord(' ')) and so on, avoiding the inscrutable numbers.
The second way is that the expressions are not properly grouped. As they stand they do this:
character is not in the range 'A' to 'Z',
or character is in the range 'a' to 'z',
or character is 'space',
then count this as an error
so some of the characters that should be good (specifically 'a' through 'z' and space) are counted as bad. To fix, you need to rework the parentheses so that the condition is:
character is not in the range 'A' to 'Z',
and character is not in the range 'a' to 'z',
and character is not space,
then count this as an error
or (this is style you were trying for)
character is not (in the range 'A' to 'Z'
or in the range 'a' to 'z'
or a space)
I'm not going to give you the exact drop-in expression to fix the program, it'll be better for you to work it out for yourself. (A good way to deal with this kind of complexity is to move it into a separate function that returns True or False. That makes it easy to test that your implementation is correct, just by calling the function with different characters and seeing that the result is what you wanted.)
When you get the correct expression, you'll find that the program discovers a different "best key" and the decrypted string for that key contains no goofy out-of-range characters that behave strangely with print.
The print function is the culprit - it is translating the characters \x00 and \x07 to ASCII values when executed. Specifically, this only occurs when passing a string to the print function, not an iterable or other object (like your tuple).
This is an example:
>>> s = 'This\x00string\x00is\x00an\x00\x07Example.'
>>> s
'This\x00string\x00is\x00an\x00\x07Example.'
>>> print(s)
This string is an Example.
If you were to add the string s to an iterable (tuple, set, or list), s will not be formatted by the print function:
>>> s_list = [s]
>>> print(s_list) # List
['This\x00string\x00is\x00an\x00\x07Example.']
>>> print(set(s_list)) # Set
{'This\x00string\x00is\x00an\x00\x07Example.'}
>>> print(tuple(s_list)) # Tuple
('This\x00string\x00is\x00an\x00\x07Example.')
Edit
Because the \x00 and \x07 bytes are ASCII control characters, (\x00 being NUL and \x07 being BEL), you can't represent them in any other way. So one of the only ways you could strip these characters from the string without printing would be to use the .replace() method; but given \x00 bytes are being treated as spaces by the terminal, you would have to use s.replace('\x00', ' ') to get the same output, which has now changed the true content of the string.
Otherwise when building the string; you could try and implement some logic to check for ASCII control characters and either not add them to tempMess or add a different character like a space or similar.
References
ASCII Wiki: https://en.wikipedia.org/wiki/ASCII
Curses Module: https://docs.python.org/3.7/library/curses.ascii.html?highlight=ascii#module-curses.ascii (Might be useful if you wish to implement any logic).
I have a string of integers like '32102739' and a dictionary like {32: 'a', '739': 'b', '102': 'c'}. Given that string, I want to return the output string 'acb'.
I am trying
def change(string_, letters, length=1):
if string_[:length] in letters:
return letters[string_[:length]]
else:
return change(string_, letters, length+1)
I am having trouble with checking until the first letter is found, and then continuing to check for the next letter.
Assuming that there is no ambiguity (and that the numbers don't overlap), one could proceed as:
s = '32102739'
d = {'32': 'a', '739': 'b', '102': 'c'}
ret = ''
#start at the beginning of the string
pos, N = 0, len(s)
while pos < N:
#start at the current position pos and keep
#adding characters until a match is found
found = False
for i in range(pos+1, N+1):
#t = int(s[pos:i])
t = s[pos:i]
if t in d:
ret += d[t]
found = True
break
#if no match is found, signal an "invalid" input
if not found: raise ValueError
#update current position in the string
pos = i
print(ret) #gives 'acb'
Your code could work with just a few minor tweaks (at least, it will work for non-overlapping dictionary keys). You just need to change your successful match case so that you recurse on the rest of the string, after translating a prefix. You'll also need to add a base case, so that the recursion stops when the matching fails, or there's nothing left to match.
def change(string_, letters, length=1):
if len(string_) < length: # base case
return "" # you might want to test if string_ is empty and if not, raise an exception
elif string_[:length] in letters:
return letters[string_[:length]] + change(string_[length:], letters) # add recursion
else:
return change(string_, letters, length+1)
I'll end with a note that recursion is not as efficient in Python as it is in some other languages. You could restructure your function to use iteration instead of recursion, and it would probably be more efficient and "Pythonic". ewcz's answer is a good example of such a restructuring.