python, encryption of any size code with a smaller key - python

So I'm trying to work to create a program which can take two inputs such as
encrypt('12345','12')
and it will return
'33557'
where the code ('12345') and been incremented by the key ('12'), working from right to left.
I have already created one which will work for when the code and key are both 8 long, but I cannot work out how to do this should the code be allowed to be any size, possibly with nested for statments?
Here is the one i did early so you can see better what i am trying to do
def code_block(char,charBlock):
if len(char) == 8 and len(charBlock) == 8: #Check to make sure both are 8 didgets.
c = char
cB = charBlock
line = ""
for i in range(0,8):
getDidget = code_char2(front(c),front(cB))
c = last(c)
cB = str(last(cB))
line =line + getDidget
print(line)
else:
print("Make sure two inputs are 8 didgets long")
def front(word):
return word[:+1]
def last(word):
return word[+1:]

Some code tested on Python 3.2:
from decimal import Decimal
import itertools
def encrypt(numbers_as_text, code):
key = itertools.cycle(code[::-1])
num = Decimal(numbers_as_text)
power = 1
for _ in numbers_as_text:
num += power * int(next(key))
power *= Decimal(10)
return num
if __name__ == "__main__":
print(encrypt('12345','12'))
Some explanation:
code[::-1] is a cool way to reverse a string. Stolen from here
itertools.cycle endlessly repeats your key. So the variable key now contains a generator which yields 2, 1, 2, 1, 2, 1, etc
Decimal is a datatype which can handle arbitrary precision numbers. Actually Python 3's integer numbers would be sufficient because they can handle integer numbers with arbitrary number of digits. Calling the type name as a function Decimal(), calls the constructor of the type and as such creates a new object of that type. The Decimal() constructor can handle one argument which is then converted into a Decimal object. In the example, the numbers_as_text string and the integer 10 are both converted into the type Decimal with its constructor.
power is a variable that starts with 1 is multiplied by 10 for every digit that we have worked on (counting from the right). It's basically a pointer to where we need to modify num in the current loop iteration
The for loop header ensures we're doing one iteration for each digit in the given input text. We could also use something like for index in range(len(numbers_as_text)) but that's unnecessarily complex
Of course if you want to encode text, this approach does not work. But since that wasn't in your question's spec, this is a function focused on dealing with integers.

Related

Python for iteration with previous variable

the function is meant to do the follow, "to get the n (non-negative integer) copies of the first 2 characters of a given string. Return the n copies of the whole string if the length is less than 2."
Can anyone tell me what does the substr do in line 12?
I get how it works previously on line 8 (when string is larger than 2), but it looses me on how it works on line 12, where the string is lower than 2.
def substring_copy(str, n):
"""
Method 2
"""
f_lenght = 2
if f_lenght > len(str): # If strings length is larger than 2
f_lenght = len(str) # Length of string will be len(str)
substr = str[:f_lenght] # substr = str[:2] (slice 0 y 1)
# If length is shorter than 2
result = ""
for i in range(n):
result = result + substr
return result
print ("\nMethod 2:")
print(substring_copy('abcdef', 2))
print(substring_copy('p', 3));
If the length of p is 1, then isn't it a case that substr isn't that important and the for loop will run 3 (thanks to 3* in the last line of code)?
Thanks in advance!
I think I got it, substr is important for if there are more than 2 characters in the string. When there are less than 2, substr could have a value of 200; the p string would still be just one p and that would concatenate n times (3 times in this example).
So as you inferred substr is just the substring of length 2 (or less) from the original, which can be the input itself if it's already 2 or less.
It's your "duplication target", basically.
Though I do want to point out that the entire thing is a rather bad style, it is over-complicated and doesn't make good use of python:
str is the python string type (which also acts as a conversion function), it's a builtin, shadowing builtin is a bad idea and str is a common builtin, naming a variable str is terrible style, sometimes it's justifiable, but not here
python slicing is "bounded" to what it's slicing, so e.g. "ab"[:5] will return "ab", unlike regular indexing it does not care that the input is too short, this means the entire mess with f_lenght is unnecessary, you can just
substr = s[:2]
Python strings have an override for multiplication, str * n repeats the string n times, this also works with lists (though that is more risky because of mutability and reference semantics)
So the entire function could just be:
def substring_copy(s, n):
return s[:2] * n
The prompt is also not great because of the ambiguity of the word "character", but I guess we can let that slide

Running multiline test cases

I have the following prompt:
We want you to calculate the sum of squares of given integers, excluding any negatives.
The first line of the input will be an integer N (1 <= N <= 100), indicating the number of test cases to follow.
Each of the test cases will consist of a line with an integer X (0 < X <= 100), followed by another line consisting of X number of space-separated integers Yn (-100 <= Yn <= 100).
For each test case, calculate the sum of squares of the integers, excluding any negatives, and print the calculated sum in the output.
Note: There should be no output until all the input has been received.
Note 2: Do not put blank lines between test cases solutions.
Note 3: Take input from standard input, and output to standard output.
Specific Rules for Python Solution:
Your source code must be a single file, containing at least a main function
Do not use any for loop, while loop, or any list / set / dictionary comprehension
I have written my square_sum function as:
def square_sum(arr):
if not arr:
return 0
value = arr[0]
if value < 0:
value = 0
return value**2 + square_sum(arr[1:])
square_sum([9, 6, -53, 32, 16])
However, I cannot figure out how to run the multiline test cases on my function and display the result in the aforementioned format. Interestingly, there can be any number of test cases so how do I add the capability to accommodate them? I would like some guidance in this part, thank you.
Assuming that this assignment is to see how to replace all iteration with recursion, and withholding all judgement on the wisdom of doing so, here is a sample solution.
You have already implemented the inner loop. My suggestion would be to add the parsing to that loop, since otherwise you have to either use map or replace it with another recursion.
def square_sum(lst):
if not lst:
return 0
value = int(lst[0])
if value < 0:
value = 0
return value**2 + square_sum(lst[1:])
The outer loop will need to read two lines: the first (discarded) line will contain the number of elements. The second line will contain the strings that you will pass to square_sum. To control the depth of your recursion, use the first line of the input, which tells you how many samples there will be:
def run(n):
count = int(input())
print(square_sum(input().split()))
if n > 1:
run(n - 1)
def main()
n = int(input())
run(n)
Your question asks for a main function. If you need to run it in your module, go ahead and do that:
main()

Why doesn't my code generate random numbers without str?

Can someone explain to me why when I remove 'str' from 7 line of code "myset.add(str(i))", do I get the same numbers every time I hit run and get random numbers with 'str' included?
def RandomFunction(x, y):
myset = set()
mylist = []
listofnumbers = []
for i in range(x, y):
myset.add(str(i))
for x in myset:
mylist.append(int(x))
if len(mylist) <= 5:
print('In order to generate 5 numbers, the range of input needs to be higher')
else:
for y in mylist:
listofnumbers.append(y)
if len(listofnumbers) >= 5:
print(listofnumbers)
break
RandomFunction(10, 20)
Set keeps the elements in hash table, it uses default python's hash() function. Its implementation for numeric types looks like this:
def hash(number):
return number % (2 ** 61 - 1)
So, if the numbers aren't huge, hash value of an integer will be equal to the same integer. Because of this integers in python's set will be kept in ascending order (for also reads hash table in ascending order).
But string is a sequense of unicode characters with \0 at the end and python has another implementation of hash() for strings, so it won't work the same way for them.

Why is i in my code not considered an integer?

Im new to Python and am trying to write a function that returns the amount of 1s in a binary number (i.e if the input is 12, then it should return 2, since 12 is 1100 in binary. However I keep getting the error "TypeError: list indices must be integers, not str" and I don't know whats wrong, I have googled the problem but havent understood what the answers meant.
Heres the code (I know it could be shorter I'm just asking the question because I get this error often and don't know why it comes):
def count_ones(num):
x=0
for i in bin(num):
if list(bin(num))[i] ==1:
x += 1
return x
Simply, bin(x) returns a string representing the binary number, as the docs point out.
So for i in bin(num): iterates over the characters in that string.
You can achieve what you want by checking if i == '1' instead, so your code would be
def count_ones(num):
x=0
for i in bin(num):
if i == '1':
x += 1
return x
Consider simplifying your function, however, by using the count method to count the occurrences of '1' in the binary representation, as follows:
def count_ones(num):
return bin(num).count('1')
bin returns a string, where you are iterating over each bit as a string. You should use int(i). You should probably use format or str.format to remove the 0b prefix as b is not an integer.
for i in format(num, 'b'):
I would just use a generator expression for this.
def count_ones(num):
return sum(1 for digit in bin(num) if digit == '1')
Example
>>> count_ones(50)
3
>>> bin(50)
'0b110010'
For what it's worth, the reason your code is not working is that bin returns a string, so each element you loop over will by of type str not int.

lightly alter a hash programmatically

At the moment I frequently have to do something in unittests with hashes and cryptographic signatures. Sometimes they get generated, and I just need to alter one slightly and prove that something no longer works. They are strings of hex-digits 0-9 and a-f of specific length. Here is a sample 64 long:
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
I want to change just one character somewhere in there.
You can't be sure that every digit 0 - 9 and a - f will be in there, although would guess it's at least 95% certain that they all are. If you could be sure, I would just run h = h.replace('a', 'b', 1) on it.
If you do it manually, you can just look at it and see the third digit is 2 and run:
new = list(h)
new[2] = '3'
h = ''.join(new)
But if you cannot see it and it needs to happen programmatically, what is a clean and certain way to change just one character in it somewhere?
from random import randrange
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
i = randrange(len(h))
new_h = h[:i] + hex(int(h[i], 16) + randrange(1, 16))[-1:] + h[i+1:]
In words:
choose a random index i in h
split the string into the part before the index, the char at the index, and the rest
replace the char at the index with its hex value incremented by a random int between 1 and 15, modulo 16 (i.e., its rightmost hex character)
build the new string from the above pieces
Note that an increment by a value between 1 and 15 (included), followed by a modulo 16, never maps a hex digit onto itself. An increment by 0 or 16 would map it exactly onto itself.
You can just choose a random index
import random
valid_chars = '0...f'
def replace_hash(hash_digest):
idx_to_replace = random.randint(64)
char_to_replace = hash_digest[idx_to_replace]
replacements = valid_chars.replace(char_to_replace, '')
hash_digest[idx_to_replace] = replacements[random.randint(15)
return hash_digest
The most efficient way is to just replace the first char with 1 of 2 replacements. I mean, you can only collide with one char anyway so there's no need to do it randomly. But if you want a random change the function'll work.
I suggest you increment the last character of the hash (cycling to 0 after f). That way you are sure to get a different hash, only differing by one character.
You can easily extend this method to change a character at the position of your choosing, and not just the last one.
h = '702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a'
def change_hash(h, index=-1):
digits = list(h)
old_digit= digits[index]
v = int(old_digit, 16)
new_v = (v+1)%16
new_digit = '{:x}'.format(new_v)
digits[index] = new_digit
return ''.join(digits)
print(change_hash(h))
# 702b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91b
# ^
print(change_hash(h, 2))
# 703b31faad0246cc89a5dc782cdf5235a885d0f529fb30a4e1e70e00938df91a
# ^
EDIT:
added option to change a digit at an arbitrary position
formatting the digit using format() as it was proposed in another answer
h = chr(ord(h[0]) + ((-1) if (h[0] in "9z") else 1)) + h[1:]

Categories