How shuffle and then unshuffle a bytearray, given a key as seed - python

I am trying to create an encryption system, and for that I will be taking a bytearray, and I intend to use a
random.Random(seed).shuffle(bytearray)
function to encrypt the information.
I'm having trouble reversing this process for the deencryption, i tried something like (didnt work) :
random.Random(1/seed).shuffle(encryptedbytearray)
Is there anyway to do this?

Shuffle a sorted range, so that we can match the shuffled indicies to the unshuffled indicies.
x = list(range(len(s)))
random.Random(seed).shuffle(x)
For a seed of 12345, this produces [14, 15, 12, 3, 24, 16, 7, 22, 10, 2, 19, 4, 20, 17, 1, 21, 5, 25, 18, 8, 6, 11, 9, 0, 23, 13]. This indicates that the value in the 0th index in the shuffled list is actually in the 14th index of the unshuffled list, the 1st index is actually the 15th unshuffled, etc.
Then match each shuffled index to the shuffled value, and then sort (based on the index) back into their unshuffled positions.
unshuffled = bytearray(c for i, c in sorted(zip(x, s)))
print(unshuffled)
Full example:
import random
# setup
s = bytearray(b"abcdefghijklmnopqrstuvxwyz")
seed = 12345
random.Random(seed).shuffle(s)
# shuffle a sorted range, so that we can match the shuffled indicies to the unshuffled indicies
x = list(range(len(s)))
random.Random(seed).shuffle(x)
# match each shuffled index to the shuffled value, and then sort (based on the index) back into their unshuffled positions
unshuffled = bytearray(c for i, c in sorted(zip(x, s)))
print(unshuffled)
The process detailed above should work for any shuffled sequence (eg. lists), not just bytearrays.
Here is a more detailed explanation of this process on crypto.se with a lot more math involved.

You need to use the same seed to shuffle indexes so that you can backtrack the original positions. (enumerate will allow you to avoid sorting that mapping)
import random
def encrypt(decrypted,seed=4):
encrypted = decrypted.copy()
random.Random(seed).shuffle(encrypted)
return encrypted
def decrypt(encrypted,seed=4):
decrypted = encrypted.copy()
indexes = list(range(len(encrypted)))
random.Random(seed).shuffle(indexes)
for e,d in enumerate(indexes):
decrypted[d] = encrypted[e]
return decrypted
Sample run (using list of characters but it will work for bytearrays or any other type of lists):
clearText = list('ABCDE')
encryptedText = encrypt(clearText)
print(encryptedText)
['D', 'E', 'A', 'C', 'B']
decryptedText = decrypt(encryptedText)
print(decryptedText)
['A', 'B', 'C', 'D', 'E']
If you want the functions to work "in place" directly on the array (instead of returning a value), you can writ them like this:
def encrypt(decrypted,seed=4):
random.Random(seed).shuffle(encrypted)
def decrypt(encrypted,seed=4):
before = encrypted.copy()
indexes = list(range(len(encrypted)))
random.Random(seed).shuffle(indexes)
for e,d in enumerate(indexes):
encrypted[d] = before[e]

Related

Explain [:0] in Python

Ok, so before I get flamed for not RTFM, I understand that [:0] in my case of:
s ="itsastring"
newS= []
newS[:0] = s
ends up converting s to a list through slicing. This is my end goal, but coming from a Java background, I don't fully understand the "0" part in "[:0] and syntactically why it's placed there (I know it roughly means increase by 0). Finally, how does Python know that I want to have each char of s be an element based on this syntax? I want to understand it so I can remember it more clearly.
If S and T are sequences, S[a:b] = T will replace the subsequence from index a to b-1 of S by the elements of T.
If a == b, it will act as a simple insertion.
And S[:0] is the same thing as S[0:0] : so it's a simple insertion at the front.
s = [11,22,33,44,55,66,77]
s[3:3] = [1,2,3] # insertion at position 3
print( s )
s = [11,22,33,44,55,66,77]
s[3:4] = [1,2,3] # deletion of element at position 3, and then insertion
print( s )
s = [11,22,33,44,55,66,77]
s[3:6] = [1,2,3] # deletion of elements from position 3 to 5, and then insertion
print( s )
s = [11,22,33,44,55,66,77]
s[:] = [1,2,3] # deletion of all elements, and then insertion : whole replacement
print( s )
output:
[11, 22, 33, 1, 2, 3, 44, 55, 66, 77]
[11, 22, 33, 1, 2, 3, 55, 66, 77]
[11, 22, 33, 1, 2, 3, 77]
[1, 2, 3]
Hope it helps:
s ="itsastring"
#if you add any variable in left side then python will start slicing from there
#and slice to one less than last index assigned on right side
newS = s[:]
So the [:0] means slice the string from the beggining to the 0 (or again the first) element in the s string and the result is nothing. By default the slicing is done from the first element to the last or print(s[0:]) is the same as print(s).
I suggest you try a loop through the whole string like this:
s = [x for x in "itsastring"]
print(s)
# result
['i', 't', 's', 'a', 's', 't', 'r', 'i', 'n', 'g']

How to get a list of values from tuples which the second value is the same as first in the next tuple?

I'm having trouble trying to create a list of values from a list of tuples, which link to where the second value is the same as the first value in another tuple, that starts and ends with certain values.
For example:
start = 11
end = 0
list_tups = [(0,1),(0, 2),(0, 3),(261, 0),(8, 15),(118, 32),(11, 8),(15, 118),(32, 261)]
So I want to iterate through those list of tups, starting with the one which is the same as the start value and searching through the tups where it'll end with the end value.
So my desired output would be:
[11, 8, 15, 118, 32, 261, 0]
I understand how to check the values i'm just having trouble with interating through the tuples every time to check if there is a tuple in the list that matches the second value.
You are describing pathfinding in a directed graph.
>>> import networkx as nx
>>> g = nx.DiGraph(list_tups)
>>> nx.shortest_path(g, start, end)
[11, 8, 15, 118, 32, 261, 0]
This doesn't work with end = 0 because there is no 0 at the end, but here it is with 32:
>>> start = 11
>>> end = 32
>>> flattened = [i for t in list_tups for i in t]
>>> flattened[flattened.index(start):flattened.index(end, flattened.index(start))+1]
[11, 8, 15, 118, 32]
You can recursively search the tuples, moving the start value closer and closer. The path will be accumulated as we move back up through the chain You may need to tweak the path a little to get your desired outcome (I believe you'll need to append the first starting value, then reverse it).
def find(start, end, tuples, path):
for t in tuples:
if t[0] == start:
if t[1] == end or find(t[1], end, tuples):
path.append(t[1])
return True
return False

What is the fastest way to convert a dictionary frequency to list in Python?

I have dictionary frequency as follows:
freq = {'a': 1, 'b': 2, 'c': 3}
It simply means that I have one a's, twob's, and three c's.
I would like to convert it into a complete list:
lst = ['a', 'b', 'b', 'c', 'c', 'c']
What is the fastest way (time-efficient) or most compact way (space-efficient) to do so?
Yes, but only if the items are (or can be represented as) integers, and if the number of items between the smallest and largest item is sufficiently close to the difference between the two, in which case you can use bucket sort, resulting in O(n) time complexity, where n is the difference between the smallest and the largest item. This would be more efficient than using other sorting algorithms, with an average time complexity of O(n log n).
In the case of List = [1, 4, 5, 2, 6, 7, 9, 3] as it is in your question, it is indeed more efficient to use bucket sort when it is known that 1 is the smallest item and 9 is the largest item, since only 8 is missing between the range. The following example uses collections.Counter to account for the possibility that there can be duplicates in the input list:
from collections import Counter
counts = Counter(List)
print(list(Counter({i: counts[i] for i in range(1, 10)}).elements()))
This outputs:
[1, 2, 3, 4, 5, 6, 7, 9]
Let's break this into two O(N) passes: one to catalog the numbers, and one to create the sorted list. I updated the variable names; List is an especially bad choice, given the built-in type list. I also added 10 to each value, so you can see how the low-end offset works.
coll = [11, 14, 15, 12, 16, 17, 19, 13]
last = 19
first = 11
offset = first
size = last-first+1
# Recognize all values in a dense "array"
need = [False] * size
for item in coll:
need[item - offset] = True
# Iterate again in numerical order; for each True value, add that item to the new list
sorted_list = [idx + offset for idx, needed_flag in enumerate(need) if needed_flag]
print(sorted_list)
OUTPUT:
[11, 12, 13, 14, 15, 16, 17, 19]
The most compact way I usually use is list comprehension -
lst = ['a', 'b', 'b', 'c', 'c', 'c']
freq = {i: 0 for i in lst}
for i in lst: freq[i] += 1
Space complexity - O(n)
Time complexity - O(n)

How to apply a dict in python to a string as opposed to a single letter

I am trying to output the alphabetical values of a user entered string, I have created a dict and this process works, but only with one letter.
If I try entering more than one letter, it returns a KeyError: (string I entered)
If I try creating a list of the string so it becomes ['e', 'x', 'a', 'm', 'p', 'l', 'e'] and I get a TypeError: unhashable type: 'list'
I cannot use the chr and ord functions (I know how to but they aren't applicable in this situation) and I have tried using the map function once I've turned it to a list but only got strange results.
I've also tried turning the list into a tuple but that produces the same error.
Here is my code:
import string
step = 1
values = dict()
for index, letter in enumerate(string.ascii_lowercase):
values[letter] = index + 1
keyw=input("Enter your keyword for encryption")
keylist=list(keyw)
print(values[keylist])
Alt version without the list:
import string
step=1
values=dict()
for index, letter in enumerate(string.ascii_lowercase):
values[letter] = index + 1
keyw=input("Enter your keyword for encryption")
print(values[keyw])
You need to loop through all the letters and map each one individually:
mapped = [values[letter] for letter in keyw]
print(mapped)
This uses a list comprehension to build the list of integers:
>>> [values[letter] for letter in 'example']
[5, 24, 1, 13, 16, 12, 5]
The map() function would do the same thing, essentially, but returns an iterator; you need to loop over that object to see the results:
>>> for result in map(values.get, 'example'):
... print(result)
5
24
1
13
16
12
5
Note that you can build your values dictionary in one line; enumerate() takes a second argument, the start value (which defaults to 0); using a dict comprehension to reverse the value-key tuple would give you:
values = {letter: index for index, letter in enumerate(string.ascii_lowercase, 1)}
You most certanly can use ord()
inp = input('enter stuff:')
# a list of the ord() value of alphabetic character
# made uppercase and subtracted 64 --> position in the alphabet
alpha_value = [ord(n.upper())-64 for n in inp if n.isalpha()]
print(alpha_value)
Test:
import string
print([ord(n.upper())-64 for n in string.ascii_lowercase if n.isalpha()])
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
You can write simple for loop to map alphabet to integer.
try to this.
print[(item, (values[item]))for item in keylist]

What does the random.sample() method in Python do?

I want to know the use of random.sample() method and what does it give? When should it be used and some example usage.
According to documentation:
random.sample(population, k)
Return a k length list of unique elements
chosen from the population sequence. Used for random sampling without
replacement.
Basically, it picks k unique random elements, a sample, from a sequence:
>>> import random
>>> c = list(range(0, 15))
>>> c
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> random.sample(c, 5)
[9, 2, 3, 14, 11]
random.sample works also directly from a range:
>>> c = range(0, 15)
>>> c
range(0, 15)
>>> random.sample(c, 5)
[12, 3, 6, 14, 10]
In addition to sequences, random.sample works with sets too:
>>> c = {1, 2, 4}
>>> random.sample(c, 2)
[4, 1]
However, random.sample doesn't work with arbitrary iterators:
>>> c = [1, 3]
>>> random.sample(iter(c), 5)
TypeError: Population must be a sequence or set. For dicts, use list(d).
random.sample() also works on text
example:
> text = open("textfile.txt").read()
> random.sample(text, 5)
> ['f', 's', 'y', 'v', '\n']
\n is also seen as a character so that can also be returned
you could use random.sample() to return random words from a text file if you first use the split method
example:
> words = text.split()
> random.sample(words, 5)
> ['the', 'and', 'a', 'her', 'of']
random.sample(population, k)
It is used for randomly sampling a sample of length 'k' from a population. returns a 'k' length list of unique elements chosen from the population sequence or set
it returns a new list and leaves the original population unchanged and the resulting list is in selection order so that all sub-slices will also be valid random samples
I am putting up an example in which I am splitting a dataset randomly. It is basically a function in which you pass x_train(population) as an argument and return indices of 60% of the data as D_test.
import random
def randomly_select_70_percent_of_data_from_1_to_length(x_train):
return random.sample(range(0, len(x_train)), int(0.6*len(x_train)))
from random import *
lst1 = sample(range(0, 1000), 100)
lst2 = sample(range(0, 1000), 100)
print(lst1)
print(lst2)
print(set(lst1).intersection(set(lst2)))

Categories