Related
I'm a python student (by myself), and as an exercise I decided to try making a script to 'encrypt/decrypt' a message.
The 'encryption algo' that I'm using is very simple, I learned during military service and it was used for troops in the field to encrypt radio messages only.
I assume it's not a secure way to encrypt stuff. (If someone can comment on that, I would love to know more)
Anyway, I'm doing it as an exercise for programming logic, but I've been stuck for a while now.
Here it's how it works:
You get a keyword/phrase (More often used with 2 words (vertical and horizontal) but for now I'm coding the 1 keyword only).
Let's use 'PASSWORD' as key and the message: 'This is a sample message'. I would make a table with PASSWORD as colum index, and fill the table with the message:
P A S S W O R D
t h i s i s a s
a m p l e m e s
s a g e x y z x
[Since the message didn't complete all the columns we completed it with letters that won't cause issues]
Then, we determine the order for the scramble, deriving it alphabetically from the key:
4 1 6 7 8 3 5 2
P A S S W O R D
[a,d,o,p,r,s,s,w]
So line by line, letter by letter, we would take the letters from the message according to the key-order, and form the encrypted message:
'hsstaisi' for the first line, 'msmaeple' and 'axyszgex' for the second and third line.
So the message would be 'hsstaisimsmaepleaxyszgex' [Usually transmitted as "hssta isims maepl eaxys zgex" to make it easier for the radio operator]
Now the code:
I manage to make it work (kind of...), here is how:
I get the message and key, remove spaces, make both them into lists. I create a dictionary where every letter from the key(list) becomes a key in the dict, and the value is a number (from 0 to lenght of the key), like an iterator.
{ 'p':0, a':1, 's':2,... } #[Here is my problem]
After that we sort the key(list) alphabetically and use it as iterator to call for the key(dict) that will call for a number that will be a index from the message list. (My explanation is confusing, may be easier to understand by checking the code bellow).
Letter by letter the message is scrambled and appended in a new list, and then presented as 'encrypted'.
It works! Except if the keyphrase has repeated letters (like ours 'password'). In that situation the corresponding value of a repeated dictionary key gets overwritten, because dict keys are unique.
I've written several different versions for the same code, but I always get stuck in the dict problem, at some point or the other.
Here is the piece of code:
key = ['p','a','s','s','w','o','r','d']
msg = ['t','h','i','s','i','s','a','s','a','m','p','l','e','m','e','s','s','a','g','e']
def encrypt(key_list,msg_list):
while len(msg_list) % len(key_list) != 0:
rest = len(key_list) - (len(msg_list) % len(key_list))
for i in range(rest):
if msg_list[-1] == 'z':
msg_list.append('x')
else:
msg_list.append('z')
key_dict = {}
for i in range(len(key_list)):
key_dict[key_list[i]] = i
key_list.sort()
qnty_rows = len(msg_list) // len(key_list)
cloop = 0
scramble_list = []
while cloop < qnty_rows:
for i in range(len(key_list)):
scramble_list.append(msg_list[key_dict[key_list[i]]+(cloop*len(key_list))])
cloop +=1
encrypted_msg = "".join(scramble_list)
print(encrypted_msg)
Can someone help me find a solution to this, or point me at the right direction?
Considering that I'm still learning to code, any constructive criticism for the code in general is welcomed.
Your error lies in how you assign column numbers to each of the key characters, using a dictionary:
for i in range(len(key_list)):
key_dict[key_list[i]] = i
For repeated letters, only the last index remains; s from password maps first to 2, then to 3, so key_dict['s'] ends up being 3:
i = 0, key_list[i] == 'p', key_dict['p'] = 0
i = 1, key_list[i] == 'a', key_dict['a'] = 1
i = 2, key_list[i] == 's', key_dict['s'] = 2
i = 3, key_list[i] == 's', key_dict['s'] = 3 # replacing 2
i = 4, key_list[i] == 'w', key_dict['w'] = 4
# etc.
Don't use a dictionary; generate a list of paired index and character values, sort this by letter, then extract just the indices:
indices = [i for i, c in sorted(enumerate(key_list, key=lambda p: p[1]))]
I used the enumerate() function to generate the indices; it's achieves the same thing as your range(len(key_list)) loop in a more compact form.
Because enumerate() produces (index, value) pairs and we want to sort on the values (the characters), the above code uses a sort key extracting the values (p[1]).
Note that you don't need your key to be a list even, the above would work directly on a string too; strings are sequences just like lists are.
Here's how this works:
>>> keyphrase = 'password'
>>> list(enumerate(keyphrase)) # add indices
[(0, 'p'), (1, 'a'), (2, 's'), (3, 's'), (4, 'w'), (5, 'o'), (6, 'r'), (7, 'd')]
>>> sorted(enumerate(keyphrase), key=lambda p: p[1]) # sorted on the letter
[(1, 'a'), (7, 'd'), (5, 'o'), (0, 'p'), (6, 'r'), (2, 's'), (3, 's'), (4, 'w')]
>>> [i for i, c in sorted(enumerate(keyphrase), key=lambda p: p[1])] # just the indices
[1, 7, 5, 0, 6, 2, 3, 4]
Now you can use these indices to remap chunks of the plaintext input to encrypted output.
What you have is a called a columnar transposition cipher. For modern computers such a cipher is rather trivial to break. See https://crypto.stackexchange.com/questions/40119/how-to-solve-columnar-transposition-cipher-without-a-key for a discussion on how to approach cracking such a ciphertext.
I have values like
amity = 0
erudite = 2
etc.
And I am able to sort the integers with
print (sorted([amity, abnegation, candor, erudite, dauntless]))`
but I want the variable names to be attached to the integers as well, so that when the numbers are sorted I can tell what each number means.
Is there a way to do this?
Define a mapping between the names and the numbers:
numbers = dict(dauntless=42, amity=0, abnegation=1, candor=4, erudite=2)
Then sort:
d = sorted(numbers.items(), key=lambda x: x[1])
print(d)
# [('amity', 0), ('abnegation', 1), ('erudite', 2), ('candor', 4), ('dauntless', 42)]
To keep the result as a mapping/dictionary, call collections.OrderedDict on the sorted list:
from collections import OrderedDict
print(OrderedDict(d))
# OrderedDict([('amity', 0), ('abnegation', 1), ('erudite', 2), ('candor', 4), ('dauntless', 42)])
Python has a built in data-type called dictionary, it is used to map key, value pairs. It is pretty much what you asked for in your question, to attach a value into a specific key.
You can read a bit more about dictionaries here.
What I think you should do is to create a dictionary and map the names of the variables as strings to each of their integer values as shown below:
amity = 0
erudite = 2
abnegation = 50
dauntless = 10
lista = [amity, erudite, abnegation, dauntless]
dictonary = {} # initialize dictionary
dictionary[amity] = 'amity'# You're mapping the value 0 to the string amity, not the variable amity in this case.
dictionary[abnegation] = 'abnegation'
dictionary[erudite] = 'erudite'
dictionary[dauntless] = 'dauntless'
print(dictionary) # prints all key, value pairs in the dictionary
print(dictionary[0]) # outputs amity.
for item in sorted(lista):
print(dictionary[x]) # prints values of dictionary in an ordered manner.
i'm having trouble to merge the values of a dictionary whereas the dictionary varies in its key count
i found an working example using two lists like
t1 = [1,2,3]
t2 = ["a","b","c"]
output = list(zip(t1, t2))
which leads to [(1, 'a'), (2, 'b'), (3, 'c')] ... first success.
But I need to zip all the values from a dictionary, which varies in the count of the key values. (Sometimes there are 2 keys in, sometimes 4 and so on..)
Is there a way to do the zip with a dynamic input, dependent on the count of the keys
Lets say
t1 = [1,2,3]
t2 = ["a","b","c"]
generated_rows = OrderedDict()
generated_rows['t1'] = t1
generated_rows['t2']=t2
output = list(zip(??*))
the expected output would be as above:
[(1, 'a'), (2, 'b'), (3, 'c')]
but the parameters of the zip method should somehow come from the dictionary in a dynamic way. The following variing dicts should work with the method:
d1 = {'k1':[0,1,2], 'k2':['a','b','c']}
d2 = {'k1':[0,1,2], 'k2':['a','b','c'], 'k3':['x','y','z']}
d3 = ...
solution (thanks to Todd):
d1 = {'k1':[0,1,2], 'k2':['a','b','c']}
o = list(zip(*d1.values()))
If your second piece of code accurately represents what you want to do with N different lists, then the code would probably be:
t1 = [ 1, 2, 3 ]
t2 = [ 'a', 'b', 'c' ]
# And so on
x = []
x.append( t1 )
x.append( t2 )
# And so on
output = zip(*x)
You don't need the extra list() because zip() already returns a list. The * operator is sometimes referred to as the 'splat' operator, and when used like this represents unpacking the arguments.
A list is used instead of a dictionary because the 'splat' operator doesn't guarantee the order it unpacks things in beyond "whatever order the type in question uses when iterating over it". An ordered dictionary may work if the keys are selected to impose the correct ordering.
I'm working with a large set of records and need to sum a given field for each customer account to reach an overall account balance. While I can probably put the data in any reasonable form, I figured the easiest would be a list of tuples (cust_id,balance_contribution) as I process through each record. After the round of processing, I'd like to add up the second item for each cust_id, and I am trying to do it without looping though the data thousands of time.
As an example, the input data could look like:[(1,125.50),(2,30.00),(1,24.50),(1,-25.00),(2,20.00)]
And I want the output to be something like this:
[(1,125.00),(2,50.00)]
I've read other questions where people have just wanted to add the values of the second element of the tuple using the form of sum(i for i, j in a), but that does separate them by the first element.
This discussion, python sum tuple list based on tuple first value, which puts the values as a list assigned to each key (cust_id) in a dictionary. I suppose then I could figure out how to add each of the values in a list?
Any thoughts on a better approach to this?
Thank you in advance.
import collections
def total(records):
dct = collections.defaultdict(int)
for cust_id, contrib in records:
dct[cust_id] += contrib
return dct.items()
Would the following code be useful?
in_list = [(1,125.50),(2,30.00),(1,24.50),(1,-25.00),(3,20.00)]
totals = {}
for uid, x in in_list :
if uid not in totals :
totals[uid] = x
else :
totals[uid] += x
print(totals)
output :
{1: 125.0, 2: 30.0, 3: 20.0}
People usually like one-liners in python:
[(uk,sum([vv for kk,vv in data if kk==uk])) for uk in set([k for k,v in data])]
When
data=[(1,125.50),(2,30.00),(1,24.50),(1,-25.00),(3,20.00)]
The output is
[(1, 125.0), (2, 30.0), (3, 20.0)]
Here's an itertools solution:
from itertools import groupby
>>> x
[(1, 125.5), (2, 30.0), (1, 24.5), (1, -25.0), (2, 20.0)]
>>> sorted(x)
[(1, -25.0), (1, 24.5), (1, 125.5), (2, 20.0), (2, 30.0)]
>>> for a,b in groupby(sorted(x), key=lambda item: item[0]):
print a, sum([item[1] for item in list(b)])
1 125.0
2 50.0
I have some code written like so:
class Invite(models.Model):
STATE_UNKNOWN = 0
STATE_WILL_PLAY = 1
STATE_WONT_PLAY = 2
STATE_READY = 3
STATE_CHOICES = ((STATE_UNKNOWN, _("Unknown")),
(STATE_WILL_PLAY, _("Yes, I'll play")),
(STATE_WONT_PLAY, _("Sorry, can't play")),
(STATE_READY, _("I'm ready to play now")))
...
def change_state(self, state):
assert(state in dict(Invite.STATE_CHOICES))
This code works like I want it to, but I'm curious as to why it works this way. It is admittedly very convenient that it does work this way, but it seems like maybe I'm missing some underlying philosophy as to why that is.
If I try something like:
dict((1,2,3), (2,2,3), (3,2,3))
ValueError: dictionary update sequence element #0 has length 3; 2 is required
it doesn't create a dict that looks like
{1: (2,3), 2: (2,3), 3: (2,3)}
So the general pattern is not to take the first part of the tuple as the key and the rest as the value. Is there some fundamental underpinning that causes this behavior, or it is just, well, it would be convenient if it did....
I think it's somewhat obvious. In your example, (1,2,3) is a single object. So the idea behind a dictionary is to map a key to a value (i.e. object).
So consider the output:
>>> dict(((1,(2,3)), (2,(2,3)))).items()
[(1, (2, 3)), (2, (2, 3))]
But you can also do something like this:
>>> dict((((1,2),3), ((2,2),3)))
[((1, 2), 3), ((2, 2), 3)]
Where the key is actually an object too! In this case a tuple also.
So in your example:
dict((1,2,3), (2,2,3), (3,2,3))
how do you know which part of each tuple is the key and which is the value?
If you find this annoying, it's a simple fix to write your own constructor:
def special_dict(*args):
return dict((arg[0], arg[1:]) for arg in args)
Also, to Rafe's comment, you should define the dictionary right away:
class Invite(models.Model):
STATE_UNKNOWN = 0
STATE_WILL_PLAY = 1
STATE_WONT_PLAY = 2
STATE_READY = 3
STATE_CHOICES = dict(((STATE_UNKNOWN, _("Unknown")),
(STATE_WILL_PLAY, _("Yes, I'll play")),
(STATE_WONT_PLAY, _("Sorry, can't play")),
(STATE_READY, _("I'm ready to play now"))))
...
def change_state(self, state):
assert(state in Invite.STATE_CHOICES)
If you ever want to iterate over the states, all you have to do is:
for state, description = Invite.STATE_CHOICES.iteritems():
print "{0} == {1}".format(state, description)
The construction of the dictionary in your change_state function is unnecessarily costly.
When you define the Django field, just do:
models.IntegerField(sorted(choices=Invite.STATE_CHOICES.iteritems()))
The constructor of dict accepts (among other things) a sequence of (key, value) tuples. Your second examples passes a list of tuples of length 3 instead of 2, and hence fails.
dict([(1, (2, 3)), (2, (2, 3)), (3, (2, 3))])
however will create the dictionary
{1: (2, 3), 2: (2, 3), 3: (2, 3)}
The general pattern is just this: you can create a dict from a list (in general: iterable) of pairs, treated as (key, value). Anything longer would be arbitrary: why (1,2,3)->{1:(2,3)} and not (1,2,3)-> {(1,2):3}?
Moreover, the pairs<->dict conversion is obviously two-way. With triples it couldn't be (see the above example).